"""Tests for NamingGenerator."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.backend.naming import NamingGenerator, apply_abbreviations, apply_case_style
from prisme.generators.base import GeneratorContext
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec
from prisme.spec.naming import (
    CaseStyle,
    ClassNamingConfig,
    EndpointNamingConfig,
    NamingConfig,
    PluralStyle,
    TableNamingConfig,
)
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),
        ],
    )


@pytest.fixture
def multi_model_stack() -> StackSpec:
    return StackSpec(
        name="test-project",
        version="1.0.0",
        models=[
            ModelSpec(
                name="Customer",
                fields=[
                    FieldSpec(name="name", type=FieldType.STRING, required=True),
                ],
            ),
            ModelSpec(
                name="OrderItem",
                fields=[
                    FieldSpec(name="quantity", type=FieldType.INTEGER, required=True),
                ],
            ),
        ],
    )


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


@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 custom_naming_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",
            naming=NamingConfig(
                tables=TableNamingConfig(prefix="app_", plural=PluralStyle.PLURAL),
                endpoints=EndpointNamingConfig(style=CaseStyle.SNAKE_CASE),
                classes=ClassNamingConfig(model_suffix="Model", service_suffix="Svc"),
                abbreviations={"customer": "cust"},
            ),
        ),
    )


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


class TestApplyCaseStyle:
    """Test the apply_case_style helper."""

    def test_snake_case(self) -> None:
        assert apply_case_style("CustomerOrder", CaseStyle.SNAKE_CASE) == "customer_order"

    def test_camel_case(self) -> None:
        assert apply_case_style("customer_order", CaseStyle.CAMEL_CASE) == "customerOrder"

    def test_pascal_case(self) -> None:
        assert apply_case_style("customer_order", CaseStyle.PASCAL_CASE) == "CustomerOrder"

    def test_kebab_case(self) -> None:
        assert apply_case_style("customer_order", CaseStyle.KEBAB_CASE) == "customer-order"

    def test_screaming_snake(self) -> None:
        assert apply_case_style("customer_order", CaseStyle.SCREAMING_SNAKE) == "CUSTOMER_ORDER"


class TestApplyAbbreviations:
    """Test the apply_abbreviations helper."""

    def test_applies_abbreviation(self) -> None:
        result = apply_abbreviations("organization_config", {"organization": "org"})
        assert result == "org_config"

    def test_no_abbreviations(self) -> None:
        result = apply_abbreviations("customer_order", {})
        assert result == "customer_order"

    def test_multiple_abbreviations(self) -> None:
        result = apply_abbreviations(
            "organization_configuration",
            {"organization": "org", "configuration": "config"},
        )
        assert result == "org_config"


class TestNamingGenerator:
    """Tests for NamingGenerator file generation."""

    def test_generates_files(self, context: GeneratorContext) -> None:
        gen = NamingGenerator(context)
        files = gen.generate_files()
        assert len(files) >= 3  # naming.py + naming.ts + __init__.py

    def test_generates_python_naming(self, context: GeneratorContext) -> None:
        gen = NamingGenerator(context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        assert "NAMING_CONFIG" in py_file.content
        assert "Customer" in py_file.content

    def test_generates_typescript_naming(self, context: GeneratorContext) -> None:
        gen = NamingGenerator(context)
        files = gen.generate_files()
        ts_file = next(
            (f for f in files if f.path.name == "naming.ts"),
            None,
        )
        assert ts_file is not None
        assert "MODEL_NAMES" in ts_file.content
        assert "Customer" in ts_file.content

    def test_generates_init_file(self, context: GeneratorContext) -> None:
        gen = NamingGenerator(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
        assert "NAMING_CONFIG" in init_file.content

    def test_python_file_has_helpers(self, context: GeneratorContext) -> None:
        gen = NamingGenerator(context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        assert "get_table_name" in py_file.content
        assert "get_endpoint_path" in py_file.content
        assert "get_class_name" in py_file.content
        assert "get_variable_name" in py_file.content

    def test_typescript_file_has_helpers(self, context: GeneratorContext) -> None:
        gen = NamingGenerator(context)
        files = gen.generate_files()
        ts_file = next(
            (f for f in files if f.path.name == "naming.ts"),
            None,
        )
        assert ts_file is not None
        assert "getEndpointPath" in ts_file.content
        assert "getClassName" in ts_file.content
        assert "getVariableName" in ts_file.content

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

    def test_multi_model_entries(self, multi_model_context: GeneratorContext) -> None:
        gen = NamingGenerator(multi_model_context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        assert "Customer" in py_file.content
        assert "OrderItem" in py_file.content


class TestNamingGeneratorCustomConfig:
    """Tests for NamingGenerator with custom naming config."""

    def test_table_prefix_applied(self, custom_naming_context: GeneratorContext) -> None:
        gen = NamingGenerator(custom_naming_context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        assert "app_" in py_file.content

    def test_model_suffix_applied(self, custom_naming_context: GeneratorContext) -> None:
        gen = NamingGenerator(custom_naming_context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        assert "CustomerModel" in py_file.content

    def test_service_suffix_applied(self, custom_naming_context: GeneratorContext) -> None:
        gen = NamingGenerator(custom_naming_context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        assert "CustomerSvc" in py_file.content

    def test_abbreviation_applied_in_table(self, custom_naming_context: GeneratorContext) -> None:
        gen = NamingGenerator(custom_naming_context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        # "customer" abbreviated to "cust" with prefix "app_" and plural
        assert "app_custs" in py_file.content

    def test_config_section_in_python(self, custom_naming_context: GeneratorContext) -> None:
        gen = NamingGenerator(custom_naming_context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        assert '"table_prefix": "app_"' in py_file.content
        assert '"endpoint_style": "snake_case"' in py_file.content

    def test_endpoint_style_snake_case(self, custom_naming_context: GeneratorContext) -> None:
        gen = NamingGenerator(custom_naming_context)
        files = gen.generate_files()
        py_file = next(
            (f for f in files if f.path.name == "naming.py" and "naming" in str(f.path)),
            None,
        )
        assert py_file is not None
        # With snake_case endpoint style, should use underscores
        assert "custs" in py_file.content


class TestNamingGeneratorModelMap:
    """Tests for the internal _build_model_naming_map."""

    def test_default_naming_map(self, context: GeneratorContext) -> None:
        gen = NamingGenerator(context)
        entries = gen._build_model_naming_map()
        assert len(entries) == 1
        entry = entries[0]
        assert entry["model_name"] == "Customer"
        assert entry["table_name"] == "customer"  # snake_case, singular
        assert entry["endpoint_segment"] == "customers"  # kebab-case, plural
        assert entry["model_class"] == "Customer"  # no suffix
        assert entry["service_class"] == "CustomerService"
        assert entry["backend_var"] == "customer"
        assert entry["frontend_var"] == "customer"

    def test_custom_naming_map(self, custom_naming_context: GeneratorContext) -> None:
        gen = NamingGenerator(custom_naming_context)
        entries = gen._build_model_naming_map()
        assert len(entries) == 1
        entry = entries[0]
        assert entry["model_class"] == "CustomerModel"
        assert entry["service_class"] == "CustomerSvc"
        # Table: prefix + abbreviated + plural
        assert entry["table_name"].startswith("app_")

    def test_explicit_table_name_override(self, tmp_path: Path) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Customer",
                    table_name="my_customers",
                    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 = NamingGenerator(ctx)
        entries = gen._build_model_naming_map()
        assert entries[0]["table_name"] == "my_customers"
