// This is not autogenerated.
package academy

import (
	"encoding/json"
	"fmt"
	"html"
	"strconv"
	"strings"

	"github.com/gofrs/uuid"
	openapi_types "github.com/oapi-codegen/runtime/types"
)

// HugoQuiz captures the legacy academy-theme quiz JSON shape so callers can
// decode published content and normalize it into the canonical Quiz model.
type HugoQuiz struct {
	ID                         string             `json:"id"`
	Title                      string             `json:"title"`
	OrgID                      string             `json:"org_id"`
	Description                string             `json:"description"`
	Slug                       string             `json:"slug"`
	RelPermalink               string             `json:"relPermalink"`
	Permalink                  string             `json:"permalink"`
	Type                       string             `json:"type"`
	Section                    string             `json:"section"`
	Layout                     string             `json:"layout"`
	Date                       openapi_types.Date `json:"date"`
	Final                      bool               `json:"final"`
	Lastmod                    openapi_types.Date `json:"lastmod"`
	Draft                      bool               `json:"draft"`
	FilePathLegacy             string             `json:"file_path"`
	FilePathCamel              string             `json:"filePath,omitempty"`
	PassPercentageLegacy       float32            `json:"pass_percentage"`
	PassPercentageCamel        *float32           `json:"passPercentage,omitempty"`
	TimeLimitLegacy            hugoQuizInt        `json:"time_limit"`
	TimeLimitCamel             *int               `json:"timeLimit,omitempty"`
	MaxAttemptsLegacy          int                `json:"max_attempts"`
	MaxAttemptsCamel           *int               `json:"maxAttempts,omitempty"`
	Questions                  []HugoQuestion     `json:"questions"`
	TotalQuestionsLegacy       int                `json:"total_questions"`
	TotalQuestionsCamel        *int               `json:"totalQuestions,omitempty"`
	TotalQuestionsInBankLegacy int                `json:"total_questions_in_bank"`
	TotalQuestionsInBankCamel  *int               `json:"totalQuestionsInBank,omitempty"`
	TotalQuestionSetsLegacy    int                `json:"total_question_sets"`
	TotalQuestionSetsCamel     *int               `json:"totalQuestionSets,omitempty"`
	TotalMarksLegacy           int                `json:"total_marks"`
	TotalMarksCamel            *int               `json:"totalMarks,omitempty"`
	Prerequisites              []HugoParent       `json:"prerequisites"`
	Parent                     *HugoParent        `json:"parent,omitempty"`
	NextPageLegacy             HugoParent         `json:"next_page"`
	NextPageCamel              *HugoParent        `json:"nextPage,omitempty"`
}

type HugoParent struct {
	ID           string `json:"id"`
	Title        string `json:"title"`
	RelPermalink string `json:"relPermalink"`
	Type         string `json:"type"`
}

type HugoQuestion struct {
	ID                    string               `json:"id"`
	Text                  string               `json:"text"`
	Type                  string               `json:"type"`
	Marks                 int                  `json:"marks"`
	MultipleAnswers       *bool                `json:"multipleAnswers,omitempty"`
	MultipleAnswersLegacy *bool                `json:"multiple_answers,omitempty"`
	Options               []HugoQuestionOption `json:"options"`
	CorrectAnswer         string               `json:"correct_answer"`
	CorrectAnswerCamel    string               `json:"correctAnswer,omitempty"`
}

type HugoQuestionOption struct {
	ID             string `json:"id"`
	Text           string `json:"text"`
	IsCorrect      bool   `json:"is_correct"`
	IsCorrectCamel *bool  `json:"isCorrect,omitempty"`
}

type hugoQuizInt int

