"""Tests for the Docker generator."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.docker import DockerGenerator
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec
from prisme.spec.project import DockerConfig, DockerProductionConfig, ProjectSpec
from prisme.spec.stack import FileStrategy, StackSpec


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


@pytest.fixture
def docker_project_spec() -> ProjectSpec:
    return ProjectSpec(
        name="test-project",
        docker=DockerConfig(
            include_redis=True,
            include_mcp=True,
            production=DockerProductionConfig(domain="example.com", replicas=3),
        ),
    )


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


class TestDockerGeneratorNoConfig:
    """Tests when Docker config is absent."""

    def test_no_project_spec_returns_empty(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(domain_spec=basic_stack, output_dir=tmp_path, dry_run=True)
        gen = DockerGenerator(ctx)
        assert gen.generate_files() == []

    def test_no_docker_config_returns_empty(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", docker=None),
        )
        gen = DockerGenerator(ctx)
        assert gen.generate_files() == []


class TestDockerGeneratorBasicConfig:
    """Tests for basic Docker generation (no production)."""

    def test_generates_compose_dev(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", docker=DockerConfig()),
        )
        gen = DockerGenerator(ctx)
        files = gen.generate_files()
        compose = next((f for f in files if f.path == Path("docker-compose.dev.yml")), None)
        assert compose is not None
        assert compose.strategy == FileStrategy.GENERATE_ONCE

    def test_generates_backend_dockerfile(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", docker=DockerConfig()),
        )
        gen = DockerGenerator(ctx)
        files = gen.generate_files()
        backend_df = next((f for f in files if f.path == Path("Dockerfile.backend")), None)
        assert backend_df is not None
        assert backend_df.strategy == FileStrategy.ALWAYS_OVERWRITE

    def test_generates_frontend_dockerfile(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", docker=DockerConfig()),
        )
        gen = DockerGenerator(ctx)
        files = gen.generate_files()
        frontend_df = next((f for f in files if f.path == Path("Dockerfile.frontend")), None)
        assert frontend_df is not None
        assert frontend_df.strategy == FileStrategy.ALWAYS_OVERWRITE

    def test_generates_dockerignore(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", docker=DockerConfig()),
        )
        gen = DockerGenerator(ctx)
        files = gen.generate_files()
        ignore = next((f for f in files if f.path == Path(".dockerignore")), None)
        assert ignore is not None

    def test_basic_config_generates_4_files(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", docker=DockerConfig()),
        )
        gen = DockerGenerator(ctx)
        files = gen.generate_files()
        assert len(files) == 4


class TestDockerGeneratorProductionConfig:
    """Tests for Docker generation with production config."""

    def test_production_with_domain_generates_extra_files(
        self, docker_context: GeneratorContext
    ) -> None:
        gen = DockerGenerator(docker_context)
        files = gen.generate_files()
        # Base 4 + prod backend dockerfile + prod frontend dockerfile + prod compose + env example
        assert len(files) == 8

    def test_generates_production_compose(self, docker_context: GeneratorContext) -> None:
        gen = DockerGenerator(docker_context)
        files = gen.generate_files()
        prod_compose = next((f for f in files if f.path == Path("docker-compose.prod.yml")), None)
        assert prod_compose is not None

    def test_generates_production_backend_dockerfile(
        self, docker_context: GeneratorContext
    ) -> None:
        gen = DockerGenerator(docker_context)
        files = gen.generate_files()
        prod_backend = next((f for f in files if f.path == Path("Dockerfile.backend.prod")), None)
        assert prod_backend is not None

    def test_generates_production_frontend_dockerfile(
        self, docker_context: GeneratorContext
    ) -> None:
        gen = DockerGenerator(docker_context)
        files = gen.generate_files()
        prod_frontend = next((f for f in files if f.path == Path("Dockerfile.frontend.prod")), None)
        assert prod_frontend is not None

    def test_generates_env_example(self, docker_context: GeneratorContext) -> None:
        gen = DockerGenerator(docker_context)
        files = gen.generate_files()
        env_example = next((f for f in files if f.path == Path(".env.prod.example")), None)
        assert env_example is not None
        assert "example.com" in env_example.content
        assert "REDIS_URL" in env_example.content

    def test_non_default_replicas_triggers_production(
        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",
                docker=DockerConfig(
                    production=DockerProductionConfig(replicas=5),
                ),
            ),
        )
        gen = DockerGenerator(ctx)
        files = gen.generate_files()
        assert len(files) > 4  # More than just the base 4 files

    def test_default_replicas_no_domain_skips_production(
        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",
                docker=DockerConfig(
                    production=DockerProductionConfig(),  # defaults: domain=None, replicas=2
                ),
            ),
        )
        gen = DockerGenerator(ctx)
        files = gen.generate_files()
        assert len(files) == 4
