"""Unit tests for the Docker launch broker (PRD 0070). Docker is mocked."""

from __future__ import annotations

import secrets
import unittest
from unittest.mock import Mock, patch

from bot_bottle.orchestrator.broker import (
    BrokerAuthError,
    LaunchRequest,
    sign_request,
)
from bot_bottle.orchestrator.docker_broker import (
    BOTTLE_ID_LABEL,
    DockerBroker,
    DockerBrokerError,
    container_name,
    rm_argv,
    run_argv,
)


class TestArgv(unittest.TestCase):
    def test_run_argv_uses_only_static_fields(self) -> None:
        req = LaunchRequest(op="launch", bottle_id="b1", image_ref="busybox")
        argv = run_argv(req)
        self.assertEqual(["docker", "run"], argv[:2])
        self.assertIn("--name", argv)
        self.assertIn(container_name("b1"), argv)
        self.assertIn(f"{BOTTLE_ID_LABEL}=b1", argv)
        self.assertEqual("busybox", argv[-1])  # image is the terminal arg

    def test_rm_argv(self) -> None:
        req = LaunchRequest(op="teardown", bottle_id="b1")
        self.assertEqual(["docker", "rm", "--force", container_name("b1")], rm_argv(req))

    def test_container_name_is_prefixed(self) -> None:
        name = container_name("b1")
        self.assertTrue(name.startswith("bot-bottle-orch-"))
        self.assertTrue(name.endswith("b1"))


class TestDockerBroker(unittest.TestCase):
    def setUp(self) -> None:
        self.secret = secrets.token_bytes(16)
        self.broker = DockerBroker(self.secret)

    def _submit(self, req: LaunchRequest) -> None:
        self.broker.submit(sign_request(req, self.secret))

    def test_launch_invokes_docker_run(self) -> None:
        req = LaunchRequest(op="launch", bottle_id="b1", image_ref="busybox")
        with patch.object(self.broker, "_docker", return_value=Mock(returncode=0, stderr="")) as m:
            self._submit(req)
        m.assert_called_once()
        self.assertEqual(run_argv(req), m.call_args.args[0])

    def test_launch_without_image_raises_and_skips_docker(self) -> None:
        req = LaunchRequest(op="launch", bottle_id="b1", image_ref="")
        with patch.object(self.broker, "_docker") as m:
            with self.assertRaises(DockerBrokerError):
                self._submit(req)
            m.assert_not_called()

    def test_launch_docker_failure_raises(self) -> None:
        req = LaunchRequest(op="launch", bottle_id="b1", image_ref="busybox")
        with patch.object(self.broker, "_docker", return_value=Mock(returncode=1, stderr="boom")):
            with self.assertRaises(DockerBrokerError):
                self._submit(req)

    def test_teardown_invokes_docker_rm(self) -> None:
        req = LaunchRequest(op="teardown", bottle_id="b1")
        with patch.object(self.broker, "_docker", return_value=Mock(returncode=0, stderr="")) as m:
            self._submit(req)
        self.assertEqual(rm_argv(req), m.call_args.args[0])

    def test_teardown_is_idempotent_on_missing_container(self) -> None:
        req = LaunchRequest(op="teardown", bottle_id="b1")
        absent = Mock(returncode=1, stderr="Error: No such container: bot-bottle-orch-b1")
        with patch.object(self.broker, "_docker", return_value=absent):
            self._submit(req)  # must not raise

    def test_teardown_other_failure_raises(self) -> None:
        req = LaunchRequest(op="teardown", bottle_id="b1")
        with patch.object(self.broker, "_docker", return_value=Mock(returncode=1, stderr="daemon down")):
            with self.assertRaises(DockerBrokerError):
                self._submit(req)

    def test_forged_token_never_touches_docker(self) -> None:
        req = LaunchRequest(op="launch", bottle_id="b1", image_ref="busybox")
        forged = sign_request(req, secrets.token_bytes(16))
        with patch.object(self.broker, "_docker") as m:
            with self.assertRaises(BrokerAuthError):
                self.broker.submit(forged)
            m.assert_not_called()


if __name__ == "__main__":
    unittest.main()