func (value *hugoQuizInt) UnmarshalJSON(data []byte) error {
	var intValue int
	if err := json.Unmarshal(data, &intValue); err == nil {
		*value = hugoQuizInt(intValue)
		return nil
	}

	var stringValue string
	if err := json.Unmarshal(data, &stringValue); err != nil {
		return fmt.Errorf("decode quiz integer from %q: %w", string(data), err)
	}

	normalized := html.UnescapeString(strings.TrimSpace(stringValue))
	normalized = strings.Trim(normalized, `"`)
	if normalized == "" {
		*value = 0
		return nil
	}

	intValue, err := strconv.Atoi(normalized)
	if err != nil {
		return fmt.Errorf("parse quiz integer %q: %w", normalized, err)
	}

	*value = hugoQuizInt(intValue)
	return nil
}

func (quiz *Quiz) UnmarshalJSON(data []byte) error {
	type canonicalQuiz Quiz
	var alias canonicalQuiz
	if err := json.Unmarshal(data, &alias); err == nil {
		*quiz = Quiz(alias)
		return nil
	}
	canonicalErr := json.Unmarshal(data, &alias)

	var legacy HugoQuiz
	if err := json.Unmarshal(data, &legacy); err != nil {
		return canonicalErr
	}

	canonical, err := legacy.ToCanonical()
	if err != nil {
		if looksLikeHugoQuiz(data) {
			return err
		}
		return fmt.Errorf("unmarshal canonical academy quiz: %w; legacy fallback: %v", canonicalErr, err)
	}

	*quiz = canonical
	return nil
}

func (quiz HugoQuiz) ToCanonical() (Quiz, error) {
	orgID, err := parseRequiredUUID("org_id", quiz.OrgID)
	if err != nil {
		return Quiz{}, err
	}

	quizID := quiz.deriveQuizUUID()
	questions := make([]Question, len(quiz.Questions))
	for index, question := range quiz.Questions {
		canonicalQuestion, err := quiz.toCanonicalQuestion(quizID, index, question)
		if err != nil {
			return Quiz{}, err
		}
		questions[index] = canonicalQuestion
	}

	prerequisites := make([]Parent, len(quiz.Prerequisites))
	for index, prerequisite := range quiz.Prerequisites {
		canonicalParent, err := quiz.toCanonicalParent(prerequisite)
		if err != nil {
			return Quiz{}, err
		}
		prerequisites[index] = canonicalParent
	}

	var parent *Parent
	if quiz.Parent != nil {
		if !quiz.Parent.isZero() {
			canonicalParent, err := quiz.toCanonicalParent(*quiz.Parent)
			if err != nil {
				return Quiz{}, err
			}
			parent = &canonicalParent
		}
	}

	nextPage, err := quiz.toCanonicalParent(quiz.nextPage())
	if err != nil {
		return Quiz{}, err
	}

	return Quiz{
		Date:                 quiz.Date,
		Description:          quiz.Description,
		Draft:                quiz.Draft,
		FilePath:             quiz.filePath(),
		Final:                quiz.Final,
		ID:                   quizID,
		Lastmod:              quiz.Lastmod,
		Layout:               quiz.Layout,
		MaxAttempts:          quiz.maxAttempts(),
		NextPage:             nextPage,
		OrgId:                orgID,
		Parent:               parent,
		PassPercentage:       quiz.passPercentage(),
		Permalink:            quiz.Permalink,
		Prerequisites:        prerequisites,
		Questions:            questions,
		RelPermalink:         quiz.RelPermalink,
		Section:              quiz.Section,
		Slug:                 quiz.Slug,
		TimeLimit:            quiz.timeLimit(),
		Title:                quiz.Title,
		TotalMarks:           quiz.totalMarks(),
		TotalQuestionSets:    quiz.totalQuestionSets(),
		TotalQuestions:       quiz.totalQuestions(),
		TotalQuestionsInBank: quiz.totalQuestionsInBank(),
		Type:                 quiz.Type,
	}, nil
}

