"""Unit: Docker launch teardown warning on ExitStack failure (issue #156).

When a callback registered in the ExitStack raises during teardown,
the teardown function must emit a WARNING-level message that includes
the container name and operation type, rather than silently discarding
the exception.
"""

from __future__ import annotations

import contextlib
import io
import tempfile
import unittest
from pathlib import Path
from unittest import mock

from bot_bottle.agent_provider import AgentProvisionPlan
from bot_bottle.backend import BottleSpec
from bot_bottle.backend.docker import launch as launch_mod
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
from bot_bottle.egress import EgressPlan
from bot_bottle.git_gate import GitGatePlan
from bot_bottle.manifest import ManifestIndex

_INDEX = ManifestIndex.from_json_obj({
    "bottles": {"dev": {}},
    "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
})


def _plan(tmp: str) -> DockerBottlePlan:
    stage = Path(tmp)
    manifest = _INDEX.load_for_agent("demo")
    spec = BottleSpec(
        manifest=_INDEX,
        agent_name="demo",
        copy_cwd=False,
        user_cwd=tmp,
        identity="test-teardown-00001",
    )
    return DockerBottlePlan(
        spec=spec,
        manifest=manifest,
        stage_dir=stage,
        git_gate_plan=GitGatePlan(
            slug="test-teardown-00001",
            entrypoint_script=stage / "entrypoint.sh",
            hook_script=stage / "hook.sh",
            access_hook_script=stage / "access-hook.sh",
            upstreams=(),
        ),
        egress_plan=EgressPlan(
            slug="test-teardown-00001",
            routes_path=stage / "egress.yaml",
            routes=(),
            token_env_map={},
        ),
        supervise_plan=None,
        agent_provision=AgentProvisionPlan(
            template="claude",
            command="claude",
            prompt_mode="append_file",
            image="bot-bottle-claude:latest",
            dockerfile="",
            guest_home="/home/node",
            instance_name="bot-bottle-test-teardown-abc",
            prompt_file=stage / "prompt.txt",
            guest_env={},
        ),
        slug="test-teardown-00001",
        forwarded_env={},
        use_runsc=False,
    )


class TestTeardownWarning(unittest.TestCase):
    def setUp(self) -> None:
        self._tmp = tempfile.mkdtemp(prefix="docker-launch-teardown-test.")

    def tearDown(self) -> None:
        import shutil
        shutil.rmtree(self._tmp, ignore_errors=True)

    def test_teardown_failure_emits_warning_with_container_and_operation(self):
        plan = _plan(self._tmp)
        buf = io.StringIO()

        with mock.patch.object(launch_mod.docker_mod, "build_image"), \
             mock.patch.object(
                 launch_mod, "egress_tls_init",
                 return_value=(Path("/egress_ca"), Path("/egress_cert")),
             ), \
             mock.patch.object(
                 launch_mod.network_mod, "network_name_for_slug",
                 return_value="bb-internal-test",
             ), \
             mock.patch.object(
                 launch_mod.network_mod, "network_egress_name_for_slug",
                 return_value="bb-egress-test",
             ), \
             mock.patch.object(
                 launch_mod, "bottle_plan_to_compose",
                 return_value={"services": {"agent": {}}},
             ), \
             mock.patch.object(
                 launch_mod, "write_compose_file",
                 return_value=Path("/tmp/compose.yml"),
             ), \
             mock.patch.object(launch_mod, "compose_up"), \
             mock.patch.object(launch_mod, "compose_dump_logs"), \
             mock.patch.object(
                 launch_mod, "compose_down",
                 side_effect=RuntimeError("network remove failed"),
             ), \
             contextlib.redirect_stderr(buf):
            provision = mock.Mock(return_value=None)
            with launch_mod.launch(plan, provision=provision):
                pass

        output = buf.getvalue()
        self.assertIn("bot-bottle: warning:", output)
        self.assertIn("bot-bottle-test-teardown-abc", output)
        self.assertIn("compose-down", output)


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