"""Tests for the backend and frontend testing generators.

Tests that the testing generators correctly produce test files,
factories, and fixtures for various model configurations.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.testing.backend import BackendTestGenerator
from prisme.generators.testing.frontend import FrontendTestGenerator
from prisme.spec.auth import AuthConfig
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec, RelationshipSpec
from prisme.spec.project import ProjectSpec, TestingConfig
from prisme.spec.stack import FileStrategy, StackSpec


@pytest.fixture
def basic_model() -> ModelSpec:
    return ModelSpec(
        name="Customer",
        description="Customer entity",
        timestamps=True,
        fields=[
            FieldSpec(name="name", type=FieldType.STRING, required=True, max_length=255),
            FieldSpec(name="email", type=FieldType.STRING, required=True, unique=True),
            FieldSpec(name="age", type=FieldType.INTEGER, required=False),
            FieldSpec(name="notes", type=FieldType.TEXT, required=False),
            FieldSpec(
                name="status",
                type=FieldType.ENUM,
                enum_values=["active", "inactive"],
                default="active",
            ),
            FieldSpec(name="is_verified", type=FieldType.BOOLEAN, default=False),
            FieldSpec(name="score", type=FieldType.FLOAT, required=False),
        ],
    )


@pytest.fixture
def model_with_relations() -> list[ModelSpec]:
    return [
        ModelSpec(
            name="Author",
            fields=[
                FieldSpec(name="name", type=FieldType.STRING, required=True),
            ],
            relationships=[
                RelationshipSpec(name="books", target_model="Book", type="one_to_many"),
            ],
        ),
        ModelSpec(
            name="Book",
            fields=[
                FieldSpec(name="title", type=FieldType.STRING, required=True),
                FieldSpec(
                    name="author_id", type=FieldType.INTEGER, required=True, references="Author"
                ),
            ],
        ),
    ]


@pytest.fixture
def basic_stack(basic_model: ModelSpec) -> StackSpec:
    return StackSpec(
        name="test-project",
        version="1.0.0",
        models=[basic_model],
    )


@pytest.fixture
def relational_stack(model_with_relations: list[ModelSpec]) -> StackSpec:
    return StackSpec(
        name="test-project",
        version="1.0.0",
        models=model_with_relations,
    )


@pytest.fixture
def project_spec() -> ProjectSpec:
    return ProjectSpec(name="test-project")


@pytest.fixture
def context(basic_stack: StackSpec, project_spec: ProjectSpec, tmp_path: Path) -> GeneratorContext:
    return GeneratorContext(
        domain_spec=basic_stack,
        output_dir=tmp_path,
        dry_run=True,
        project_spec=project_spec,
    )


@pytest.fixture
def relational_context(
    relational_stack: StackSpec, project_spec: ProjectSpec, tmp_path: Path
) -> GeneratorContext:
    return GeneratorContext(
        domain_spec=relational_stack,
        output_dir=tmp_path,
        dry_run=True,
        project_spec=project_spec,
    )


class TestBackendTestGenerator:
    """Tests for BackendTestGenerator."""

    def test_generates_conftest(self, context: GeneratorContext) -> None:
        gen = BackendTestGenerator(context)
        files = gen.generate_files()
        conftest = next((f for f in files if f.path.name == "conftest.py"), None)
        assert conftest is not None
        assert "pytest" in conftest.content

    def test_generates_migration_check(self, context: GeneratorContext) -> None:
        gen = BackendTestGenerator(context)
        files = gen.generate_files()
        migration_check = next((f for f in files if "test_migration_check" in str(f.path)), None)
        assert migration_check is not None

    def test_generates_model_test(self, context: GeneratorContext) -> None:
        gen = BackendTestGenerator(context)
        files = gen.generate_files()
        model_test = next((f for f in files if "test_customer" in f.path.name.lower()), None)
        assert model_test is not None
        assert "Customer" in model_test.content

    def test_generates_factory(self, context: GeneratorContext) -> None:
        gen = BackendTestGenerator(context)
        files = gen.generate_files()
        factory = next(
            (f for f in files if "factories" in str(f.path) and f.path.name == "customer.py"), None
        )
        assert factory is not None

    def test_generates_factories_init(self, context: GeneratorContext) -> None:
        gen = BackendTestGenerator(context)
        files = gen.generate_files()
        factories_init = next(
            (f for f in files if "factories" in str(f.path) and f.path.name == "__init__.py"),
            None,
        )
        assert factories_init is not None

    def test_all_files_use_always_overwrite(self, context: GeneratorContext) -> None:
        gen = BackendTestGenerator(context)
        files = gen.generate_files()
        for f in files:
            assert f.strategy == FileStrategy.ALWAYS_OVERWRITE

    def test_relational_models_generate_tests(self, relational_context: GeneratorContext) -> None:
        gen = BackendTestGenerator(relational_context)
        files = gen.generate_files()
        author_test = next((f for f in files if "test_author" in f.path.name.lower()), None)
        book_test = next((f for f in files if "test_book" in f.path.name.lower()), None)
        assert author_test is not None
        assert book_test is not None

    def test_relational_factory_has_fk_setup(self, relational_context: GeneratorContext) -> None:
        gen = BackendTestGenerator(relational_context)
        files = gen.generate_files()
        book_factory = next(
            (f for f in files if "factories" in str(f.path) and f.path.name == "book.py"), None
        )
        assert book_factory is not None
        assert "Author" in book_factory.content or "author" in book_factory.content

    def test_auth_enabled_generates_auth_tests(
        self, basic_stack: StackSpec, tmp_path: Path
    ) -> None:
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                auth=AuthConfig(enabled=True),
            ),
        )
        gen = BackendTestGenerator(ctx)
        files = gen.generate_files()
        auth_tests = [f for f in files if "auth" in str(f.path).lower()]
        assert len(auth_tests) > 0

    def test_no_factories_when_disabled(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                testing=TestingConfig(generate_factories=False),
            ),
        )
        gen = BackendTestGenerator(ctx)
        files = gen.generate_files()
        # Should not have factories init file
        factories_init = next(
            (f for f in files if "factories" in str(f.path) and f.path.name == "__init__.py"),
            None,
        )
        assert factories_init is None


class TestFrontendTestGenerator:
    """Tests for FrontendTestGenerator."""

    def test_generates_test_file(self, context: GeneratorContext) -> None:
        gen = FrontendTestGenerator(context)
        files = gen.generate_files()
        assert len(files) > 0

    def test_generates_model_test(self, context: GeneratorContext) -> None:
        gen = FrontendTestGenerator(context)
        files = gen.generate_files()
        customer_test = next(
            (f for f in files if "Customer" in str(f.path) or "customer" in str(f.path)),
            None,
        )
        assert customer_test is not None

    def test_all_files_use_always_overwrite(self, context: GeneratorContext) -> None:
        gen = FrontendTestGenerator(context)
        files = gen.generate_files()
        for f in files:
            assert f.strategy == FileStrategy.ALWAYS_OVERWRITE

    def test_generates_setup_files(self, context: GeneratorContext) -> None:
        gen = FrontendTestGenerator(context)
        files = gen.generate_files()
        setup = next((f for f in files if "setup" in f.path.name.lower()), None)
        assert setup is not None

    def test_non_exposed_model_no_test(self, tmp_path: Path) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Internal",
                    expose=False,
                    fields=[FieldSpec(name="name", type=FieldType.STRING)],
                )
            ],
        )
        ctx = GeneratorContext(
            domain_spec=stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test"),
        )
        gen = FrontendTestGenerator(ctx)
        files = gen.generate_files()
        internal_tests = [f for f in files if "Internal" in str(f.path)]
        assert len(internal_tests) == 0
