"""Tests for the Hetzner Cloud worker module."""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

import pytest

from prisme.deploy.hetzner_worker import (
    HetznerWorkerManager,
    WorkerVM,
    _build_cloud_init,
)
from prisme.spec.infrastructure import HetznerWorkerJobSpec, HetznerWorkersConfig


class TestHetznerWorkerJobSpec:
    """Tests for HetznerWorkerJobSpec model."""

    def test_minimal_job(self) -> None:
        job = HetznerWorkerJobSpec(
            name="data-processor",
            command="python -m app.workers.process",
        )
        assert job.name == "data-processor"
        assert job.command == "python -m app.workers.process"
        assert job.schedule is None
        assert job.server_type == "cx22"
        assert job.restart_policy == "on-failure"
        assert job.max_restarts == 3
        assert job.timeout_seconds == 3600
        assert job.cpu == 2
        assert job.memory_mb == 4096

    def test_scheduled_job(self) -> None:
        job = HetznerWorkerJobSpec(
            name="price-fetcher",
            command="python -m app.jobs.fetch_prices",
            schedule="*/15 * * * *",
            restart_policy="never",
        )
        assert job.schedule == "*/15 * * * *"
        assert job.restart_policy == "never"

    def test_long_running_worker(self) -> None:
        job = HetznerWorkerJobSpec(
            name="queue-consumer",
            command="python -m app.workers.consume",
            restart_policy="always",
            server_type="cx32",
            cpu=4,
            memory_mb=8192,
        )
        assert job.restart_policy == "always"
        assert job.server_type == "cx32"
        assert job.cpu == 4
        assert job.memory_mb == 8192

    def test_custom_environment(self) -> None:
        job = HetznerWorkerJobSpec(
            name="reporter",
            command="python -m app.jobs.report",
            environment={"API_KEY": "secret", "REGION": "eu-west"},
        )
        assert job.environment["API_KEY"] == "secret"

    def test_custom_docker_image(self) -> None:
        job = HetznerWorkerJobSpec(
            name="ml-worker",
            command="python -m train",
            docker_image="registry.example.com/ml-model:v2",
        )
        assert job.docker_image == "registry.example.com/ml-model:v2"


class TestHetznerWorkersConfig:
    """Tests for HetznerWorkersConfig model."""

    def test_default_config(self) -> None:
        config = HetznerWorkersConfig()
        assert config.enabled is False
        assert config.default_server_type == "cx22"
        assert config.default_location == "fsn1"
        assert config.jobs == []

    def test_enabled_with_jobs(self) -> None:
        config = HetznerWorkersConfig(
            enabled=True,
            jobs=[
                HetznerWorkerJobSpec(
                    name="worker-1",
                    command="python -m worker",
                ),
                HetznerWorkerJobSpec(
                    name="worker-2",
                    command="python -m scheduler",
                    schedule="0 * * * *",
                ),
            ],
        )
        assert config.enabled is True
        assert len(config.jobs) == 2


class TestBuildCloudInit:
    """Tests for cloud-init generation."""

    def test_long_running_worker_cloud_init(self) -> None:
        job = HetznerWorkerJobSpec(
            name="processor",
            command="python -m app.workers.process",
            restart_policy="always",
        )
        cloud_init = _build_cloud_init(
            job=job,
            docker_registry="ghcr.io",
            docker_image="ghcr.io/myorg/myapp-backend:latest",
            project_name="myapp",
        )
        assert "#cloud-config" in cloud_init
        assert "docker.io" in cloud_init
        assert "ghcr.io/myorg/myapp-backend:latest" in cloud_init
        assert "--restart=always" in cloud_init
        assert "python -m app.workers.process" in cloud_init

    def test_scheduled_job_cloud_init(self) -> None:
        job = HetznerWorkerJobSpec(
            name="fetcher",
            command="python -m app.jobs.fetch",
            schedule="*/15 * * * *",
        )
        cloud_init = _build_cloud_init(
            job=job,
            docker_registry="ghcr.io",
            docker_image="ghcr.io/myorg/myapp-backend:latest",
            project_name="myapp",
        )
        assert "cron" in cloud_init
        assert "*/15 * * * *" in cloud_init

    def test_on_failure_restart(self) -> None:
        job = HetznerWorkerJobSpec(
            name="task",
            command="python run.py",
            restart_policy="on-failure",
            max_restarts=5,
        )
        cloud_init = _build_cloud_init(
            job=job,
            docker_registry="ghcr.io",
            docker_image="img:latest",
            project_name="app",
        )
        assert "--restart=on-failure:5" in cloud_init

    def test_environment_variables_in_cloud_init(self) -> None:
        job = HetznerWorkerJobSpec(
            name="env-test",
            command="python run.py",
            environment={"DB_URL": "postgres://localhost/db", "LOG_LEVEL": "debug"},
        )
        cloud_init = _build_cloud_init(
            job=job,
            docker_registry="ghcr.io",
            docker_image="img:latest",
            project_name="app",
        )
        assert "DB_URL" in cloud_init
        assert "LOG_LEVEL" in cloud_init


