from __future__ import annotations

import json
import os
from pathlib import Path
from unittest.mock import Mock, patch

from appraisal_forge_sdk import cli
from appraisal_forge_sdk.client import ApiError


def test_cli_create_calls_client(tmp_path: Path) -> None:
    client = Mock()
    client.create_appraisal.return_value = {"id": 123}

    with patch.object(cli, "AppraisalForgeClient", return_value=client):
        code = cli.main(
            [
                "--api-url",
                "http://localhost:8090",
                "--token",
                "abc",
                "create",
                "--name",
                "My Report",
                "--subject-address",
                "123 Main St",
            ]
        )

    assert code == 0
    client.create_appraisal.assert_called_once_with("My Report", "123 Main St")


def test_cli_fields_write_uses_json_file(tmp_path: Path) -> None:
    payload_path = tmp_path / "fields.json"
    payload_path.write_text(json.dumps({"0100.0009": "Raleigh"}))

    client = Mock()
    client.write_fields.return_value = {"updated": 1}

    with patch.object(cli, "AppraisalForgeClient", return_value=client):
        code = cli.main(
            [
                "--api-url",
                "http://localhost:8090",
                "--token",
                "abc",
                "fields-write",
                "--appraisal-id",
                "44",
                "--json",
                str(payload_path),
            ]
        )

    assert code == 0
    client.write_fields.assert_called_once_with(44, {"0100.0009": "Raleigh"})


def test_cli_fill_calls_orchestrator(tmp_path: Path) -> None:
    cfg = tmp_path / "fill.json"
    cfg.write_text(json.dumps({"subject_address": "123 Main St"}))

    client = Mock()

    with patch.object(cli, "AppraisalForgeClient", return_value=client), patch.object(
        cli,
        "run_fill",
        return_value={"appraisal_id": 99, "dry_run": True},
    ) as mocked_fill:
        code = cli.main(
            [
                "--api-url",
                "http://localhost:8090",
                "--token",
                "abc",
                "fill",
                "--config",
                str(cfg),
                "--dry-run",
            ]
        )

    assert code == 0
    mocked_fill.assert_called_once()


def test_cli_tokens_calls_client() -> None:
    client = Mock()
    client.list_tokens.return_value = {"tokens": []}

    with patch.object(cli, "AppraisalForgeClient", return_value=client):
        code = cli.main(
            [
                "--api-url",
                "http://localhost:8090",
                "--token",
                "abc",
                "tokens",
            ]
        )

    assert code == 0
    client.list_tokens.assert_called_once_with()


def test_cli_create_token_posts_params() -> None:
    client = Mock()
    client.create_token.return_value = {"id": 3, "token": "sdk-token"}

    with patch.object(cli, "AppraisalForgeClient", return_value=client):
        code = cli.main(
            [
                "--api-url",
                "http://localhost:8090",
                "--token",
                "abc",
                "create-token",
                "--label",
                "My Label",
                "--scope",
                "full",
            ]
        )

    assert code == 0
    client.create_token.assert_called_once_with(label="My Label", scope="full")


def test_cli_revoke_token_calls_client() -> None:
    client = Mock()
    client.revoke_token.return_value = {"message": "Token revoked", "id": 7}

    with patch.object(cli, "AppraisalForgeClient", return_value=client):
        code = cli.main(
            [
                "--api-url",
                "http://localhost:8090",
                "--token",
                "abc",
                "revoke-token",
                "--token-id",
                "7",
            ]
        )

    assert code == 0
    client.revoke_token.assert_called_once_with(7)


def test_cli_connection_info_calls_client() -> None:
    client = Mock()
    client.get_connection_info.return_value = {
        "api_base_url": "http://localhost:8088",
        "mcp_base_url": "http://localhost:8089",
    }

    with patch.object(cli, "AppraisalForgeClient", return_value=client):
        code = cli.main(
            [
                "--api-url",
                "http://localhost:8090",
                "--token",
                "abc",
                "connection-info",
            ]
        )

    assert code == 0
    client.get_connection_info.assert_called_once_with()


def test_cli_verify_token_calls_client() -> None:
    client = Mock()
    client.verify_token.return_value = {"valid": True}

    with patch.object(cli, "AppraisalForgeClient", return_value=client):
        code = cli.main(
            [
                "--api-url",
                "http://localhost:8090",
                "--token",
                "abc",
                "verify-token",
            ]
        )

    assert code == 0
    client.verify_token.assert_called_once_with()


def test_cli_login_device_flow_saves_token(tmp_path: Path) -> None:
    cfg_dir = tmp_path / "cfg"
    with patch.dict(os.environ, {"APPRAISAL_FORGE_CONFIG_DIR": str(cfg_dir)}, clear=False):
        with patch.object(
            cli.AppraisalForgeClient,
            "start_device_login",
            return_value={
                "device_code": "dev-1",
                "user_code": "ABCD-EF12",
                "verification_uri_complete": "http://localhost:8090/api/auth/device/approve/?user_code=ABCD-EF12",
                "interval": 1,
            },
        ):
            with patch.object(
                cli.AppraisalForgeClient,
                "poll_device_token",
                side_effect=[
                    ApiError(
                        "pending",
                        status_code=428,
                        payload={"error": "authorization_pending"},
                    ),
                    {
                        "access_token": "tok-123",
                        "scope": "full",
                        "user": {"id": 1, "username": "demo"},
                    },
                ],
            ):
                with patch.object(cli.time, "sleep", return_value=None):
                    code = cli.main(
                        [
                            "--api-url",
                            "http://localhost:8090",
                            "login",
                            "--timeout-seconds",
                            "30",
                        ]
                    )

    assert code == 0
    config_path = cfg_dir / "config.json"
    saved = json.loads(config_path.read_text())
    assert saved["api_url"] == "http://localhost:8090"
    assert saved["token"] == "tok-123"


def test_cli_relay_resume_uses_saved_runtime_command(tmp_path: Path) -> None:
    cfg_dir = tmp_path / "cfg"
    cfg_dir.mkdir(parents=True, exist_ok=True)
    (cfg_dir / "config.json").write_text(
        json.dumps(
            {
                "api_url": "http://localhost:8090",
                "token": "tok-123",
                "relay_agent_cmd": "python3 -m demo_worker",
                "relay_last_appraisal_id": 301,
            }
        )
    )
    client = Mock()
    client.api_url = "http://localhost:8090"
    client.token = "tok-123"

    with patch.dict(os.environ, {"APPRAISAL_FORGE_CONFIG_DIR": str(cfg_dir)}, clear=False):
        with patch.object(cli, "AppraisalForgeClient", return_value=client), patch.object(
            cli,
            "_start_local_worker",
            return_value={"started": True},
        ) as mocked_start:
            code = cli.main(["relay-resume"])

    assert code == 0
    mocked_start.assert_called_once_with(
        api_url="http://localhost:8090",
        token="tok-123",
        agent_cmd="python3 -m demo_worker",
        appraisal_id=301,
    )


def test_cli_relay_start_rejects_codex_prompt_flag() -> None:
    code = cli.main(
        [
            "--api-url",
            "http://localhost:8090",
            "--token",
            "abc",
            "relay-start",
            "--agent-cmd",
            "codex --prompt '{message}'",
        ]
    )
    assert code == 2
