"""Tests for the Hetzner Cloud ephemeral runner module."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from prisme.ci.hetzner_runner import (
    HetznerRunnerConfig,
    HetznerRunnerManager,
    RunnerVM,
)


class TestHetznerRunnerConfig:
    """Tests for HetznerRunnerConfig dataclass."""

    def test_default_config(self) -> None:
        config = HetznerRunnerConfig()
        assert config.server_type == "cx22"
        assert config.location == "fsn1"
        assert config.image == "ubuntu-24.04"
        assert config.labels["managed-by"] == "prisme"
        assert config.labels["purpose"] == "ci-runner"
        assert config.ssh_key_name is None

    def test_custom_config(self) -> None:
        config = HetznerRunnerConfig(
            server_type="cx32",
            location="nbg1",
            image="ubuntu-22.04",
            ssh_key_name="my-key",
        )
        assert config.server_type == "cx32"
        assert config.location == "nbg1"
        assert config.image == "ubuntu-22.04"
        assert config.ssh_key_name == "my-key"

    def test_arm_server_type(self) -> None:
        config = HetznerRunnerConfig(server_type="cax11")
        assert config.server_type == "cax11"


class TestRunnerVM:
    """Tests for RunnerVM dataclass."""

    def test_runner_vm_creation(self) -> None:
        vm = RunnerVM(
            server_id=12345,
            name="prisme-runner-abc123",
            ipv4="1.2.3.4",
            status="running",
            labels={"managed-by": "prisme"},
            created="2025-01-01T00:00:00+00:00",
        )
        assert vm.server_id == 12345
        assert vm.name == "prisme-runner-abc123"
        assert vm.ipv4 == "1.2.3.4"
        assert vm.status == "running"

    def test_runner_vm_defaults(self) -> None:
        vm = RunnerVM(server_id=1, name="test", ipv4="0.0.0.0", status="creating")
        assert vm.labels == {}
        assert vm.created == ""


class TestHetznerRunnerManager:
    """Tests for HetznerRunnerManager."""

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_verify_hcloud_cli_success(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        manager = HetznerRunnerManager()
        assert manager.config.server_type == "cx22"

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_verify_hcloud_cli_not_found(self, mock_run: MagicMock) -> None:
        mock_run.side_effect = FileNotFoundError()
        with pytest.raises(RuntimeError, match="hcloud CLI not found"):
            HetznerRunnerManager()

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_create_runner_calls_hcloud(self, mock_run: MagicMock) -> None:
        # First call: verify hcloud CLI
        # Second call: hcloud server create
        server_json = '{"server": {"id": 42, "public_net": {"ipv4": {"ip": "5.6.7.8"}}}}'
        mock_run.side_effect = [
            MagicMock(returncode=0),  # hcloud version
            MagicMock(returncode=0, stdout=server_json),  # server create
        ]

        manager = HetznerRunnerManager()
        vm = manager.create_runner(
            github_repo="owner/repo",
            registration_token="fake-token",
            runner_labels=["self-hosted", "hetzner"],
        )

        assert vm.server_id == 42
        assert vm.ipv4 == "5.6.7.8"
        assert vm.name.startswith("prisme-runner-")

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_create_runner_arm_arch(self, mock_run: MagicMock) -> None:
        server_json = '{"server": {"id": 99, "public_net": {"ipv4": {"ip": "1.1.1.1"}}}}'
        mock_run.side_effect = [
            MagicMock(returncode=0),  # hcloud version
            MagicMock(returncode=0, stdout=server_json),  # server create
        ]

        config = HetznerRunnerConfig(server_type="cax11")
        manager = HetznerRunnerManager(config)
        vm = manager.create_runner(
            github_repo="owner/repo",
            registration_token="fake-token",
        )

        assert vm.server_id == 99
        # Verify ARM arch was used in cloud-init
        create_call = mock_run.call_args_list[1]
        cmd = create_call[0][0]
        assert "--type" in cmd
        type_idx = cmd.index("--type")
        assert cmd[type_idx + 1] == "cax11"

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_destroy_runner(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        manager = HetznerRunnerManager()
        manager.destroy_runner(42)

        # Second call should be server delete
        delete_call = mock_run.call_args_list[1]
        cmd = delete_call[0][0]
        assert "server" in cmd
        assert "delete" in cmd
        assert "42" in cmd

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_destroy_runner_by_name(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        manager = HetznerRunnerManager()
        manager.destroy_runner_by_name("prisme-runner-abc123")

        delete_call = mock_run.call_args_list[1]
        cmd = delete_call[0][0]
        assert "prisme-runner-abc123" in cmd

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_list_runners_empty(self, mock_run: MagicMock) -> None:
        mock_run.side_effect = [
            MagicMock(returncode=0),  # hcloud version
            MagicMock(returncode=0, stdout="[]"),  # server list
        ]
        manager = HetznerRunnerManager()
        runners = manager.list_runners()
        assert runners == []

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_list_runners_with_results(self, mock_run: MagicMock) -> None:
        import json

        servers = [
            {
                "id": 1,
                "name": "prisme-runner-abc",
                "public_net": {"ipv4": {"ip": "1.2.3.4"}},
                "status": "running",
                "labels": {"managed-by": "prisme"},
                "created": "2025-01-01T00:00:00+00:00",
            },
            {
                "id": 2,
                "name": "prisme-runner-def",
                "public_net": {"ipv4": {"ip": "5.6.7.8"}},
                "status": "creating",
                "labels": {"managed-by": "prisme"},
                "created": "2025-01-01T01:00:00+00:00",
            },
        ]
        mock_run.side_effect = [
            MagicMock(returncode=0),  # hcloud version
            MagicMock(returncode=0, stdout=json.dumps(servers)),  # server list
        ]
        manager = HetznerRunnerManager()
        runners = manager.list_runners()
        assert len(runners) == 2
        assert runners[0].server_id == 1
        assert runners[0].name == "prisme-runner-abc"
        assert runners[1].server_id == 2

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_custom_labels_in_create(self, mock_run: MagicMock) -> None:
        server_json = '{"server": {"id": 1, "public_net": {"ipv4": {"ip": "1.1.1.1"}}}}'
        mock_run.side_effect = [
            MagicMock(returncode=0),  # hcloud version
            MagicMock(returncode=0, stdout=server_json),  # server create
        ]

        config = HetznerRunnerConfig(
            labels={"managed-by": "prisme", "purpose": "ci-runner", "team": "backend"}
        )
        manager = HetznerRunnerManager(config)
        vm = manager.create_runner(
            github_repo="owner/repo",
            registration_token="fake-token",
        )

        assert "team" in vm.labels
        assert vm.labels["team"] == "backend"

    @patch("prisme.ci.hetzner_runner.subprocess.run")
    def test_ssh_key_in_create(self, mock_run: MagicMock) -> None:
        server_json = '{"server": {"id": 1, "public_net": {"ipv4": {"ip": "1.1.1.1"}}}}'
        mock_run.side_effect = [
            MagicMock(returncode=0),
            MagicMock(returncode=0, stdout=server_json),
        ]

        config = HetznerRunnerConfig(ssh_key_name="my-key")
        manager = HetznerRunnerManager(config)
        manager.create_runner(
            github_repo="owner/repo",
            registration_token="fake-token",
        )

        create_call = mock_run.call_args_list[1]
        cmd = create_call[0][0]
        assert "--ssh-key" in cmd
        assert "my-key" in cmd