class TestWorkerVM:
    """Tests for WorkerVM dataclass."""

    def test_worker_vm(self) -> None:
        vm = WorkerVM(
            server_id=100,
            name="myapp-worker-processor",
            ipv4="10.0.0.1",
            status="running",
            job_name="processor",
        )
        assert vm.server_id == 100
        assert vm.job_name == "processor"

    def test_worker_vm_defaults(self) -> None:
        vm = WorkerVM(server_id=1, name="w", ipv4="0.0.0.0", status="creating", job_name="j")
        assert vm.labels == {}
        assert vm.created == ""


class TestHetznerWorkerManager:
    """Tests for HetznerWorkerManager."""

    @patch("prisme.deploy.hetzner_worker.subprocess.run")
    def test_deploy_job(self, mock_run: MagicMock) -> None:
        server_json = '{"server": {"id": 55, "public_net": {"ipv4": {"ip": "10.0.0.5"}}}}'
        mock_run.side_effect = [
            MagicMock(returncode=0),  # hcloud version
            MagicMock(returncode=0, stdout=server_json),  # server create
        ]

        config = HetznerWorkersConfig(enabled=True)
        job = HetznerWorkerJobSpec(
            name="processor",
            command="python -m process",
        )

        manager = HetznerWorkerManager(config, project_name="testapp")
        vm = manager.deploy_job(job)

        assert vm.server_id == 55
        assert vm.name == "testapp-worker-processor"
        assert vm.job_name == "processor"

    @patch("prisme.deploy.hetzner_worker.subprocess.run")
    def test_deploy_all(self, mock_run: MagicMock) -> None:
        server_json1 = '{"server": {"id": 1, "public_net": {"ipv4": {"ip": "1.1.1.1"}}}}'
        server_json2 = '{"server": {"id": 2, "public_net": {"ipv4": {"ip": "2.2.2.2"}}}}'
        mock_run.side_effect = [
            MagicMock(returncode=0),  # hcloud version
            MagicMock(returncode=0, stdout=server_json1),
            MagicMock(returncode=0, stdout=server_json2),
        ]

        config = HetznerWorkersConfig(
            enabled=True,
            jobs=[
                HetznerWorkerJobSpec(name="job1", command="cmd1"),
                HetznerWorkerJobSpec(name="job2", command="cmd2"),
            ],
        )

        manager = HetznerWorkerManager(config, project_name="testapp")
        vms = manager.deploy_all()
        assert len(vms) == 2

    @patch("prisme.deploy.hetzner_worker.subprocess.run")
    def test_deploy_all_no_jobs(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        config = HetznerWorkersConfig(enabled=True, jobs=[])
        manager = HetznerWorkerManager(config, project_name="testapp")
        vms = manager.deploy_all()
        assert vms == []

    @patch("prisme.deploy.hetzner_worker.subprocess.run")
    def test_destroy_job(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        config = HetznerWorkersConfig(enabled=True)
        manager = HetznerWorkerManager(config, project_name="testapp")
        manager.destroy_job("processor")

        delete_call = mock_run.call_args_list[1]
        cmd = delete_call[0][0]
        assert "testapp-worker-processor" in cmd

    @patch("prisme.deploy.hetzner_worker.subprocess.run")
    def test_list_workers_empty(self, mock_run: MagicMock) -> None:
        mock_run.side_effect = [
            MagicMock(returncode=0),  # hcloud version
            MagicMock(returncode=0, stdout="[]"),  # server list
        ]
        config = HetznerWorkersConfig(enabled=True)
        manager = HetznerWorkerManager(config, project_name="testapp")
        workers = manager.list_workers()
        assert workers == []

    @patch("prisme.deploy.hetzner_worker.subprocess.run")
    def test_list_workers_with_results(self, mock_run: MagicMock) -> None:
        servers = [
            {
                "id": 10,
                "name": "testapp-worker-job1",
                "public_net": {"ipv4": {"ip": "1.2.3.4"}},
                "status": "running",
                "labels": {"managed-by": "prisme", "job-name": "job1"},
                "created": "2025-01-01T00:00:00+00:00",
            }
        ]
        mock_run.side_effect = [
            MagicMock(returncode=0),
            MagicMock(returncode=0, stdout=json.dumps(servers)),
        ]
        config = HetznerWorkersConfig(enabled=True)
        manager = HetznerWorkerManager(config, project_name="testapp")
        workers = manager.list_workers()
        assert len(workers) == 1
        assert workers[0].job_name == "job1"

    @patch("prisme.deploy.hetzner_worker.subprocess.run")
    def test_hcloud_not_found(self, mock_run: MagicMock) -> None:
        mock_run.side_effect = FileNotFoundError()
        config = HetznerWorkersConfig(enabled=True)
        with pytest.raises(RuntimeError, match="hcloud CLI not found"):
            HetznerWorkerManager(config, project_name="testapp")
