"""Tests for AgentKit CLI"""
import json
import os
import tempfile
from unittest.mock import patch, MagicMock, AsyncMock

import pytest
from typer.testing import CliRunner

runner = CliRunner()


class TestVersionCommand:
    def test_version_outputs_version_string(self):
        """agentkit version outputs version number"""
        from agentkit.cli.main import app
        result = runner.invoke(app, ["version"])
        assert result.exit_code == 0
        assert "0.1.0" in result.stdout or "fischer-agentkit" in result.stdout

    def test_version_help(self):
        """agentkit version --help works"""
        from agentkit.cli.main import app
        result = runner.invoke(app, ["version", "--help"])
        assert result.exit_code == 0


class TestDoctorCommand:
    def test_doctor_server_not_running(self):
        """agentkit doctor returns error when server not running"""
        from agentkit.cli.main import app
        result = runner.invoke(app, ["doctor"])
        # Should show connection error or "not running"
        assert result.exit_code != 0 or "not running" in result.stdout.lower() or "connection" in result.stdout.lower() or "error" in result.stdout.lower()

    def test_doctor_with_custom_port(self):
        """agentkit doctor --port 9000 uses custom port"""
        from agentkit.cli.main import app
        with patch("httpx.Client") as mock_client:
            result = runner.invoke(app, ["doctor", "--port", "9000"])
            # Should attempt to connect to port 9000

    def test_doctor_server_running(self):
        """agentkit doctor returns ok when server is running"""
        from agentkit.cli.main import app
        with patch("httpx.Client.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = {"status": "ok"}
            mock_response.__enter__ = MagicMock(return_value=mock_response)
            mock_response.__exit__ = MagicMock(return_value=False)
            mock_get.return_value = mock_response
            result = runner.invoke(app, ["doctor"])
            # Should show healthy status


class TestServeCommand:
    def test_serve_help(self):
        """agentkit serve --help shows options"""
        from agentkit.cli.main import app
        result = runner.invoke(app, ["serve", "--help"])
        assert result.exit_code == 0
        assert "--host" in result.stdout
        assert "--port" in result.stdout

    def test_serve_starts_uvicorn(self):
        """agentkit serve calls uvicorn.run with correct params"""
        from agentkit.cli.main import app
        with patch("uvicorn.run") as mock_run:
            result = runner.invoke(app, ["serve", "--host", "0.0.0.0", "--port", "8001"])
            mock_run.assert_called_once()
            call_kwargs = mock_run.call_args
            assert "0.0.0.0" in str(call_kwargs) or 8001 in str(call_kwargs)


class TestMainModule:
    def test_help_shows_all_commands(self):
        """agentkit --help shows all subcommands"""
        from agentkit.cli.main import app
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert "serve" in result.stdout
        assert "version" in result.stdout
        assert "doctor" in result.stdout

    def test_main_module_entry(self):
        """python -m agentkit works"""
        # Just verify the module can be imported
        import agentkit.__main__


class TestTaskCommands:
    def test_task_help(self):
        """agentkit task --help shows subcommands"""
        from agentkit.cli.main import app
        result = runner.invoke(app, ["task", "--help"])
        assert result.exit_code == 0
        assert "submit" in result.stdout
        assert "status" in result.stdout
        assert "list" in result.stdout
        assert "cancel" in result.stdout

    def test_task_submit_remote_mode(self):
        """agentkit task submit --server-url calls API"""
        from agentkit.cli.main import app
        with patch("agentkit.server.client.AgentKitClient") as mock_client_cls:
            mock_client = MagicMock()
            mock_client.submit_task = AsyncMock(return_value={"status": "completed", "output_data": {"result": "ok"}})
            mock_client_cls.return_value = mock_client
            result = runner.invoke(app, [
                "task", "submit",
                "--server-url", "http://localhost:8001",
                "--skill", "content_generator",
                "--input", '{"topic": "AI"}',
            ])
            assert result.exit_code == 0

    def test_task_submit_async_mode(self):
        """agentkit task submit --mode async returns task_id"""
        from agentkit.cli.main import app
        with patch("agentkit.server.client.AgentKitClient") as mock_client_cls:
            mock_client = MagicMock()
            mock_client.submit_task_async = AsyncMock(return_value={"task_id": "abc-123", "status": "pending"})
            mock_client_cls.return_value = mock_client
            result = runner.invoke(app, [
                "task", "submit",
                "--server-url", "http://localhost:8001",
                "--skill", "content_generator",
                "--mode", "async",
                "--input", '{"topic": "AI"}',
            ])
            assert result.exit_code == 0
            assert "abc-123" in result.stdout or "pending" in result.stdout

    def test_task_status(self):
        """agentkit task status <id> shows status"""
        from agentkit.cli.main import app
        with patch("agentkit.server.client.AgentKitClient") as mock_client_cls:
            mock_client = MagicMock()
            mock_client.get_task_status = AsyncMock(return_value={
                "task_id": "abc-123",
                "status": "completed",
                "output_data": {"result": "ok"},
            })
            mock_client_cls.return_value = mock_client
            result = runner.invoke(app, [
                "task", "status", "abc-123",
                "--server-url", "http://localhost:8001",
            ])
            assert result.exit_code == 0
            assert "completed" in result.stdout

    def test_task_list(self):
        """agentkit task list shows tasks"""
        from agentkit.cli.main import app
        with patch("agentkit.server.client.AgentKitClient") as mock_client_cls:
            mock_client = MagicMock()
            mock_client.list_tasks = AsyncMock(return_value=[
                {"task_id": "abc-123", "status": "completed", "agent_name": "test"},
            ])
            mock_client_cls.return_value = mock_client
            result = runner.invoke(app, [
                "task", "list",
                "--server-url", "http://localhost:8001",
            ])
            assert result.exit_code == 0

    def test_task_cancel(self):
        """agentkit task cancel <id> cancels task"""
        from agentkit.cli.main import app
        with patch("agentkit.server.client.AgentKitClient") as mock_client_cls:
            mock_client = MagicMock()
            mock_client.cancel_task = AsyncMock(return_value={"task_id": "abc-123", "status": "cancelled"})
            mock_client_cls.return_value = mock_client
            result = runner.invoke(app, [
                "task", "cancel", "abc-123",
                "--server-url", "http://localhost:8001",
            ])
            assert result.exit_code == 0

    def test_task_submit_input_file(self):
        """agentkit task submit --input-file reads from file"""
        from agentkit.cli.main import app
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump({"topic": "AI"}, f)
            f.flush()
            with patch("agentkit.server.client.AgentKitClient") as mock_client_cls:
                mock_client = MagicMock()
                mock_client.submit_task = AsyncMock(return_value={"status": "completed", "output_data": {}})
                mock_client_cls.return_value = mock_client
                result = runner.invoke(app, [
                    "task", "submit",
                    "--server-url", "http://localhost:8001",
                    "--skill", "content_generator",
                    "--input-file", f.name,
                ])
                assert result.exit_code == 0
        os.unlink(f.name)

    def test_task_submit_no_server_url_shows_error(self):
        """agentkit task submit without --server-url shows error"""
        from agentkit.cli.main import app
        result = runner.invoke(app, [
            "task", "submit",
            "--skill", "content_generator",
            "--input", '{"topic": "AI"}',
        ])
        # Should show error about missing server URL or local mode not available
        assert result.exit_code != 0 or "server" in result.stdout.lower() or "error" in result.stdout.lower()


class TestSkillCommands:
    def test_skill_help(self):
        """agentkit skill --help shows subcommands"""
        from agentkit.cli.main import app
        result = runner.invoke(app, ["skill", "--help"])
        assert result.exit_code == 0
        assert "list" in result.stdout
        assert "load" in result.stdout
        assert "info" in result.stdout

    def test_skill_list_remote(self):
        """agentkit skill list --server-url calls API"""
        from agentkit.cli.main import app
        with patch("httpx.Client.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = [
                {"name": "content_generator", "agent_type": "llm", "description": "Generate content"},
            ]
            mock_response.raise_for_status = MagicMock()
            mock_get.return_value = mock_response
            result = runner.invoke(app, [
                "skill", "list",
                "--server-url", "http://localhost:8001",
            ])
            assert result.exit_code == 0
            assert "content_generator" in result.stdout

    def test_skill_list_empty(self):
        """agentkit skill list with no skills shows empty message"""
        from agentkit.cli.main import app
        with patch("httpx.Client.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = []
            mock_response.raise_for_status = MagicMock()
            mock_get.return_value = mock_response
            result = runner.invoke(app, [
                "skill", "list",
                "--server-url", "http://localhost:8001",
            ])
            assert result.exit_code == 0
            assert "no skill" in result.stdout.lower() or "0" in result.stdout or "empty" in result.stdout.lower()

    def test_skill_info_remote(self):
        """agentkit skill info <name> shows skill details"""
        from agentkit.cli.main import app
        with patch("httpx.Client.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = {
                "name": "content_generator",
                "agent_type": "llm",
                "description": "Generate content",
                "version": "1.0.0",
            }
            mock_response.raise_for_status = MagicMock()
            mock_get.return_value = mock_response
            result = runner.invoke(app, [
                "skill", "info", "content_generator",
                "--server-url", "http://localhost:8001",
            ])
            assert result.exit_code == 0
            assert "content_generator" in result.stdout

    def test_skill_load_local(self):
        """agentkit skill load <path> loads a YAML skill config"""
        from agentkit.cli.main import app
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            import yaml
            yaml.dump({
                "name": "test_skill",
                "description": "A test skill",
                "agent_type": "llm",
                "task_mode": "llm_generate",
                "prompt": {"system": "You are a test assistant"},
            }, f)
            f.flush()
            result = runner.invoke(app, [
                "skill", "load", f.name,
            ])
            assert result.exit_code == 0
            assert "test_skill" in result.stdout or "loaded" in result.stdout.lower()
        os.unlink(f.name)

    def test_skill_load_invalid_file(self):
        """agentkit skill load with invalid file shows error"""
        from agentkit.cli.main import app
        result = runner.invoke(app, [
            "skill", "load", "/nonexistent/file.yaml",
        ])
        assert result.exit_code != 0 or "error" in result.stdout.lower() or "not found" in result.stdout.lower()


class TestInitCommand:
    def test_init_non_interactive(self):
        """agentkit init --non-interactive generates config files"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            result = runner.invoke(app, ["init", "--non-interactive", "--output-dir", tmpdir])
            assert result.exit_code == 0
            # Check generated files
            assert os.path.exists(os.path.join(tmpdir, "agentkit.yaml"))
            assert os.path.exists(os.path.join(tmpdir, ".env.example"))
            assert os.path.exists(os.path.join(tmpdir, "docker-compose.yaml"))
            assert os.path.exists(os.path.join(tmpdir, "skills"))

    def test_init_agentkit_yaml_content(self):
        """agentkit init generates valid agentkit.yaml"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            runner.invoke(app, ["init", "--non-interactive", "--output-dir", tmpdir])
            import yaml
            with open(os.path.join(tmpdir, "agentkit.yaml")) as f:
                config = yaml.safe_load(f)
            assert "server" in config
            assert "llm" in config
            assert config["server"]["port"] == 8001

    def test_init_env_example_content(self):
        """agentkit init generates .env.example with API key placeholders"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            runner.invoke(app, ["init", "--non-interactive", "--output-dir", tmpdir])
            with open(os.path.join(tmpdir, ".env.example")) as f:
                content = f.read()
            assert "OPENAI_API_KEY" in content or "API_KEY" in content

    def test_init_docker_compose_content(self):
        """agentkit init generates docker-compose.yaml with 3 services"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            runner.invoke(app, ["init", "--non-interactive", "--output-dir", tmpdir])
            import yaml
            with open(os.path.join(tmpdir, "docker-compose.yaml")) as f:
                compose = yaml.safe_load(f)
            services = compose.get("services", {})
            assert "agentkit" in services
            assert "redis" in services
            assert "postgres" in services

    def test_init_existing_files_no_overwrite(self):
        """agentkit init does not overwrite existing files without --force"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create existing file
            with open(os.path.join(tmpdir, "agentkit.yaml"), "w") as f:
                f.write("existing")
            result = runner.invoke(app, ["init", "--non-interactive", "--output-dir", tmpdir])
            # Should either skip or prompt
            with open(os.path.join(tmpdir, "agentkit.yaml")) as f:
                content = f.read()
            # File should still be "existing" (not overwritten) or overwritten with --force
            assert content == "existing" or "agentkit" in content.lower()


class TestUsageCommand:
    def test_usage_remote(self):
        """agentkit usage --server-url calls API"""
        from agentkit.cli.main import app
        with patch("httpx.Client.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = {
                "total_requests": 10,
                "total_tokens": 5000,
                "total_cost": 0.15,
            }
            mock_get.return_value = mock_response
            result = runner.invoke(app, [
                "usage",
                "--server-url", "http://localhost:8001",
            ])
            assert result.exit_code == 0

    def test_usage_format_json(self):
        """agentkit usage --format json outputs JSON"""
        from agentkit.cli.main import app
        with patch("httpx.Client.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = {
                "total_requests": 10,
                "total_tokens": 5000,
                "total_cost": 0.15,
            }
            mock_get.return_value = mock_response
            result = runner.invoke(app, [
                "usage",
                "--server-url", "http://localhost:8001",
                "--format", "json",
            ])
            assert result.exit_code == 0

    def test_usage_no_server(self):
        """agentkit usage without --server-url shows local usage or error"""
        from agentkit.cli.main import app
        result = runner.invoke(app, ["usage"])
        # Should either show local usage or error about missing server
        # Either is acceptable


class TestPairCommand:
    def test_pair_generates_api_key(self):
        """agentkit pair --name geo generates an API key"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            result = runner.invoke(app, [
                "pair",
                "--name", "geo-backend",
                "--config-dir", tmpdir,
            ])
            assert result.exit_code == 0
            assert "ak_live_" in result.stdout or "api_key" in result.stdout.lower()

    def test_pair_saves_client_config(self):
        """agentkit pair saves client registration to clients.yaml"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            result = runner.invoke(app, [
                "pair",
                "--name", "geo-backend",
                "--config-dir", tmpdir,
            ])
            assert result.exit_code == 0
            # Check clients.yaml was created
            import yaml
            clients_path = os.path.join(tmpdir, "clients.yaml")
            assert os.path.exists(clients_path)
            with open(clients_path) as f:
                clients = yaml.safe_load(f)
            assert "geo-backend" in clients
            assert "api_key" in clients["geo-backend"]
            assert clients["geo-backend"]["api_key"].startswith("ak_live_")

    def test_pair_shows_connection_instructions(self):
        """agentkit pair shows how to connect"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            result = runner.invoke(app, [
                "pair",
                "--name", "geo-backend",
                "--config-dir", tmpdir,
            ])
            assert result.exit_code == 0
            assert "AGENTKIT_API_KEY" in result.stdout or "AGENTKIT_SERVER_URL" in result.stdout

    def test_pair_rejects_duplicate_name(self):
        """agentkit pair rejects duplicate client name"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            # First pair
            runner.invoke(app, ["pair", "--name", "geo-backend", "--config-dir", tmpdir])
            # Second pair with same name
            result = runner.invoke(app, ["pair", "--name", "geo-backend", "--config-dir", tmpdir])
            assert result.exit_code != 0 or "already" in result.stdout.lower() or "exists" in result.stdout.lower()

    def test_pair_with_custom_skills(self):
        """agentkit pair --skills-dir registers custom skills for client"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a skills directory
            skills_dir = os.path.join(tmpdir, "custom_skills")
            os.makedirs(skills_dir)
            import yaml
            with open(os.path.join(skills_dir, "test_skill.yaml"), "w") as f:
                yaml.dump({"name": "test_skill", "description": "Test", "agent_type": "assistant", "mode": "llm_generate", "prompt": "You are a test assistant"}, f)

            result = runner.invoke(app, [
                "pair",
                "--name", "geo-backend",
                "--skills-dir", skills_dir,
                "--config-dir", tmpdir,
            ])
            assert result.exit_code == 0
            # Check client config includes skills_dir
            clients_path = os.path.join(tmpdir, "clients.yaml")
            with open(clients_path) as f:
                clients = yaml.safe_load(f)
            assert "skills_dir" in clients["geo-backend"]

    def test_pair_list(self):
        """agentkit pair --list shows all paired clients"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            # Pair two clients
            runner.invoke(app, ["pair", "--name", "geo-backend", "--config-dir", tmpdir])
            runner.invoke(app, ["pair", "--name", "another-app", "--config-dir", tmpdir])
            # List
            result = runner.invoke(app, ["pair", "--list", "--config-dir", tmpdir])
            assert result.exit_code == 0
            assert "geo-backend" in result.stdout
            assert "another-app" in result.stdout

    def test_pair_revoke(self):
        """agentkit pair --revoke removes a client"""
        from agentkit.cli.main import app
        with tempfile.TemporaryDirectory() as tmpdir:
            runner.invoke(app, ["pair", "--name", "geo-backend", "--config-dir", tmpdir])
            result = runner.invoke(app, ["pair", "--revoke", "geo-backend", "--config-dir", tmpdir])
            assert result.exit_code == 0
            # Check client is removed
            import yaml
            clients_path = os.path.join(tmpdir, "clients.yaml")
            with open(clients_path) as f:
                clients = yaml.safe_load(f)
            assert "geo-backend" not in clients
