"""Tests for backend generators (models, schemas, services, REST, GraphQL)."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.backend.models import ModelsGenerator
from prisme.generators.backend.rest import RESTGenerator
from prisme.generators.backend.schemas import SchemasGenerator
from prisme.generators.backend.services import ServicesGenerator
from prisme.generators.base import GeneratorContext
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
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),
            FieldSpec(name="metadata", type=FieldType.JSON, required=False),
        ],
    )


@pytest.fixture
def relational_models() -> list[ModelSpec]:
    return [
        ModelSpec(
            name="Author",
            fields=[
                FieldSpec(name="name", type=FieldType.STRING, required=True),
                FieldSpec(name="bio", type=FieldType.TEXT, required=False),
            ],
            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"
                ),
                FieldSpec(name="isbn", type=FieldType.STRING, unique=True),
                FieldSpec(name="published_date", type=FieldType.DATE, required=False),
                FieldSpec(name="price", type=FieldType.DECIMAL, required=False),
            ],
        ),
    ]


@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 TestModelsGenerator:
    """Tests for ModelsGenerator."""

    def test_generates_model_file(self, context: GeneratorContext) -> None:
        gen = ModelsGenerator(context)
        files = gen.generate_files()
        customer_model = next(
            (f for f in files if "customer" in f.path.name.lower() and f.path.suffix == ".py"),
            None,
        )
        assert customer_model is not None
        assert "Customer" in customer_model.content

    def test_generates_init_file(self, context: GeneratorContext) -> None:
        gen = ModelsGenerator(context)
        files = gen.generate_files()
        init_file = next(
            (f for f in files if f.path.name == "__init__.py"),
            None,
        )
        assert init_file is not None

    def test_model_has_fields(self, context: GeneratorContext) -> None:
        gen = ModelsGenerator(context)
        files = gen.generate_files()
        customer_model = next(
            (f for f in files if "customer" in f.path.name.lower() and f.path.suffix == ".py"),
            None,
        )
        assert customer_model is not None
        assert "name" in customer_model.content
        assert "email" in customer_model.content

    def test_model_has_enum_field(self, context: GeneratorContext) -> None:
        gen = ModelsGenerator(context)
        files = gen.generate_files()
        customer_model = next(
            (f for f in files if "customer" in f.path.name.lower() and f.path.suffix == ".py"),
            None,
        )
        assert customer_model is not None
        assert "status" in customer_model.content

    def test_relational_model_has_foreign_key(self, relational_context: GeneratorContext) -> None:
        gen = ModelsGenerator(relational_context)
        files = gen.generate_files()
        book_model = next(
            (f for f in files if "book" in f.path.name.lower() and f.path.suffix == ".py"),
            None,
        )
        assert book_model is not None
        assert "author_id" in book_model.content

    def test_model_file_count(self, context: GeneratorContext) -> None:
        gen = ModelsGenerator(context)
        files = gen.generate_files()
        # Should have model file + possibly init
        assert len(files) >= 1

    def test_relational_generates_both_models(self, relational_context: GeneratorContext) -> None:
        gen = ModelsGenerator(relational_context)
        files = gen.generate_files()
        model_names = [f.path.name.lower() for f in files]
        has_author = any("author" in n for n in model_names)
        has_book = any("book" in n for n in model_names)
        assert has_author
        assert has_book


class TestSchemasGenerator:
    """Tests for SchemasGenerator."""

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

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

    def test_schema_has_create_and_read_variants(self, context: GeneratorContext) -> None:
        gen = SchemasGenerator(context)
        files = gen.generate_files()
        customer_schema = next(
            (f for f in files if "customer" in f.path.name.lower()),
            None,
        )
        assert customer_schema is not None
        content = customer_schema.content
        # Should have Create and Read schemas
        assert "Create" in content or "create" in content
        assert "Read" in content or "read" in content

    def test_relational_schema_has_fk(self, relational_context: GeneratorContext) -> None:
        gen = SchemasGenerator(relational_context)
        files = gen.generate_files()
        book_schema = next(
            (f for f in files if "book" in f.path.name.lower()),
            None,
        )
        assert book_schema is not None
        assert "author_id" in book_schema.content


class TestServicesGenerator:
    """Tests for ServicesGenerator."""

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

    def test_service_has_crud_operations(self, context: GeneratorContext) -> None:
        gen = ServicesGenerator(context)
        files = gen.generate_files()
        customer_service = next(
            (f for f in files if "customer" in f.path.name.lower()),
            None,
        )
        assert customer_service is not None
        content = customer_service.content
        # Should have create, read, update, delete operations
        assert "create" in content.lower()
        assert "get" in content.lower() or "read" in content.lower()

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


class TestRESTGenerator:
    """Tests for RESTGenerator."""

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

    def test_router_has_endpoints(self, context: GeneratorContext) -> None:
        gen = RESTGenerator(context)
        files = gen.generate_files()
        customer_router = next(
            (f for f in files if "customer" in f.path.name.lower()),
            None,
        )
        assert customer_router is not None
        content = customer_router.content
        # Should have HTTP method decorators or route definitions
        assert "get" in content.lower() or "post" in content.lower()

    def test_relational_router(self, relational_context: GeneratorContext) -> None:
        gen = RESTGenerator(relational_context)
        files = gen.generate_files()
        router_names = [f.path.name.lower() for f in files]
        has_author = any("author" in n for n in router_names)
        has_book = any("book" in n for n in router_names)
        assert has_author
        assert has_book

    def test_rest_with_auth_context(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 = RESTGenerator(ctx)
        files = gen.generate_files()
        assert len(files) > 0


class TestMultipleFieldTypes:
    """Tests that generators handle various field types."""

    def test_date_and_datetime_fields(self, tmp_path: Path) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Event",
                    fields=[
                        FieldSpec(name="title", type=FieldType.STRING, required=True),
                        FieldSpec(name="start_date", type=FieldType.DATE, required=True),
                        FieldSpec(name="start_time", type=FieldType.DATETIME, required=True),
                    ],
                )
            ],
        )
        ctx = GeneratorContext(
            domain_spec=stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test"),
        )
        gen = ModelsGenerator(ctx)
        files = gen.generate_files()
        event_model = next(
            (f for f in files if "event" in f.path.name.lower() and f.path.suffix == ".py"),
            None,
        )
        assert event_model is not None
        assert "start_date" in event_model.content

    def test_json_field(self, tmp_path: Path) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Config",
                    fields=[
                        FieldSpec(name="settings", type=FieldType.JSON, required=False),
                    ],
                )
            ],
        )
        ctx = GeneratorContext(
            domain_spec=stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test"),
        )
        gen = ModelsGenerator(ctx)
        files = gen.generate_files()
        config_model = next(
            (f for f in files if "config" in f.path.name.lower() and f.path.suffix == ".py"),
            None,
        )
        assert config_model is not None

    def test_decimal_field(self, tmp_path: Path) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Product",
                    fields=[
                        FieldSpec(name="name", type=FieldType.STRING, required=True),
                        FieldSpec(name="price", type=FieldType.DECIMAL, required=True),
                    ],
                )
            ],
        )
        ctx = GeneratorContext(
            domain_spec=stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test"),
        )
        gen = ModelsGenerator(ctx)
        files = gen.generate_files()
        assert len(files) >= 1
