"""Tests for the Deploy generator."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.deploy import DeployGenerator
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec
from prisme.spec.project import DeployConfig, DeployHetznerConfig, 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 deploy_project_spec() -> ProjectSpec:
    return ProjectSpec(
        name="test-project",
        deploy=DeployConfig(
            provider="hetzner",
            domain="example.com",
            ssl_email="admin@example.com",
            use_redis=True,
            hetzner=DeployHetznerConfig(
                location="fsn1",
                staging_server_type="cx22",
                production_server_type="cx32",
            ),
        ),
    )


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


class TestDeployGeneratorNoConfig:
    """Tests when deploy 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 = DeployGenerator(ctx)
        assert gen.generate_files() == []

    def test_no_deploy_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", deploy=None),
        )
        gen = DeployGenerator(ctx)
        assert gen.generate_files() == []

    def test_non_hetzner_provider_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",
                deploy=DeployConfig(provider="aws", domain="example.com"),
            ),
        )
        gen = DeployGenerator(ctx)
        assert gen.generate_files() == []


class TestDeployGeneratorHetzner:
    """Tests for Hetzner deployment generation."""

    def test_generates_terraform_files(self, deploy_context: GeneratorContext) -> None:
        gen = DeployGenerator(deploy_context)
        files = gen.generate_files()
        tf_files = [f for f in files if "terraform" in str(f.path)]
        # main.tf, variables.tf, outputs.tf, versions.tf, staging.tfvars, production.tfvars
        # + modules (server: 3 files, volume: 3 files) + cloud-init
        assert len(tf_files) >= 6

    def test_generates_deploy_scripts(self, deploy_context: GeneratorContext) -> None:
        gen = DeployGenerator(deploy_context)
        files = gen.generate_files()
        deploy_script = next((f for f in files if f.path == Path("deploy/scripts/deploy.sh")), None)
        rollback_script = next(
            (f for f in files if f.path == Path("deploy/scripts/rollback.sh")), None
        )
        assert deploy_script is not None
        assert rollback_script is not None

    def test_generates_env_templates(self, deploy_context: GeneratorContext) -> None:
        gen = DeployGenerator(deploy_context)
        files = gen.generate_files()
        staging_env = next(
            (f for f in files if f.path == Path("deploy/env/.env.staging.template")), None
        )
        prod_env = next(
            (f for f in files if f.path == Path("deploy/env/.env.production.template")), None
        )
        assert staging_env is not None
        assert prod_env is not None

    def test_generates_github_workflows(self, deploy_context: GeneratorContext) -> None:
        gen = DeployGenerator(deploy_context)
        files = gen.generate_files()
        deploy_wf = next((f for f in files if f.path == Path(".github/workflows/deploy.yml")), None)
        terraform_wf = next(
            (f for f in files if f.path == Path(".github/workflows/terraform.yml")), None
        )
        assert deploy_wf is not None
        assert terraform_wf is not None

    def test_generates_traefik_configs(self, deploy_context: GeneratorContext) -> None:
        gen = DeployGenerator(deploy_context)
        files = gen.generate_files()
        traefik = next((f for f in files if f.path == Path("deploy/traefik/traefik.yml")), None)
        infra = next(
            (f for f in files if f.path == Path("deploy/traefik/dynamic/infrastructure.yml")),
            None,
        )
        assert traefik is not None
        assert infra is not None

    def test_generates_readme(self, deploy_context: GeneratorContext) -> None:
        gen = DeployGenerator(deploy_context)
        files = gen.generate_files()
        readme = next((f for f in files if f.path == Path("deploy/README.md")), None)
        assert readme is not None

    def test_all_files_use_generate_once(self, deploy_context: GeneratorContext) -> None:
        gen = DeployGenerator(deploy_context)
        files = gen.generate_files()
        for f in files:
            assert f.strategy == FileStrategy.GENERATE_ONCE

    def test_generates_terraform_modules(self, deploy_context: GeneratorContext) -> None:
        gen = DeployGenerator(deploy_context)
        files = gen.generate_files()
        server_main = next(
            (f for f in files if f.path == Path("deploy/terraform/modules/server/main.tf")),
            None,
        )
        volume_main = next(
            (f for f in files if f.path == Path("deploy/terraform/modules/volume/main.tf")),
            None,
        )
        assert server_main is not None
        assert volume_main is not None

    def test_location_mapping(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        """Different Hetzner locations are mapped correctly."""
        for location in ["nbg1", "fsn1", "hel1", "ash", "hil"]:
            ctx = GeneratorContext(
                domain_spec=basic_stack,
                output_dir=tmp_path,
                dry_run=True,
                project_spec=ProjectSpec(
                    name="test-project",
                    deploy=DeployConfig(
                        provider="hetzner",
                        domain="example.com",
                        hetzner=DeployHetznerConfig(location=location),
                    ),
                ),
            )
            gen = DeployGenerator(ctx)
            files = gen.generate_files()
            assert len(files) > 0

    def test_server_type_mapping(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        """Different server types are mapped correctly."""
        for server_type in ["cx22", "cx23", "cx32", "cx42", "cx52", "cax11"]:
            ctx = GeneratorContext(
                domain_spec=basic_stack,
                output_dir=tmp_path,
                dry_run=True,
                project_spec=ProjectSpec(
                    name="test-project",
                    deploy=DeployConfig(
                        provider="hetzner",
                        domain="example.com",
                        hetzner=DeployHetznerConfig(staging_server_type=server_type),
                    ),
                ),
            )
            gen = DeployGenerator(ctx)
            files = gen.generate_files()
            assert len(files) > 0

    def test_generate_method_makes_scripts_executable(
        self, basic_stack: StackSpec, tmp_path: Path
    ) -> None:
        """The generate() method makes deploy scripts executable."""
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=False,
            force=True,
            project_spec=ProjectSpec(
                name="test-project",
                deploy=DeployConfig(
                    provider="hetzner",
                    domain="example.com",
                ),
            ),
        )
        gen = DeployGenerator(ctx)
        gen.generate()

        deploy_script = tmp_path / "deploy/scripts/deploy.sh"
        rollback_script = tmp_path / "deploy/scripts/rollback.sh"

        if deploy_script.exists():
            assert deploy_script.stat().st_mode & 0o755
        if rollback_script.exists():
            assert rollback_script.stat().st_mode & 0o755
