"""Tests for KOAN CLI commands."""

from __future__ import annotations

import os
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch

if TYPE_CHECKING:
    from pathlib import Path

from typer.testing import CliRunner

from koan.cli import app
from koan.cli.graph import _parse_cypher_statements

runner = CliRunner()


class TestHelpOutput:
    def test_help_shows_all_commands(self) -> None:
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert "verify" in result.output
        assert "init" in result.output
        assert "mcp" in result.output
        assert "graph" in result.output

    def test_verify_help(self) -> None:
        result = runner.invoke(app, ["verify", "--help"])
        assert result.exit_code == 0
        assert "API keys" in result.output

    def test_init_help(self) -> None:
        result = runner.invoke(app, ["init", "--help"])
        assert result.exit_code == 0

    def test_mcp_help(self) -> None:
        result = runner.invoke(app, ["mcp", "--help"])
        assert result.exit_code == 0
        assert "dev" in result.output

    def test_graph_help(self) -> None:
        result = runner.invoke(app, ["graph", "--help"])
        assert result.exit_code == 0
        assert "init" in result.output


class TestVerifyCommand:
    @patch.dict(os.environ, {}, clear=True)
    def test_verify_with_no_keys(self) -> None:
        result = runner.invoke(app, ["verify"])
        assert result.exit_code == 1
        assert "Missing" in result.output

    def test_verify_with_openai_key(self) -> None:
        env = {**os.environ, "OPENAI_API_KEY": "sk-test1234"}
        env.pop("ANTHROPIC_API_KEY", None)
        result = runner.invoke(app, ["verify"], env=env)
        assert result.exit_code == 0
        assert "Found" in result.output

    def test_verify_with_anthropic_key(self) -> None:
        env = {**os.environ, "ANTHROPIC_API_KEY": "sk-ant-test1234"}
        env.pop("OPENAI_API_KEY", None)
        result = runner.invoke(app, ["verify"], env=env)
        assert result.exit_code == 0
        assert "Found" in result.output


class TestInitCommand:
    def test_init_creates_directories(self, tmp_path: Path) -> None:
        result = runner.invoke(app, ["init", str(tmp_path)])
        assert result.exit_code == 0
        assert (tmp_path / "agents").is_dir()
        assert (tmp_path / "tools").is_dir()
        assert (tmp_path / "prompts").is_dir()

    def test_init_creates_env_example(self, tmp_path: Path) -> None:
        runner.invoke(app, ["init", str(tmp_path)])
        env_file = tmp_path / ".env.example"
        assert env_file.exists()
        assert "OPENAI_API_KEY" in env_file.read_text()

    def test_init_creates_example_agent(self, tmp_path: Path) -> None:
        runner.invoke(app, ["init", str(tmp_path)])
        agent_file = tmp_path / "agents" / "example_agent.py"
        assert agent_file.exists()
        assert "ReactAgent" in agent_file.read_text()

    def test_init_creates_init_files(self, tmp_path: Path) -> None:
        runner.invoke(app, ["init", str(tmp_path)])
        assert (tmp_path / "agents" / "__init__.py").exists()
        assert (tmp_path / "tools" / "__init__.py").exists()
        assert (tmp_path / "prompts" / "__init__.py").exists()

    def test_init_idempotent(self, tmp_path: Path) -> None:
        runner.invoke(app, ["init", str(tmp_path)])
        result = runner.invoke(app, ["init", str(tmp_path)])
        assert result.exit_code == 0

    def test_init_default_current_dir(self, tmp_path: Path, monkeypatch: object) -> None:
        import pytest  # noqa: PLC0415

        monkeypatch = pytest.MonkeyPatch()  # type: ignore[assignment]
        monkeypatch.chdir(tmp_path)
        result = runner.invoke(app, ["init"])
        assert result.exit_code == 0
        assert (tmp_path / "agents").is_dir()


class TestMcpDevCommand:
    def test_mcp_dev_file_not_found(self) -> None:
        result = runner.invoke(app, ["mcp", "dev", "/nonexistent/server.py"])
        assert result.exit_code != 0

    @patch("koan.cli.mcp.subprocess.run")
    def test_mcp_dev_runs_server(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        result = runner.invoke(app, ["mcp", "dev", "my_server.py"])
        assert result.exit_code == 0
        mock_run.assert_called_once()
        args = mock_run.call_args
        assert "my_server.py" in args[0][0]

    @patch("koan.cli.mcp.subprocess.run")
    def test_mcp_dev_custom_port(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        runner.invoke(app, ["mcp", "dev", "server.py", "--port", "9090"])
        args = mock_run.call_args
        assert args[1]["env"]["MCP_DEV_PORT"] == "9090"


class TestGraphInitCommand:
    def test_parse_cypher_statements(self) -> None:
        text = """// Comment
CREATE CONSTRAINT foo IF NOT EXISTS FOR (f:Foo) REQUIRE f.id IS UNIQUE;

// Another comment
CREATE INDEX bar IF NOT EXISTS FOR (b:Bar) ON (b.name);
"""
        stmts = _parse_cypher_statements(text)
        assert len(stmts) == 2
        assert stmts[0].startswith("CREATE CONSTRAINT")
        assert not stmts[0].endswith(";")

    def test_parse_cypher_empty(self) -> None:
        stmts = _parse_cypher_statements("// only comments\n\n")
        assert stmts == []

    @patch("koan.cli.graph._SCHEMA_FILE")
    def test_graph_init_missing_schema_file(self, mock_path: MagicMock) -> None:
        mock_path.exists.return_value = False
        result = runner.invoke(app, ["graph", "init"])
        assert result.exit_code == 1

    @patch("koan.cli.graph._SCHEMA_FILE")
    def test_graph_init_runs_statements(self, mock_path: MagicMock) -> None:
        mock_path.exists.return_value = True
        mock_path.read_text.return_value = (
            "CREATE CONSTRAINT foo FOR (f:Foo) REQUIRE f.id IS UNIQUE;\n"
        )

        mock_session = MagicMock()
        mock_driver = MagicMock()
        mock_driver.session.return_value.__enter__ = MagicMock(return_value=mock_session)
        mock_driver.session.return_value.__exit__ = MagicMock(return_value=False)

        mock_neo4j = MagicMock()
        mock_neo4j.GraphDatabase.driver.return_value = mock_driver

        import sys  # noqa: PLC0415

        with patch.dict(sys.modules, {"neo4j": mock_neo4j}):
            result = runner.invoke(app, ["graph", "init", "--password", "test"])

        assert result.exit_code == 0
        mock_session.run.assert_called_once()
