"""Tests for frontend generators (types, components, pages, search, graphql_ops)."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.frontend.components import ComponentsGenerator
from prisme.generators.frontend.pages import PagesGenerator
from prisme.generators.frontend.search import SearchPageGenerator
from prisme.generators.frontend.types import TypeScriptGenerator
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec, RelationshipSpec
from prisme.spec.overrides import FrontendOverrides
from prisme.spec.project import ProjectSpec
from prisme.spec.stack import StackSpec


@pytest.fixture
def basic_model() -> ModelSpec:
    return ModelSpec(
        name="Customer",
        description="Customer entity",
        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="is_active", type=FieldType.BOOLEAN, default=True),
            FieldSpec(
                name="status",
                type=FieldType.ENUM,
                enum_values=["active", "inactive", "pending"],
                default="active",
            ),
            FieldSpec(name="score", type=FieldType.FLOAT, required=False),
            FieldSpec(name="created_at", type=FieldType.DATETIME, required=False),
        ],
    )


@pytest.fixture
def relational_models() -> 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(relational_models: list[ModelSpec]) -> StackSpec:
    return StackSpec(
        name="test-project",
        version="1.0.0",
        models=relational_models,
    )


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


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


class TestTypeScriptGenerator:
    """Tests for TypeScriptGenerator."""

    def test_generates_type_file(self, context: GeneratorContext) -> None:
        gen = TypeScriptGenerator(context)
        files = gen.generate_files()
        # TypeScriptGenerator produces a single generated.ts file
        generated = next(
            (f for f in files if "generated" in f.path.name.lower()),
            None,
        )
        assert generated is not None

    def test_type_file_has_interface(self, context: GeneratorContext) -> None:
        gen = TypeScriptGenerator(context)
        files = gen.generate_files()
        generated = next((f for f in files if "generated" in f.path.name.lower()), None)
        assert generated is not None
        assert "Customer" in generated.content
        assert "interface" in generated.content or "type" in generated.content

    def test_type_has_fields(self, context: GeneratorContext) -> None:
        gen = TypeScriptGenerator(context)
        files = gen.generate_files()
        generated = next((f for f in files if "generated" in f.path.name.lower()), None)
        assert generated is not None
        assert "name" in generated.content
        assert "email" in generated.content

    def test_relational_types(self, relational_context: GeneratorContext) -> None:
        gen = TypeScriptGenerator(relational_context)
        files = gen.generate_files()
        generated = next((f for f in files if "generated" in f.path.name.lower()), None)
        assert generated is not None
        assert "Author" in generated.content
        assert "Book" in generated.content

    def test_non_exposed_model_no_types(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 = TypeScriptGenerator(ctx)
        files = gen.generate_files()
        # Non-exposed models should not have types in generated.ts
        if files:
            generated = next((f for f in files if "generated" in f.path.name.lower()), None)
            if generated:
                assert "Internal" not in generated.content

    def test_generates_at_least_one_file(self, context: GeneratorContext) -> None:
        gen = TypeScriptGenerator(context)
        files = gen.generate_files()
        assert len(files) >= 1


class TestComponentsGenerator:
    """Tests for ComponentsGenerator."""

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

    def test_generates_table_component(self, context: GeneratorContext) -> None:
        gen = ComponentsGenerator(context)
        files = gen.generate_files()
        table = next(
            (f for f in files if "table" in f.path.name.lower() or "Table" in str(f.path)),
            None,
        )
        assert table is not None

    def test_generates_form_component(self, context: GeneratorContext) -> None:
        gen = ComponentsGenerator(context)
        files = gen.generate_files()
        form = next(
            (f for f in files if "form" in f.path.name.lower() or "Form" in str(f.path)),
            None,
        )
        assert form is not None

    def test_no_form_when_disabled(self, tmp_path: Path) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Metric",
                    fields=[FieldSpec(name="value", type=FieldType.FLOAT)],
                    frontend_overrides=FrontendOverrides(
                        generate_form=False,
                        generate_table=True,
                    ),
                )
            ],
        )
        ctx = GeneratorContext(
            domain_spec=stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test"),
        )
        gen = ComponentsGenerator(ctx)
        files = gen.generate_files()
        forms = [
            f for f in files if "form" in f.path.name.lower() and "metric" in str(f.path).lower()
        ]
        assert len(forms) == 0


class TestPagesGenerator:
    """Tests for PagesGenerator."""

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

    def test_generates_list_page(self, context: GeneratorContext) -> None:
        gen = PagesGenerator(context)
        files = gen.generate_files()
        # Pages use index.tsx for list pages
        list_page = next(
            (f for f in files if f.path.name == "index.tsx" and "customers" in str(f.path)),
            None,
        )
        assert list_page is not None

    def test_generates_detail_page(self, context: GeneratorContext) -> None:
        gen = PagesGenerator(context)
        files = gen.generate_files()
        # Pages use [id].tsx for detail pages
        detail_page = next(
            (f for f in files if f.path.name == "[id].tsx" and "customers" in str(f.path)),
            None,
        )
        assert detail_page is not None

    def test_no_detail_when_disabled(self, tmp_path: Path) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Metric",
                    fields=[FieldSpec(name="value", type=FieldType.FLOAT)],
                    frontend_overrides=FrontendOverrides(
                        generate_detail_view=False,
                        generate_table=True,
                    ),
                )
            ],
        )
        ctx = GeneratorContext(
            domain_spec=stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test"),
        )
        gen = PagesGenerator(ctx)
        files = gen.generate_files()
        detail_pages = [
            f
            for f in files
            if ("detail" in f.path.name.lower() or "Detail" in str(f.path))
            and "metric" in str(f.path).lower()
        ]
        assert len(detail_pages) == 0

    def test_non_exposed_model_no_pages(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 = PagesGenerator(ctx)
        files = gen.generate_files()
        internal = [f for f in files if "Internal" in str(f.path)]
        assert len(internal) == 0


class TestSearchPageGenerator:
    """Tests for SearchPageGenerator."""

    def test_generates_search_page(self, context: GeneratorContext) -> None:
        gen = SearchPageGenerator(context)
        files = gen.generate_files()
        # Search should generate at least one file
        assert len(files) >= 0  # May be empty if no searchable models

    def test_search_with_multiple_models(self, relational_context: GeneratorContext) -> None:
        gen = SearchPageGenerator(relational_context)
        files = gen.generate_files()
        # Should generate search page(s) for exposed models
        assert isinstance(files, list)