func looksLikeHugoQuiz(data []byte) bool {
	var object map[string]json.RawMessage
	if err := json.Unmarshal(data, &object); err != nil {
		return false
	}

	legacyTopLevelKeys := []string{
		"file_path",
		"pass_percentage",
		"time_limit",
		"max_attempts",
		"total_questions",
		"total_questions_in_bank",
		"total_question_sets",
		"total_marks",
		"next_page",
	}

	for _, key := range legacyTopLevelKeys {
		if _, ok := object[key]; ok {
			return true
		}
	}

	rawQuestions, ok := object["questions"]
	if !ok {
		return false
	}

	var questions []map[string]json.RawMessage
	if err := json.Unmarshal(rawQuestions, &questions); err != nil {
		return false
	}

	for _, question := range questions {
		if _, ok := question["correct_answer"]; ok {
			return true
		}
		if _, ok := question["multiple_answers"]; ok {
			return true
		}

		rawOptions, ok := question["options"]
		if !ok {
			continue
		}

		var options []map[string]json.RawMessage
		if err := json.Unmarshal(rawOptions, &options); err != nil {
			continue
		}

		for _, option := range options {
			if _, ok := option["is_correct"]; ok {
				return true
			}
		}
	}

	return false
}

func (quiz HugoQuiz) toCanonicalQuestion(quizID uuid.UUID, index int, question HugoQuestion) (Question, error) {
	questionID := quiz.deriveQuestionUUID(quizID, question.ID, index, question.Text)
	options := make([]QuestionOption, len(question.Options))
	for optionIndex, option := range question.Options {
		options[optionIndex] = QuestionOption{
			ID:        quiz.deriveOptionUUID(questionID, option.ID, optionIndex, option.Text),
			IsCorrect: option.isCorrect(),
			Text:      option.Text,
		}
	}

	questionType := normalizeLegacyQuestionType(question.Type)
	return Question{
		CorrectAnswer:   question.correctAnswer(),
		ID:              questionID,
		Marks:           question.Marks,
		MultipleAnswers: question.multipleAnswers(questionType),
		Options:         options,
		Text:            question.Text,
		Type:            questionType,
	}, nil
}

func (quiz HugoQuiz) toCanonicalParent(parent HugoParent) (Parent, error) {
	if parent.isZero() {
		return Parent{}, nil
	}

	return Parent{
		ID:           quiz.deriveContentUUID(parent.ID, parent.RelPermalink, parent.Title, parent.Type),
		RelPermalink: parent.RelPermalink,
		Title:        parent.Title,
		Type:         parent.Type,
	}, nil
}

func (parent HugoParent) isZero() bool {
	return parent.ID == "" && parent.Title == "" && parent.RelPermalink == "" && parent.Type == ""
}

func (question HugoQuestion) correctAnswer() string {
	if question.CorrectAnswer != "" {
		return question.CorrectAnswer
	}
	return question.CorrectAnswerCamel
}

func (question HugoQuestion) multipleAnswers(questionType QuestionType) *bool {
	if question.MultipleAnswers != nil {
		return question.MultipleAnswers
	}
	if question.MultipleAnswersLegacy != nil {
		return question.MultipleAnswersLegacy
	}

	switch questionType {
	case QuestionTypeMultipleAnswers:
		value := true
		return &value
	case QuestionTypeSingleAnswer:
		value := false
		return &value
	default:
		return nil
	}
}

func (option HugoQuestionOption) isCorrect() bool {
	if option.IsCorrectCamel != nil {
		return *option.IsCorrectCamel
	}
	return option.IsCorrect
}

func normalizeLegacyQuestionType(raw string) QuestionType {
	return QuestionType(strings.ReplaceAll(strings.TrimSpace(raw), "_", "-"))
}

func parseRequiredUUID(field, raw string) (uuid.UUID, error) {
	normalized := normalizeSeedValue(raw)
	value, err := uuid.FromString(normalized)
	if err != nil {
		return uuid.Nil, fmt.Errorf("parse academy quiz %s UUID %q: %w", field, raw, err)
	}

	return value, nil
}

func (quiz HugoQuiz) deriveQuizUUID() uuid.UUID {
	return quiz.deriveContentUUID(quiz.ID, quiz.RelPermalink, quiz.Title, quiz.Type)
}

