"""Tests for the gRPC generator."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.backend.grpc import PROTO_TYPE_MAP, GRPCGenerator
from prisme.generators.base import GeneratorContext
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec
from prisme.spec.project import (
    ExposureConfig,
    GRPCConfig,
    ProjectSpec,
)
from prisme.spec.stack import StackSpec


@pytest.fixture
def grpc_stack_spec() -> StackSpec:
    """Create a stack spec for gRPC tests."""
    return StackSpec(
        name="test-project",
        version="1.0.0",
        description="Test project",
        models=[
            ModelSpec(
                name="Customer",
                description="Customer entity",
                timestamps=True,
                fields=[
                    FieldSpec(name="name", type=FieldType.STRING, required=True),
                    FieldSpec(name="email", type=FieldType.STRING, required=True),
                    FieldSpec(name="age", type=FieldType.INTEGER),
                    FieldSpec(name="active", type=FieldType.BOOLEAN, default=True),
                ],
            ),
        ],
    )


@pytest.fixture
def grpc_project_spec() -> ProjectSpec:
    """Create a project spec with gRPC enabled."""
    return ProjectSpec(
        name="test-project",
        exposure=ExposureConfig(
            grpc=GRPCConfig(enabled=True, port=9090),
        ),
    )


class TestGRPCGenerator:
    """Tests for GRPCGenerator."""

    def test_generates_proto_file(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Should generate .proto file for each model."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        proto_files = [f for f in files if f.path.suffix == ".proto"]
        assert len(proto_files) == 1
        assert "customer.proto" in str(proto_files[0].path)

    def test_proto_contains_service(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Proto file should contain service definition."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        proto = next(f for f in files if f.path.suffix == ".proto")
        assert "service CustomerService" in proto.content

    def test_proto_contains_crud_rpcs(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Proto should contain CRUD RPCs."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        proto = next(f for f in files if f.path.suffix == ".proto")
        assert "rpc GetCustomer" in proto.content
        assert "rpc ListCustomers" in proto.content
        assert "rpc CreateCustomer" in proto.content
        assert "rpc UpdateCustomer" in proto.content
        assert "rpc DeleteCustomer" in proto.content

    def test_proto_contains_streaming_rpcs(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Proto should contain streaming RPCs."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        proto = next(f for f in files if f.path.suffix == ".proto")
        assert "rpc WatchCustomer" in proto.content
        assert "stream CustomerEvent" in proto.content

    def test_proto_field_types(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Proto fields should use correct protobuf types."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        proto = next(f for f in files if f.path.suffix == ".proto")
        assert "string name" in proto.content
        assert "string email" in proto.content
        assert "int64 age" in proto.content
        assert "bool active" in proto.content

    def test_proto_has_timestamps(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Proto should include timestamp fields when model has timestamps."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        proto = next(f for f in files if f.path.suffix == ".proto")
        assert "google.protobuf.Timestamp created_at" in proto.content
        assert "google.protobuf.Timestamp updated_at" in proto.content

    def test_generates_service_implementation(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Should generate Python service implementation."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        service_files = [f for f in files if "_service.py" in str(f.path)]
        assert len(service_files) == 1
        assert "class CustomerServicer" in service_files[0].content

    def test_generates_server(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Should generate gRPC server setup."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        server_files = [f for f in files if f.path.name == "server.py"]
        assert len(server_files) == 1
        assert "GRPC_PORT = 9090" in server_files[0].content

    def test_service_has_watch_method(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Service should have Watch method that integrates with event bus."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        service = next(f for f in files if "_service.py" in str(f.path))
        assert "async def WatchCustomer" in service.content
        assert "event_bus" in service.content

    def test_generates_init_files(
        self,
        grpc_stack_spec: StackSpec,
        grpc_project_spec: ProjectSpec,
        tmp_path: Path,
    ) -> None:
        """Should generate __init__.py files."""
        context = GeneratorContext(
            domain_spec=grpc_stack_spec,
            output_dir=tmp_path,
            project_spec=grpc_project_spec,
        )
        gen = GRPCGenerator(context)
        files = gen.generate_files()

        init_files = [f for f in files if f.path.name == "__init__.py"]
        assert len(init_files) >= 2


class TestProtoTypeMap:
    """Tests for proto type mapping."""

    def test_all_field_types_mapped(self) -> None:
        """All FieldType values should have proto type mappings."""
        for ft in FieldType:
            assert ft in PROTO_TYPE_MAP, f"{ft} not in PROTO_TYPE_MAP"

    def test_string_types(self) -> None:
        assert PROTO_TYPE_MAP[FieldType.STRING] == "string"
        assert PROTO_TYPE_MAP[FieldType.TEXT] == "string"

    def test_numeric_types(self) -> None:
        assert PROTO_TYPE_MAP[FieldType.INTEGER] == "int64"
        assert PROTO_TYPE_MAP[FieldType.FLOAT] == "double"

    def test_boolean_type(self) -> None:
        assert PROTO_TYPE_MAP[FieldType.BOOLEAN] == "bool"

    def test_datetime_types(self) -> None:
        assert PROTO_TYPE_MAP[FieldType.DATETIME] == "google.protobuf.Timestamp"
