from typing import Any

from sqlalchemy.ext.asyncio import AsyncSession

from src.models import Category, Difficulty, Pattern, Question
from src.services import CategoryService, PatternService, QuestionService, StatsService

TestData = dict[str, Any]


async def create_test_data(db_session: AsyncSession) -> TestData:
    cat_arrays = Category(name="Arrays", slug="arrays")
    cat_strings = Category(name="Strings", slug="strings")
    db_session.add_all([cat_arrays, cat_strings])

    pat_two_pointers = Pattern(name="Two Pointers", slug="two-pointers")
    pat_sliding = Pattern(name="Sliding Window", slug="sliding-window")
    db_session.add_all([pat_two_pointers, pat_sliding])

    await db_session.flush()

    q1 = Question(
        title="Two Sum",
        slug="two-sum",
        difficulty=Difficulty.EASY,
        description="Find two numbers.",
    )
    q1.categories = [cat_arrays]
    q1.patterns = [pat_two_pointers]

    q2 = Question(
        title="Three Sum",
        slug="three-sum",
        difficulty=Difficulty.MEDIUM,
        description="Find three numbers.",
    )
    q2.categories = [cat_arrays]
    q2.patterns = [pat_two_pointers]

    q3 = Question(
        title="Longest Substring",
        slug="longest-substring",
        difficulty=Difficulty.MEDIUM,
        description="Find longest substring.",
    )
    q3.categories = [cat_strings]
    q3.patterns = [pat_sliding]

    db_session.add_all([q1, q2, q3])
    await db_session.commit()

    return {
        "cat_arrays": cat_arrays,
        "cat_strings": cat_strings,
        "pat_two_pointers": pat_two_pointers,
        "pat_sliding": pat_sliding,
        "q1": q1,
        "q2": q2,
        "q3": q3,
    }


class TestQuestionService:
    async def test_get_questions(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions()

        assert total == 3
        assert len(questions) == 3

    async def test_pagination(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions(page=1, limit=2)

        assert total == 3
        assert len(questions) == 2

    async def test_filter_difficulty(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions(difficulties=[Difficulty.EASY])

        assert total == 1
        assert questions[0].title == "Two Sum"

    async def test_filter_category(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions(category_slug="arrays")

        assert total == 2

    async def test_filter_pattern(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions(pattern_slug="sliding-window")

        assert total == 1
        assert questions[0].title == "Longest Substring"

    async def test_search(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions(search="Sum")

        assert total == 2

    async def test_get_by_slug(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        question = await service.get_question_by_slug("two-sum")

        assert question is not None
        assert question.title == "Two Sum"

    async def test_get_by_slug_not_found(self, db_session: AsyncSession) -> None:
        service = QuestionService(db_session)
        question = await service.get_question_by_slug("nonexistent")

        assert question is None

    async def test_get_by_id(self, db_session: AsyncSession) -> None:
        data = await create_test_data(db_session)
        service = QuestionService(db_session)
        question = await service.get_question_by_id(data["q1"].id)

        assert question is not None
        assert question.title == "Two Sum"

    async def test_get_by_id_not_found(self, db_session: AsyncSession) -> None:
        from uuid import uuid4

        service = QuestionService(db_session)
        question = await service.get_question_by_id(uuid4())

        assert question is None

    async def test_combined_filters(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions(
            difficulties=[Difficulty.MEDIUM],
            category_slug="arrays",
        )

        assert total == 1
        assert questions[0].title == "Three Sum"

    async def test_pagination_page_two(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions(page=2, limit=2)

        assert total == 3
        assert len(questions) == 1

    async def test_pagination_beyond_data(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = QuestionService(db_session)
        questions, total = await service.get_questions(page=10, limit=20)

        assert total == 3
        assert len(questions) == 0


class TestCategoryService:
    async def test_get_categories(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = CategoryService(db_session)
        categories = await service.get_categories()

        assert len(categories) == 2

        arrays = next((c, cnt) for c, cnt in categories if c.slug == "arrays")
        assert arrays[1] == 2

    async def test_get_by_slug(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = CategoryService(db_session)
        category = await service.get_category_by_slug("arrays")

        assert category is not None
        assert category.name == "Arrays"

    async def test_get_by_slug_not_found(self, db_session: AsyncSession) -> None:
        service = CategoryService(db_session)
        category = await service.get_category_by_slug("nonexistent")

        assert category is None


class TestPatternService:
    async def test_get_patterns(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = PatternService(db_session)
        patterns = await service.get_patterns()

        assert len(patterns) == 2

    async def test_get_by_slug(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = PatternService(db_session)
        result = await service.get_pattern_by_slug("two-pointers")

        assert result is not None
        pattern, count = result
        assert pattern.name == "Two Pointers"
        assert count == 2

    async def test_get_by_slug_not_found(self, db_session: AsyncSession) -> None:
        service = PatternService(db_session)
        result = await service.get_pattern_by_slug("nonexistent")

        assert result is None


class TestStatsService:
    async def test_get_stats(self, db_session: AsyncSession) -> None:
        await create_test_data(db_session)
        service = StatsService(db_session)
        stats = await service.get_stats()

        assert stats.total_questions == 3
        assert stats.by_difficulty.easy == 1
        assert stats.by_difficulty.medium == 2
        assert stats.by_difficulty.hard == 0
        assert len(stats.by_category) == 2
        assert len(stats.by_pattern) == 2

    async def test_get_stats_empty(self, db_session: AsyncSession) -> None:
        service = StatsService(db_session)
        stats = await service.get_stats()

        assert stats.total_questions == 0
        assert stats.by_difficulty.easy == 0
