package questiongen

import (
	"reflect"
	"regexp"
	"strconv"
	"testing"

	"boostai-backend/internal/sqlc"
)

func TestServiceGenerateDeterministicWithSeed(t *testing.T) {
	t.Parallel()

	service := NewService()
	params := GenerateParams{
		Topic:      sqlc.QuestionTopicFractions,
		Difficulty: sqlc.QuestionDifficultyMedium,
		Count:      3,
		Seed:       123456,
	}

	first, firstSeed, err := service.Generate(params)
	if err != nil {
		t.Fatalf("first generate returned error: %v", err)
	}

	second, secondSeed, err := service.Generate(params)
	if err != nil {
		t.Fatalf("second generate returned error: %v", err)
	}

	if firstSeed != params.Seed || secondSeed != params.Seed {
		t.Fatalf("expected seed %d to be reused, got %d and %d", params.Seed, firstSeed, secondSeed)
	}

	if !reflect.DeepEqual(first, second) {
		t.Fatalf("expected deterministic output for identical seed\nfirst: %#v\nsecond: %#v", first, second)
	}
}

func TestServiceGenerateSupportsAllTopicsAndDifficulties(t *testing.T) {
	t.Parallel()

	service := NewService()
	topics := []sqlc.QuestionTopic{
		sqlc.QuestionTopicPlaceValue,
		sqlc.QuestionTopicArithmetic,
		sqlc.QuestionTopicNegativeNumbers,
		sqlc.QuestionTopicBidmas,
		sqlc.QuestionTopicFractions,
		sqlc.QuestionTopicAlgebra,
		sqlc.QuestionTopicGeometry,
		sqlc.QuestionTopicData,
	}
	difficulties := []sqlc.QuestionDifficulty{
		sqlc.QuestionDifficultyEasy,
		sqlc.QuestionDifficultyMedium,
		sqlc.QuestionDifficultyHard,
	}

	for _, topic := range topics {
		topic := topic
		for _, difficulty := range difficulties {
			difficulty := difficulty
			t.Run(string(topic)+"_"+string(difficulty), func(t *testing.T) {
				t.Parallel()

				items, usedSeed, err := service.Generate(GenerateParams{
					Topic:      topic,
					Difficulty: difficulty,
					Count:      2,
					Seed:       99,
				})
				if err != nil {
					t.Fatalf("generate returned error: %v", err)
				}
				if usedSeed != 99 {
					t.Fatalf("expected used seed 99, got %d", usedSeed)
				}
				if len(items) != 2 {
					t.Fatalf("expected 2 generated questions, got %d", len(items))
				}

				for i, item := range items {
					if item.Title == "" {
						t.Fatalf("item %d: title should not be empty", i)
					}
					if item.Prompt == "" {
						t.Fatalf("item %d: prompt should not be empty", i)
					}
					if item.CorrectAnswer == "" {
						t.Fatalf("item %d: correct answer should not be empty", i)
					}
					if len(item.WorkedSolution) == 0 {
						t.Fatalf("item %d: worked solution should not be empty", i)
					}
					assertContainsTag(t, item.Tags, string(topic))
					assertContainsTag(t, item.Tags, string(difficulty))
					assertContainsTag(t, item.Tags, "rng_generated")
				}
			})
		}
	}
}

func TestFractionsEasyUsesSingleDigitPromptValues(t *testing.T) {
	t.Parallel()

	service := NewService()
	items, _, err := service.Generate(GenerateParams{
		Topic:      sqlc.QuestionTopicFractions,
		Difficulty: sqlc.QuestionDifficultyEasy,
		Count:      20,
		Seed:       20260522,
	})
	if err != nil {
		t.Fatalf("generate returned error: %v", err)
	}

	for i, item := range items {
		values := extractIntegers(item.Prompt)
		if len(values) != 3 {
			t.Fatalf("item %d: expected 3 integers in prompt, got %v from %q", i, values, item.Prompt)
		}

		for _, value := range values {
			if value < 0 || value > 9 {
				t.Fatalf("item %d: expected easy fraction prompt values to be single-digit, got %d in %q", i, value, item.Prompt)
			}
		}

		assertContainsTag(t, item.Tags, "single_digit")
	}
}

func TestServiceGenerateRejectsUnsupportedTopic(t *testing.T) {
	t.Parallel()

	service := NewService()
	_, _, err := service.Generate(GenerateParams{
		Topic:      sqlc.QuestionTopic("unsupported_topic"),
		Difficulty: sqlc.QuestionDifficultyEasy,
		Count:      1,
		Seed:       1,
	})
	if err == nil {
		t.Fatal("expected unsupported topic to return an error")
	}
}

func assertContainsTag(t *testing.T, tags []string, want string) {
	t.Helper()
	for _, tag := range tags {
		if tag == want {
			return
		}
	}
	t.Fatalf("expected tags %v to contain %q", tags, want)
}

var integerPattern = regexp.MustCompile(`-?\d+`)

func extractIntegers(input string) []int {
	matches := integerPattern.FindAllString(input, -1)
	values := make([]int, 0, len(matches))
	for _, match := range matches {
		value, err := strconv.Atoi(match)
		if err != nil {
			continue
		}
		values = append(values, value)
	}
	return values
}
