"""Tests for the Hetzner worker generator."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.worker import WorkerGenerator
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.infrastructure import HetznerWorkerJobSpec, HetznerWorkersConfig
from prisme.spec.model import ModelSpec
from prisme.spec.project import 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 worker_project_spec() -> ProjectSpec:
    return ProjectSpec(
        name="test-project",
        workers=HetznerWorkersConfig(
            enabled=True,
            jobs=[
                HetznerWorkerJobSpec(
                    name="data-processor",
                    command="python -m app.workers.process",
                    restart_policy="always",
                    server_type="cx32",
                    cpu=4,
                    memory_mb=8192,
                ),
                HetznerWorkerJobSpec(
                    name="price-fetcher",
                    command="python -m app.jobs.fetch_prices",
                    schedule="*/15 * * * *",
                    restart_policy="never",
                ),
            ],
        ),
    )


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


class TestWorkerGeneratorNoConfig:
    """Tests when worker config is absent or disabled."""

    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 = WorkerGenerator(ctx)
        assert gen.generate_files() == []

    def test_no_workers_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"),
        )
        gen = WorkerGenerator(ctx)
        assert gen.generate_files() == []

    def test_workers_disabled_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",
                workers=HetznerWorkersConfig(enabled=False),
            ),
        )
        gen = WorkerGenerator(ctx)
        assert gen.generate_files() == []

    def test_workers_enabled_no_jobs_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",
                workers=HetznerWorkersConfig(enabled=True, jobs=[]),
            ),
        )
        gen = WorkerGenerator(ctx)
        assert gen.generate_files() == []


class TestWorkerGeneratorFullConfig:
    """Tests for full worker generation."""

    def test_generates_docker_compose(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        compose = next((f for f in files if f.path == Path("docker-compose.workers.yml")), None)
        assert compose is not None
        assert compose.strategy == FileStrategy.GENERATE_ONCE
        assert "data-processor" in compose.content
        assert "price-fetcher" in compose.content

    def test_generates_deploy_workflow(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        workflow = next(
            (f for f in files if f.path == Path(".github/workflows/deploy-workers.yml")),
            None,
        )
        assert workflow is not None
        assert workflow.strategy == FileStrategy.GENERATE_ONCE
        assert "Deploy Workers" in workflow.content

    def test_total_file_count(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        # docker-compose.workers.yml + deploy-workers.yml
        assert len(files) == 2

    def test_docker_compose_has_restart_policy(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        compose = next(f for f in files if f.path == Path("docker-compose.workers.yml"))
        assert "restart: always" in compose.content

    def test_docker_compose_has_resource_limits(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        compose = next(f for f in files if f.path == Path("docker-compose.workers.yml"))
        assert "memory:" in compose.content
        assert "cpus:" in compose.content

    def test_docker_compose_has_job_commands(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        compose = next(f for f in files if f.path == Path("docker-compose.workers.yml"))
        assert "python -m app.workers.process" in compose.content
        assert "python -m app.jobs.fetch_prices" in compose.content

    def test_deploy_workflow_has_hcloud(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        workflow = next(f for f in files if f.path == Path(".github/workflows/deploy-workers.yml"))
        assert "HCLOUD_TOKEN" in workflow.content
        assert "hcloud" in workflow.content

    def test_deploy_workflow_has_destroy_job(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        workflow = next(f for f in files if f.path == Path(".github/workflows/deploy-workers.yml"))
        assert "destroy" in workflow.content.lower()

    def test_deploy_workflow_has_status_job(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        workflow = next(f for f in files if f.path == Path(".github/workflows/deploy-workers.yml"))
        assert "status" in workflow.content.lower()

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


class TestWorkerGeneratorScheduledJob:
    """Tests for scheduled job specific behavior."""

    def test_scheduled_job_has_schedule_label(self, worker_context: GeneratorContext) -> None:
        gen = WorkerGenerator(worker_context)
        files = gen.generate_files()
        compose = next(f for f in files if f.path == Path("docker-compose.workers.yml"))
        assert "prisme.job.schedule" in compose.content
        assert "*/15 * * * *" in compose.content
