"""Tests for the Hetzner runner generator."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.runner import RunnerGenerator
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec
from prisme.spec.project import CIConfig, CIHetznerRunnerConfig, 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 runner_project_spec() -> ProjectSpec:
    return ProjectSpec(
        name="test-project",
        ci=CIConfig(
            provider="github",
            include_frontend=True,
            use_redis=False,
            enable_codecov=True,
            hetzner_runner=CIHetznerRunnerConfig(
                server_type="cx22",
                location="fsn1",
                image="ubuntu-24.04",
            ),
        ),
    )


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


class TestRunnerGeneratorNoConfig:
    """Tests when runner 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 = RunnerGenerator(ctx)
        assert gen.generate_files() == []

    def test_no_ci_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", ci=None),
        )
        gen = RunnerGenerator(ctx)
        assert gen.generate_files() == []

    def test_no_hetzner_runner_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",
                ci=CIConfig(provider="github"),
            ),
        )
        gen = RunnerGenerator(ctx)
        assert gen.generate_files() == []

    def test_non_github_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",
                ci=CIConfig(
                    provider="gitlab",
                    hetzner_runner=CIHetznerRunnerConfig(),
                ),
            ),
        )
        gen = RunnerGenerator(ctx)
        assert gen.generate_files() == []


class TestRunnerGeneratorFullConfig:
    """Tests for full runner generation."""

    def test_generates_ci_hetzner_workflow(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next((f for f in files if f.path == Path(".github/workflows/ci.yml")), None)
        assert ci_file is not None
        assert ci_file.strategy == FileStrategy.GENERATE_ONCE
        assert "CI" in ci_file.content

    def test_generates_cleanup_workflow(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        cleanup = next(
            (f for f in files if f.path == Path(".github/workflows/runner-cleanup.yml")), None
        )
        assert cleanup is not None
        assert cleanup.strategy == FileStrategy.GENERATE_ONCE
        assert "Cleanup" in cleanup.content

    def test_total_file_count(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        # ci.yml + runner-cleanup.yml
        assert len(files) == 2

    def test_ci_workflow_has_setup_runner_job(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "setup-runner" in ci_file.content

    def test_ci_workflow_has_teardown_runner_job(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "teardown-runner" in ci_file.content

    def test_ci_workflow_uses_self_hosted_runner(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "self-hosted" in ci_file.content
        assert "hetzner" in ci_file.content

    def test_ci_workflow_has_hcloud_token_secret(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "HCLOUD_TOKEN" in ci_file.content

    def test_ci_workflow_has_backend_jobs(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "backend-lint" in ci_file.content
        assert "backend-typecheck" in ci_file.content
        assert "backend-test" in ci_file.content

    def test_ci_workflow_includes_frontend_when_enabled(
        self, runner_context: GeneratorContext
    ) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "frontend-lint" in ci_file.content
        assert "frontend-test" in ci_file.content

    def test_ci_workflow_includes_server_type(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "cx22" in ci_file.content

    def test_ci_workflow_includes_location(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "fsn1" in ci_file.content

    def test_all_files_have_generate_once_strategy(self, runner_context: GeneratorContext) -> None:
        gen = RunnerGenerator(runner_context)
        files = gen.generate_files()
        for f in files:
            assert f.strategy == FileStrategy.GENERATE_ONCE


class TestRunnerGeneratorNoFrontend:
    """Tests for runner generation without frontend."""

    def test_no_frontend_jobs(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",
                ci=CIConfig(
                    provider="github",
                    include_frontend=False,
                    hetzner_runner=CIHetznerRunnerConfig(),
                ),
            ),
        )
        gen = RunnerGenerator(ctx)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "frontend-lint" not in ci_file.content
        assert "frontend-test" not in ci_file.content


class TestRunnerGeneratorCustomConfig:
    """Tests for runner generation with custom Hetzner settings."""

    def test_arm_server_type(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",
                ci=CIConfig(
                    provider="github",
                    include_frontend=False,
                    hetzner_runner=CIHetznerRunnerConfig(
                        server_type="cax11",
                        location="hel1",
                    ),
                ),
            ),
        )
        gen = RunnerGenerator(ctx)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "cax11" in ci_file.content
        assert "hel1" in ci_file.content

    def test_custom_image(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",
                ci=CIConfig(
                    provider="github",
                    include_frontend=False,
                    hetzner_runner=CIHetznerRunnerConfig(
                        image="ubuntu-22.04",
                    ),
                ),
            ),
        )
        gen = RunnerGenerator(ctx)
        files = gen.generate_files()
        ci_file = next(f for f in files if f.path == Path(".github/workflows/ci.yml"))
        assert "ubuntu-22.04" in ci_file.content
