"""
Module: test_registry
Description: Tests for pack registry commands

Implements:
    - docs/cli/reference.md#registry-commands
"""

import os
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import httpx
import pytest
from typer.testing import CliRunner

from huitzo_cli.auth import TokenStore
from huitzo_cli.config import ConfigManager
from huitzo_cli.http_utils import parse_error
from huitzo_cli.main import pack_app


def test_publish_requires_manifest(cli_runner: CliRunner, temp_dir: Path) -> None:
    """Test publish requires huitzo.yaml."""
    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        result = cli_runner.invoke(pack_app, ["publish"])
        assert result.exit_code == 1
        assert "huitzo.yaml" in result.stdout
    finally:
        os.chdir(old_cwd)


def test_publish_requires_auth(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test publish requires authentication after manifest check."""
    # Create a huitzo.yaml
    manifest = temp_dir / "huitzo.yaml"
    manifest.write_text("pack:\n  name: test-pack\n  namespace: test\n  version: 0.0.0\n")

    # Ensure no token exists
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: temp_dir / "no-token"))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        result = cli_runner.invoke(pack_app, ["publish"])
        assert result.exit_code == 1
        assert "Not authenticated" in result.stdout
    finally:
        os.chdir(old_cwd)


def test_install_from_file(cli_runner: CliRunner, temp_dir: Path) -> None:
    """Test installing from local .whl file."""
    # Create a fake wheel file
    wheel_file = temp_dir / "test-pack-0.0.0-py3-none-any.whl"
    wheel_file.write_text("fake wheel")

    with patch("subprocess.run") as mock_run:
        mock_run.return_value.returncode = 0
        result = cli_runner.invoke(pack_app, ["install", str(wheel_file)])
        assert result.exit_code == 0
        mock_run.assert_called_once()


def test_install_missing_file(cli_runner: CliRunner) -> None:
    """Test installing from missing file."""
    result = cli_runner.invoke(pack_app, ["install", "/nonexistent/pack.whl"])
    assert result.exit_code == 1
    assert "File not found" in result.stdout


def test_install_from_registry(cli_runner: CliRunner) -> None:
    """Test installing from registry shows not available."""
    result = cli_runner.invoke(pack_app, ["install", "my-pack"])
    assert result.exit_code == 1
    assert "not yet available" in result.stdout


def test_list_packs_local_none(cli_runner: CliRunner) -> None:
    """Test listing local packs when none are installed."""
    with patch("huitzo_cli.commands.registry.importlib.metadata.entry_points", return_value=[]):
        result = cli_runner.invoke(pack_app, ["list", "--local"])
        assert result.exit_code == 0
        assert "none installed" in result.stdout


def test_list_packs_local_with_entries(cli_runner: CliRunner) -> None:
    """Test listing local packs discovered via entry points."""
    mock_dist = MagicMock()
    mock_dist.name = "my-pack"
    mock_dist.version = "1.2.3"

    mock_ep = MagicMock()
    mock_ep.name = "hello"
    mock_ep.dist = mock_dist

    with patch(
        "huitzo_cli.commands.registry.importlib.metadata.entry_points",
        return_value=[mock_ep],
    ):
        result = cli_runner.invoke(pack_app, ["list", "--local"])
        assert result.exit_code == 0
        assert "my-pack" in result.stdout
        assert "1.2.3" in result.stdout


def test_list_packs_remote(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test listing packs from remote API."""
    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    mock_resp = MagicMock(spec=httpx.Response)
    mock_resp.status_code = 200
    mock_resp.json.return_value = {
        "data": [
            {
                "id": "pack-1",
                "scope": "myns",
                "name": "test-pack",
                "visibility": "private",
                "created_at": "2025-01-01T00:00:00Z",
            }
        ]
    }

    with patch("huitzo_cli.commands.registry.httpx.get", return_value=mock_resp):
        result = cli_runner.invoke(pack_app, ["list"])

    assert result.exit_code == 0
    assert "test-pack" in result.stdout
    assert "@myns" in result.stdout


def test_run_requires_auth(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test run requires authentication."""
    # Ensure no token exists
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: temp_dir / "no-token"))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    result = cli_runner.invoke(pack_app, ["run", "my-pack:hello"])
    assert result.exit_code == 1
    assert "Not authenticated" in result.stdout


# --- HTTP flow tests ---


def _mock_response(status_code: int, json_data: dict | None = None, text: str = "") -> MagicMock:
    """Create a mock httpx.Response."""
    resp = MagicMock(spec=httpx.Response)
    resp.status_code = status_code
    resp.text = text
    if json_data is not None:
        resp.json.return_value = json_data
    else:
        resp.json.side_effect = ValueError("No JSON")
    return resp


def test_parse_error_json() -> None:
    """Test parse_error with valid JSON error."""
    resp = _mock_response(400, {"error": {"message": "Bad request"}})
    assert parse_error(resp) == "Bad request"


def test_parse_error_non_json() -> None:
    """Test parse_error returns generic HTTP status on non-JSON response (W25)."""
    resp = _mock_response(500, text="Internal Server Error")
    assert parse_error(resp) == "HTTP 500"


def test_parse_error_string_error() -> None:
    """Test parse_error when error is a plain string."""
    resp = _mock_response(400, {"error": "Something went wrong"})
    assert parse_error(resp) == "Something went wrong"


def test_publish_scope_consistency(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test publish sends scope without @ prefix (backend adds it)."""
    manifest = temp_dir / "huitzo.yaml"
    manifest.write_text("pack:\n  name: test-pack\n  namespace: myns\n  version: 1.0.0\n")

    # Create a fake wheel
    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    wheel = dist_dir / "test_pack-1.0.0-py3-none-any.whl"
    wheel.write_text("fake")

    # Setup auth
    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    # Mock HTTP calls — 201 returns the pack_id directly
    create_resp = _mock_response(201, {"data": {"id": "pack-1"}})
    upload_resp = _mock_response(201, {"data": {"commands": []}})

    call_count = 0
    post_calls: list[tuple[str, dict[str, Any]]] = []

    def mock_post(url: str, **kwargs: Any) -> MagicMock:
        nonlocal call_count
        call_count += 1
        post_calls.append((url, kwargs))
        if call_count == 1:
            return create_resp
        return upload_resp

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with patch("huitzo_cli.commands.registry.httpx.post", side_effect=mock_post):
            result = cli_runner.invoke(pack_app, ["publish"])
    finally:
        os.chdir(old_cwd)

    assert result.exit_code == 0
    # Verify scope in create request does NOT have @-prefix (backend adds it)
    create_url, create_kwargs = post_calls[0]
    assert create_kwargs["json"]["scope"] == "myns"


def test_publish_uses_manifest_visibility(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test publish reads visibility from huitzo.yaml manifest."""
    manifest = temp_dir / "huitzo.yaml"
    manifest.write_text(
        "pack:\n  name: test-pack\n  namespace: myns\n  version: 1.0.0\n  visibility: public\n"
    )

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "test_pack-1.0.0-py3-none-any.whl").write_text("fake")

    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    create_resp = _mock_response(201, {"data": {"id": "pack-1"}})
    upload_resp = _mock_response(201, {"data": {"commands": []}})

    call_count = 0
    post_calls: list[tuple[str, dict[str, Any]]] = []

    def mock_post(url: str, **kwargs: Any) -> MagicMock:
        nonlocal call_count
        call_count += 1
        post_calls.append((url, kwargs))
        if call_count == 1:
            return create_resp
        return upload_resp

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with patch("huitzo_cli.commands.registry.httpx.post", side_effect=mock_post):
            result = cli_runner.invoke(pack_app, ["publish"])
    finally:
        os.chdir(old_cwd)

    assert result.exit_code == 0
    create_url, create_kwargs = post_calls[0]
    assert create_kwargs["json"]["visibility"] == "public"


def test_publish_defaults_visibility_to_private(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test publish defaults visibility to 'private' when not in manifest."""
    manifest = temp_dir / "huitzo.yaml"
    manifest.write_text("pack:\n  name: test-pack\n  namespace: myns\n  version: 1.0.0\n")

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "test_pack-1.0.0-py3-none-any.whl").write_text("fake")

    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    create_resp = _mock_response(201, {"data": {"id": "pack-1"}})
    upload_resp = _mock_response(201, {"data": {"commands": []}})

    call_count = 0
    post_calls: list[tuple[str, dict[str, Any]]] = []

    def mock_post(url: str, **kwargs: Any) -> MagicMock:
        nonlocal call_count
        call_count += 1
        post_calls.append((url, kwargs))
        if call_count == 1:
            return create_resp
        return upload_resp

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with patch("huitzo_cli.commands.registry.httpx.post", side_effect=mock_post):
            result = cli_runner.invoke(pack_app, ["publish"])
    finally:
        os.chdir(old_cwd)

    assert result.exit_code == 0
    create_url, create_kwargs = post_calls[0]
    assert create_kwargs["json"]["visibility"] == "private"


def test_publish_network_error(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test publish shows friendly error on network failure."""
    manifest = temp_dir / "huitzo.yaml"
    manifest.write_text("pack:\n  name: test-pack\n  namespace: myns\n  version: 1.0.0\n")

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "test-1.0.0-py3-none-any.whl").write_text("fake")

    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with patch(
            "huitzo_cli.commands.registry.httpx.post",
            side_effect=httpx.ConnectError("Connection refused"),
        ):
            result = cli_runner.invoke(pack_app, ["publish"])
    finally:
        os.chdir(old_cwd)

    assert result.exit_code == 1
    assert "Could not connect to backend" in result.stdout


def test_run_network_error(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test run shows friendly error on network failure."""
    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    with patch(
        "huitzo_cli.commands.registry.httpx.post",
        side_effect=httpx.ConnectError("Connection refused"),
    ):
        result = cli_runner.invoke(pack_app, ["run", "my-pack:hello"])

    assert result.exit_code == 1
    assert "Could not connect to backend" in result.stdout


def test_publish_409_id_lookup(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test publish: 409 on create -> GET list -> find pack -> upload succeeds."""
    manifest = temp_dir / "huitzo.yaml"
    manifest.write_text("pack:\n  name: test-pack\n  namespace: myns\n  version: 1.0.0\n")

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "test_pack-1.0.0-py3-none-any.whl").write_text("fake")

    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    create_resp = _mock_response(409, {"error": {"message": "Already exists"}})
    list_resp = _mock_response(
        200,
        {"data": [{"id": "pack-99", "scope": "myns", "name": "test-pack"}]},
    )
    upload_resp = _mock_response(201, {"data": {"commands": []}})

    call_log: list[tuple[str, str]] = []  # (method, url)

    def mock_post(url: str, **kwargs: Any) -> MagicMock:
        call_log.append(("POST", url))
        if "/versions" in url:
            return upload_resp
        return create_resp

    def mock_get(url: str, **kwargs: Any) -> MagicMock:
        call_log.append(("GET", url))
        return list_resp

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with (
            patch("huitzo_cli.commands.registry.httpx.post", side_effect=mock_post),
            patch("huitzo_cli.commands.registry.httpx.get", side_effect=mock_get),
        ):
            result = cli_runner.invoke(pack_app, ["publish"])
    finally:
        os.chdir(old_cwd)

    assert result.exit_code == 0
    assert "Published v1.0.0 successfully" in result.stdout
    # Verify the flow: POST create -> GET list -> POST upload
    assert call_log[0][0] == "POST"
    assert call_log[1][0] == "GET"
    assert call_log[2][0] == "POST"
    assert "/versions" in call_log[2][1]


def test_publish_409_id_not_found(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test publish: 409 on create -> GET list -> pack not found -> exit 1."""
    manifest = temp_dir / "huitzo.yaml"
    manifest.write_text("pack:\n  name: test-pack\n  namespace: myns\n  version: 1.0.0\n")

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "test_pack-1.0.0-py3-none-any.whl").write_text("fake")

    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    create_resp = _mock_response(409, {"error": {"message": "Already exists"}})
    # Return a list that does NOT include the pack we're looking for
    list_resp = _mock_response(
        200,
        {"data": [{"id": "pack-other", "scope": "other", "name": "other-pack"}]},
    )

    def mock_post(url: str, **kwargs: Any) -> MagicMock:
        return create_resp

    def mock_get(url: str, **kwargs: Any) -> MagicMock:
        return list_resp

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with (
            patch("huitzo_cli.commands.registry.httpx.post", side_effect=mock_post),
            patch("huitzo_cli.commands.registry.httpx.get", side_effect=mock_get),
        ):
            result = cli_runner.invoke(pack_app, ["publish"])
    finally:
        os.chdir(old_cwd)

    assert result.exit_code == 1
    assert "Could not find pack after creation" in result.stdout


def test_publish_upload_failure(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test publish: create succeeds -> upload returns 400 -> exit 1."""
    manifest = temp_dir / "huitzo.yaml"
    manifest.write_text("pack:\n  name: test-pack\n  namespace: myns\n  version: 1.0.0\n")

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "test_pack-1.0.0-py3-none-any.whl").write_text("fake")

    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    create_resp = _mock_response(201, {"data": {"id": "pack-1"}})
    upload_resp = _mock_response(400, {"error": {"message": "Invalid wheel"}})

    call_count = 0

    def mock_post(url: str, **kwargs: Any) -> MagicMock:
        nonlocal call_count
        call_count += 1
        if call_count == 1:
            return create_resp
        return upload_resp

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with patch("huitzo_cli.commands.registry.httpx.post", side_effect=mock_post):
            result = cli_runner.invoke(pack_app, ["publish"])
    finally:
        os.chdir(old_cwd)

    assert result.exit_code == 1
    assert "Upload failed" in result.stdout


def test_run_success(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test successful command execution via run."""
    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "cfg"))

    mock_resp = _mock_response(200, {"data": {"greeting": "hello world"}})

    with patch("huitzo_cli.commands.registry.httpx.post", return_value=mock_resp):
        result = cli_runner.invoke(pack_app, ["run", "my-pack:hello"])

    assert result.exit_code == 0
    assert "hello world" in result.stdout
