"""Tests for brokr agent and grant CLI commands."""

from pathlib import Path

import typer
from typer.testing import CliRunner

from brokr.cli_agent import agent_app, grant_cmd, revoke_cmd
from brokr.store import load_store, save_store

# Standalone test app
test_app = typer.Typer()
test_app.add_typer(agent_app, name="agent")
test_app.command("grant")(grant_cmd)
test_app.command("revoke")(revoke_cmd)

runner = CliRunner()
PASSPHRASE = "test"


def _make_store(
    store_dir: Path,
    secrets: dict | None = None,
    projects: dict | None = None,
):
    """Create a store with secrets and projects."""
    data = {
        "secrets": secrets or {"API_KEY": "sk-123", "DB_URL": "postgres://x", "TOKEN": "tok-456"},
    }
    if projects is not None:
        data["projects"] = projects
    else:
        data["projects"] = {
            "my-app": {
                "secrets": ["API_KEY", "DB_URL"],
                "agents": {"existing-bot": ["API_KEY"]},
            }
        }
    save_store(data, PASSPHRASE, store_dir=store_dir)
    return data


def _setup_project(tmp_path, monkeypatch, project_name="my-app"):
    """Set env vars and create .brokr file."""
    monkeypatch.setenv("BROKR_DIR", str(tmp_path))
    monkeypatch.setenv("BROKR_PASSPHRASE", PASSPHRASE)
    (tmp_path / ".brokr").write_text(f"{project_name}\n")
    monkeypatch.chdir(tmp_path)


class TestAgentAdd:
    def test_creates_agent(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["agent", "add", "deploy-bot"])
        assert result.exit_code == 0
        assert "deploy-bot" in result.stderr

        data = load_store(PASSPHRASE, store_dir=tmp_path)
        assert "deploy-bot" in data["projects"]["my-app"]["agents"]
        assert data["projects"]["my-app"]["agents"]["deploy-bot"] == []

    def test_duplicate_agent_errors(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["agent", "add", "existing-bot"])
        assert result.exit_code == 1
        assert "already exists" in result.stderr

    def test_invalid_name_rejected(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["agent", "add", "BadName"])
        assert result.exit_code != 0

    def test_hyphenated_name_ok(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["agent", "add", "my-deploy-bot"])
        assert result.exit_code == 0

    def test_no_project_context(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        monkeypatch.setenv("BROKR_PASSPHRASE", PASSPHRASE)
        no_project = tmp_path / "noproj"
        no_project.mkdir()
        monkeypatch.chdir(no_project)

        result = runner.invoke(test_app, ["agent", "add", "bot"])
        assert result.exit_code == 1
        assert "No project context" in result.stderr


class TestAgentRm:
    def test_removes_agent(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["agent", "rm", "existing-bot"])
        assert result.exit_code == 0

        data = load_store(PASSPHRASE, store_dir=tmp_path)
        assert "existing-bot" not in data["projects"]["my-app"]["agents"]

    def test_unknown_agent_errors(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["agent", "rm", "ghost"])
        assert result.exit_code == 1
        assert "not found" in result.stderr


class TestAgentList:
    def test_lists_agents(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["agent", "list"])
        assert result.exit_code == 0
        assert "existing-bot" in result.stderr

    def test_empty_agents(self, tmp_path, monkeypatch):
        _make_store(
            tmp_path,
            projects={"my-app": {"secrets": ["API_KEY"], "agents": {}}},
        )
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["agent", "list"])
        assert "No agents" in result.stderr


class TestGrant:
    def test_grants_secret(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["grant", "existing-bot", "DB_URL"])
        assert result.exit_code == 0
        assert "Granted" in result.stderr

        data = load_store(PASSPHRASE, store_dir=tmp_path)
        assert "DB_URL" in data["projects"]["my-app"]["agents"]["existing-bot"]

    def test_secret_not_in_project(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        # TOKEN is in store but NOT linked to project
        result = runner.invoke(test_app, ["grant", "existing-bot", "TOKEN"])
        assert result.exit_code == 1
        assert "not linked to project" in result.stderr

    def test_agent_not_found(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["grant", "ghost", "API_KEY"])
        assert result.exit_code == 1
        assert "not found" in result.stderr

    def test_already_granted_warns(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        # API_KEY already granted to existing-bot
        result = runner.invoke(test_app, ["grant", "existing-bot", "API_KEY"])
        assert result.exit_code == 0
        assert "already granted" in result.stderr

    def test_invalid_secret_name(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["grant", "existing-bot", "bad-name"])
        assert result.exit_code != 0


class TestRevoke:
    def test_revokes_secret(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["revoke", "existing-bot", "API_KEY"])
        assert result.exit_code == 0
        assert "Revoked" in result.stderr

        data = load_store(PASSPHRASE, store_dir=tmp_path)
        assert "API_KEY" not in data["projects"]["my-app"]["agents"]["existing-bot"]

    def test_not_granted_errors(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["revoke", "existing-bot", "DB_URL"])
        assert result.exit_code == 1
        assert "not granted" in result.stderr

    def test_agent_not_found(self, tmp_path, monkeypatch):
        _make_store(tmp_path)
        _setup_project(tmp_path, monkeypatch)

        result = runner.invoke(test_app, ["revoke", "ghost", "API_KEY"])
        assert result.exit_code == 1
        assert "not found" in result.stderr