func (quiz HugoQuiz) deriveContentUUID(rawID, relPermalink, title, contentType string) uuid.UUID {
	rawID = normalizeSeedValue(rawID)
	if parsed, err := uuid.FromString(rawID); err == nil {
		return parsed
	}

	return uuid.NewV5(uuid.NamespaceURL, strings.Join([]string{
		"academy-theme",
		"v1beta2",
		"content",
		normalizeSeedValue(quiz.OrgID),
		strings.TrimSpace(relPermalink),
		strings.TrimSpace(title),
		strings.TrimSpace(contentType),
		rawID,
	}, "\x1f"))
}

func (quiz HugoQuiz) deriveQuestionUUID(quizID uuid.UUID, rawID string, index int, text string) uuid.UUID {
	rawID = normalizeSeedValue(rawID)
	if parsed, err := uuid.FromString(rawID); err == nil {
		return parsed
	}

	return uuid.NewV5(uuid.NamespaceURL, strings.Join([]string{
		"academy-theme",
		"v1beta2",
		"question",
		quizID.String(),
		strings.TrimSpace(quiz.filePath()),
		strconv.Itoa(index),
		rawID,
		strings.TrimSpace(text),
	}, "\x1f"))
}

func (quiz HugoQuiz) deriveOptionUUID(questionID uuid.UUID, rawID string, index int, text string) uuid.UUID {
	rawID = normalizeSeedValue(rawID)
	if parsed, err := uuid.FromString(rawID); err == nil {
		return parsed
	}

	return uuid.NewV5(uuid.NamespaceURL, strings.Join([]string{
		"academy-theme",
		"v1beta2",
		"option",
		questionID.String(),
		strconv.Itoa(index),
		rawID,
		strings.TrimSpace(text),
	}, "\x1f"))
}

func normalizeSeedValue(raw string) string {
	trimmed := strings.TrimSpace(raw)
	if parsed, err := uuid.FromString(trimmed); err == nil {
		return parsed.String()
	}
	return trimmed
}

func (quiz HugoQuiz) filePath() string {
	if strings.TrimSpace(quiz.FilePathLegacy) != "" {
		return strings.TrimSpace(quiz.FilePathLegacy)
	}
	return strings.TrimSpace(quiz.FilePathCamel)
}

func (quiz HugoQuiz) passPercentage() float32 {
	if quiz.PassPercentageCamel != nil {
		return *quiz.PassPercentageCamel
	}
	return quiz.PassPercentageLegacy
}

func (quiz HugoQuiz) timeLimit() int {
	if quiz.TimeLimitCamel != nil {
		return *quiz.TimeLimitCamel
	}
	return int(quiz.TimeLimitLegacy)
}

func (quiz HugoQuiz) maxAttempts() int {
	if quiz.MaxAttemptsCamel != nil {
		return *quiz.MaxAttemptsCamel
	}
	return quiz.MaxAttemptsLegacy
}

func (quiz HugoQuiz) totalQuestions() int {
	if quiz.TotalQuestionsCamel != nil {
		return *quiz.TotalQuestionsCamel
	}
	return quiz.TotalQuestionsLegacy
}

func (quiz HugoQuiz) totalQuestionsInBank() int {
	if quiz.TotalQuestionsInBankCamel != nil {
		return *quiz.TotalQuestionsInBankCamel
	}
	return quiz.TotalQuestionsInBankLegacy
}

func (quiz HugoQuiz) totalQuestionSets() int {
	if quiz.TotalQuestionSetsCamel != nil {
		return *quiz.TotalQuestionSetsCamel
	}
	return quiz.TotalQuestionSetsLegacy
}

func (quiz HugoQuiz) totalMarks() int {
	if quiz.TotalMarksCamel != nil {
		return *quiz.TotalMarksCamel
	}
	return quiz.TotalMarksLegacy
}

func (quiz HugoQuiz) nextPage() HugoParent {
	if quiz.NextPageCamel != nil {
		return *quiz.NextPageCamel
	}
	return quiz.NextPageLegacy
}
