import json
import subprocess
from unittest.mock import MagicMock, patch

from armorer.apps.openclaw.channels import (
    approve_openclaw_pairings,
    bind_openclaw_channels,
    discover_openclaw_pairing_requests,
    verify_openclaw_channels,
)


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_bind_openclaw_channels_telegram(mock_run, tmp_path):
    mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")

    result = bind_openclaw_channels(
        tmp_path,
        ["telegram"],
        {"TELEGRAM_BOT_TOKEN": "123:abc"},
    )

    assert result["bound_channels"] == ["telegram"]
    assert result["warnings"] == []
    args, kwargs = mock_run.call_args
    assert args[0][-5:] == ["channels", "add", "--channel", "telegram", "--token", "123:abc"][-5:]
    assert kwargs["cwd"] == tmp_path


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_bind_openclaw_channels_prefers_exec_delegate_when_available(mock_run, tmp_path):
    (tmp_path / "docker-compose.yml").write_text(
        """
services:
  openclaw-gateway:
    image: openclaw:local
  openclaw-cli:
    image: openclaw:local
    network_mode: service:openclaw-gateway
    entrypoint:
      - node
      - dist/index.js
"""
    )

    def fake_run(cmd, **kwargs):
        if cmd[:6] == ["docker", "compose", "ps", "--status", "running", "-q"]:
            return MagicMock(returncode=0, stdout="container-id\n", stderr="")
        if cmd[:5] == ["docker", "compose", "exec", "-T", "openclaw-gateway"]:
            return MagicMock(returncode=0, stdout='Added Telegram account "default".\n', stderr="")
        raise AssertionError(f"Unexpected command: {cmd}")

    mock_run.side_effect = fake_run

    result = bind_openclaw_channels(
        tmp_path,
        ["telegram"],
        {"TELEGRAM_BOT_TOKEN": "123:abc"},
    )

    assert result["bound_channels"] == ["telegram"]
    exec_calls = [
        call.args[0]
        for call in mock_run.call_args_list
        if call.args and call.args[0][:5] == ["docker", "compose", "exec", "-T", "openclaw-gateway"]
    ]
    assert exec_calls
    assert exec_calls[0][5:] == [
        "node",
        "dist/index.js",
        "channels",
        "add",
        "--channel",
        "telegram",
        "--token",
        "123:abc",
    ]


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_bind_openclaw_channels_telegram_falls_back_to_config_managed_runtime(mock_run, tmp_path):
    mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="Unknown channel: telegram")

    result = bind_openclaw_channels(
        tmp_path,
        ["telegram"],
        {"TELEGRAM_BOT_TOKEN": "123:abc"},
    )

    assert result["bound_channels"] == []
    assert result["config_managed_channels"] == ["telegram"]
    assert result["warnings"] == []


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_verify_openclaw_channels_reports_probe_state(mock_run, tmp_path):
    mock_run.return_value = MagicMock(
        returncode=0,
        stdout=json.dumps(
            {
                "channelAccounts": {
                    "telegram": [
                        {
                            "configured": True,
                            "connected": True,
                            "running": True,
                            "probe": {"ok": True},
                        }
                    ]
                }
            }
        ),
        stderr="",
    )

    result = verify_openclaw_channels(tmp_path, ["telegram"])

    assert result["ok"] is True
    assert result["statuses"]["telegram"]["configured"] is True
    assert result["statuses"]["telegram"]["verified"] is True
    assert result["warnings"] == []


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_verify_openclaw_channels_times_out_cleanly(mock_run, tmp_path):
    mock_run.side_effect = subprocess.TimeoutExpired(["docker", "compose"], timeout=20)

    result = verify_openclaw_channels(tmp_path, ["telegram"])

    assert result["ok"] is False
    assert result["statuses"]["telegram"]["verified"] is False
    assert "timed out" in result["statuses"]["telegram"]["detail"]
    assert result["warnings"]


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_verify_openclaw_channels_uses_one_off_cli_container_even_when_delegate_exists(mock_run, tmp_path):
    (tmp_path / "docker-compose.yml").write_text(
        """
services:
  openclaw-gateway:
    image: openclaw:local
  openclaw-cli:
    image: openclaw:local
    network_mode: service:openclaw-gateway
    entrypoint:
      - node
      - dist/index.js
"""
    )

    def fake_run(cmd, **kwargs):
        if cmd[:5] == ["docker", "compose", "run", "--rm", "openclaw-cli"]:
            return MagicMock(
                returncode=0,
                stdout=json.dumps(
                    {
                        "channelAccounts": {
                            "telegram": [
                                {
                                    "configured": True,
                                    "probe": {"ok": True},
                                }
                            ]
                        }
                    }
                ),
                stderr="",
            )
        raise AssertionError(f"Unexpected command: {cmd}")

    mock_run.side_effect = fake_run

    result = verify_openclaw_channels(tmp_path, ["telegram"])

    assert result["ok"] is True
    run_calls = [
        call.args[0]
        for call in mock_run.call_args_list
        if call.args and call.args[0][:5] == ["docker", "compose", "run", "--rm", "openclaw-cli"]
    ]
    assert run_calls
    assert run_calls[0][5:] == ["channels", "status", "--probe", "--json"]


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_approve_openclaw_pairings_telegram(mock_run, tmp_path):
    mock_run.return_value = MagicMock(returncode=0, stdout="Approved telegram sender 123.\n", stderr="")

    result = approve_openclaw_pairings(tmp_path, {"telegram": "PAIRME12"})

    assert result["ok"] is True
    assert result["statuses"]["telegram"]["approved"] is True
    args, kwargs = mock_run.call_args
    assert args[0][-4:] == ["pairing", "approve", "telegram", "PAIRME12"]
    assert kwargs["cwd"] == tmp_path


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_approve_openclaw_pairings_prefers_exec_delegate_when_available(mock_run, tmp_path):
    (tmp_path / "docker-compose.yml").write_text(
        """
services:
  openclaw-gateway:
    image: openclaw:local
  openclaw-cli:
    image: openclaw:local
    network_mode: service:openclaw-gateway
    entrypoint:
      - node
      - dist/index.js
"""
    )

    def fake_run(cmd, **kwargs):
        if cmd[:6] == ["docker", "compose", "ps", "--status", "running", "-q"]:
            return MagicMock(returncode=0, stdout="container-id\n", stderr="")
        if cmd[:5] == ["docker", "compose", "exec", "-T", "openclaw-gateway"]:
            return MagicMock(returncode=0, stdout="Approved telegram sender 123.\n", stderr="")
        raise AssertionError(f"Unexpected command: {cmd}")

    mock_run.side_effect = fake_run

    result = approve_openclaw_pairings(tmp_path, {"telegram": "PAIRME12"})

    assert result["ok"] is True
    exec_calls = [
        call.args[0]
        for call in mock_run.call_args_list
        if call.args and call.args[0][:5] == ["docker", "compose", "exec", "-T", "openclaw-gateway"]
    ]
    assert exec_calls
    assert exec_calls[0][5:] == ["node", "dist/index.js", "pairing", "approve", "telegram", "PAIRME12"]


def test_discover_openclaw_pairing_requests_reads_pending_telegram_codes(tmp_path):
    pairing_file = tmp_path / ".openclaw" / "credentials" / "telegram-pairing.json"
    pairing_file.parent.mkdir(parents=True)
    pairing_file.write_text(
        json.dumps(
            {
                "version": 1,
                "requests": [
                    {
                        "id": "350926187",
                        "code": "B3KHK7HN",
                        "createdAt": "2026-03-09T22:24:09.584Z",
                        "lastSeenAt": "2026-03-09T22:34:02.292Z",
                        "meta": {"username": "Shot2kill", "firstName": "Dani", "accountId": "default"},
                    }
                ],
            }
        )
    )

    requests = discover_openclaw_pairing_requests(tmp_path, "telegram")

    assert requests == [
        {
            "id": "350926187",
            "code": "B3KHK7HN",
            "created_at": "2026-03-09T22:24:09.584Z",
            "last_seen_at": "2026-03-09T22:34:02.292Z",
            "username": "Shot2kill",
            "first_name": "Dani",
            "account_id": "default",
        }
    ]


@patch("armorer.apps.openclaw.channels.subprocess.run")
def test_discover_openclaw_pairing_requests_prefers_live_cli_over_stale_cache(mock_run, tmp_path):
    (tmp_path / "docker-compose.yml").write_text(
        """
services:
  openclaw-gateway:
    image: openclaw:local
  openclaw-cli:
    image: openclaw:local
    network_mode: service:openclaw-gateway
    entrypoint:
      - node
      - dist/index.js
"""
    )
    pairing_file = tmp_path / ".openclaw" / "credentials" / "telegram-pairing.json"
    pairing_file.parent.mkdir(parents=True)
    pairing_file.write_text(
        json.dumps(
            {
                "version": 1,
                "requests": [
                    {
                        "id": "350926187",
                        "code": "STALE123",
                        "createdAt": "2026-03-09T22:24:09.584Z",
                        "lastSeenAt": "2026-03-09T22:34:02.292Z",
                        "meta": {"username": "Shot2kill", "firstName": "Dani", "accountId": "default"},
                    }
                ],
            }
        )
    )

    def fake_run(cmd, **kwargs):
        if cmd[:6] == ["docker", "compose", "ps", "--status", "running", "-q"]:
            return MagicMock(returncode=0, stdout="container-id\n", stderr="")
        if cmd[:5] == ["docker", "compose", "exec", "-T", "openclaw-gateway"]:
            return MagicMock(returncode=0, stdout=json.dumps({"channel": "telegram", "requests": []}), stderr="")
        if cmd[:5] == ["docker", "compose", "run", "--rm", "openclaw-cli"]:
            return MagicMock(returncode=0, stdout=json.dumps({"channel": "telegram", "requests": []}), stderr="")
        raise AssertionError(f"Unexpected command: {cmd}")

    mock_run.side_effect = fake_run

    requests = discover_openclaw_pairing_requests(tmp_path, "telegram")

    assert requests == []
