"""Tests for the skills CLI module."""

import json
from unittest.mock import MagicMock, patch

from click.testing import CliRunner

from oclawma.skills.cli import (
    SKILL_PREFIX,
    get_installed_skills,
    get_pypi_package_info,
    search_pypi_skills,
    skill_cli,
    skills_cli,
)


class TestGetPypiPackageInfo:
    """Tests for get_pypi_package_info function."""

    @patch("oclawma.skills.cli.httpx.get")
    def test_get_package_info_success(self, mock_get):
        """Test successful package info fetch."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"info": {"name": "test-package"}}
        mock_get.return_value = mock_response

        result = get_pypi_package_info("test-package")

        assert result is not None
        assert result["info"]["name"] == "test-package"
        mock_get.assert_called_once_with(
            "https://pypi.org/pypi/test-package/json",
            timeout=10,
            headers={"Accept": "application/json"},
        )

    @patch("oclawma.skills.cli.httpx.get")
    def test_get_package_info_not_found(self, mock_get):
        """Test package not found on PyPI."""
        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_get.return_value = mock_response

        result = get_pypi_package_info("nonexistent-package")

        assert result is None

    @patch("oclawma.skills.cli.httpx.get")
    def test_get_package_info_network_error(self, mock_get):
        """Test network error handling."""
        import httpx

        mock_get.side_effect = httpx.HTTPError("Network error")

        result = get_pypi_package_info("test-package")

        assert result is None


class TestSearchPypiSkills:
    """Tests for search_pypi_skills function."""

    @patch("oclawma.skills.cli.get_pypi_package_info")
    def test_search_with_query(self, mock_get_info):
        """Test search with a query."""
        mock_get_info.return_value = {"info": {"name": "oclawma-skill-docker", "version": "1.0.0"}}

        results = search_pypi_skills("docker", limit=5)

        assert len(results) > 0
        # Should check multiple package name variations
        assert mock_get_info.call_count > 0

    @patch("oclawma.skills.cli.get_pypi_package_info")
    def test_search_no_results(self, mock_get_info):
        """Test search with no results."""
        mock_get_info.return_value = None

        results = search_pypi_skills("nonexistent")

        assert results == []


class TestGetInstalledSkills:
    """Tests for get_installed_skills function."""

    @patch("oclawma.skills.cli.subprocess.run")
    def test_get_installed_skills_success(self, mock_run):
        """Test getting installed skills."""
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=json.dumps(
                [
                    {"name": "oclawma-skill-docker", "version": "1.0.0"},
                    {"name": "other-package", "version": "2.0.0"},
                ]
            ),
        )

        with patch("oclawma.skills.cli.get_pypi_package_info") as mock_info:
            mock_info.return_value = {"info": {"name": "oclawma-skill-docker"}}
            skills = get_installed_skills()

        assert len(skills) == 1
        assert skills[0]["name"] == "oclawma-skill-docker"

    @patch("oclawma.skills.cli.subprocess.run")
    def test_get_installed_skills_no_skills(self, mock_run):
        """Test when no oclawma skills are installed."""
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=json.dumps(
                [
                    {"name": "other-package", "version": "2.0.0"},
                ]
            ),
        )

        skills = get_installed_skills()

        assert skills == []

    @patch("oclawma.skills.cli.subprocess.run")
    def test_get_installed_skills_pip_error(self, mock_run):
        """Test handling pip command error."""
        mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="error")

        skills = get_installed_skills()

        assert skills == []


class TestSkillsListCommand:
    """Tests for the skills list command."""

    def test_list_no_skills(self):
        """Test list command with no skills."""
        runner = CliRunner()

        with patch("oclawma.skills.cli.get_installed_skills") as mock_installed:
            mock_installed.return_value = []
            result = runner.invoke(skills_cli, ["list"])

        assert result.exit_code == 0
        assert (
            "No local skills found" in result.output
            or "No PyPI-installed skills found" in result.output
        )

    def test_list_json_output(self):
        """Test list command with JSON output."""
        runner = CliRunner()

        with patch("oclawma.skills.cli.get_installed_skills") as mock_installed:
            mock_installed.return_value = []
            result = runner.invoke(skills_cli, ["list", "--json"])

        assert result.exit_code == 0
        # Should be valid JSON
        data = json.loads(result.output)
        assert "local_skills" in data
        assert "pypi_skills" in data

    def test_list_installed_flag(self):
        """Test list command with --installed flag."""
        runner = CliRunner()

        with patch("oclawma.skills.cli.get_installed_skills") as mock_installed:
            mock_installed.return_value = [
                {
                    "name": "oclawma-skill-test",
                    "version": "1.0.0",
                    "pypi_info": {"info": {"summary": "Test skill"}},
                }
            ]
            result = runner.invoke(skills_cli, ["list", "--installed"])

        assert result.exit_code == 0
        assert "test" in result.output


class TestSkillsSearchCommand:
    """Tests for the skills search command."""

    @patch("oclawma.skills.cli.search_pypi_skills")
    def test_search_with_query(self, mock_search):
        """Test search command with a query."""
        mock_search.return_value = [
            {
                "info": {
                    "name": "oclawma-skill-docker",
                    "version": "1.0.0",
                    "summary": "Docker management skill",
                    "author": "Test Author",
                }
            }
        ]

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["search", "docker"])

        assert result.exit_code == 0
        assert "docker" in result.output.lower()
        mock_search.assert_called_once_with("docker", 20)

    @patch("oclawma.skills.cli.search_pypi_skills")
    def test_search_no_results(self, mock_search):
        """Test search with no results."""
        mock_search.return_value = []

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["search", "nonexistent"])

        assert result.exit_code == 0
        assert "No skills found" in result.output

    @patch("oclawma.skills.cli.search_pypi_skills")
    def test_search_json_output(self, mock_search):
        """Test search with JSON output."""
        mock_search.return_value = [
            {
                "info": {
                    "name": "oclawma-skill-test",
                    "version": "1.0.0",
                    "summary": "Test skill",
                }
            }
        ]

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["search", "test", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.output)
        assert len(data) == 1
        assert data[0]["name"] == "oclawma-skill-test"


class TestSkillsInstallCommand:
    """Tests for the skills install command."""

    @patch("oclawma.skills.cli.get_pypi_package_info")
    @patch("oclawma.skills.cli.subprocess.run")
    def test_install_skill_success(self, mock_run, mock_info):
        """Test successful skill installation."""
        mock_info.return_value = {
            "info": {"name": "oclawma-skill-docker", "version": "1.0.0", "summary": "Docker skill"}
        }
        mock_run.return_value = MagicMock(returncode=0)

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["install", "docker"])

        assert result.exit_code == 0
        assert "Installing" in result.output

    @patch("oclawma.skills.cli.get_pypi_package_info")
    def test_install_skill_not_found(self, mock_info):
        """Test installing non-existent skill."""
        mock_info.return_value = None

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["install", "nonexistent"])

        assert result.exit_code == 0
        assert "not found" in result.output.lower()

    @patch("oclawma.skills.cli.get_pypi_package_info")
    @patch("oclawma.skills.cli.subprocess.run")
    def test_install_with_version(self, mock_run, mock_info):
        """Test installing specific version."""
        mock_info.return_value = {
            "info": {"name": "oclawma-skill-docker", "version": "2.0.0", "summary": "Docker skill"}
        }
        mock_run.return_value = MagicMock(returncode=0)

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["install", "docker", "--version", "1.0.0"])

        assert result.exit_code == 0
        # Check that pip was called with version specifier
        call_args = mock_run.call_args[0][0]
        assert "docker==1.0.0" in call_args or "oclawma-skill-docker==1.0.0" in str(call_args)


class TestSkillsInfoCommand:
    """Tests for the skills info command."""

    @patch("oclawma.skills.cli.SkillRegistry")
    @patch("oclawma.skills.cli.get_pypi_package_info")
    def test_info_local_skill(self, mock_pypi, mock_registry_class):
        """Test info for local skill."""
        mock_registry = MagicMock()
        mock_registry.has_skill.return_value = True
        mock_manifest = MagicMock()
        mock_manifest.version = "1.0.0"
        mock_manifest.description = "Test skill"
        mock_manifest.author = "Test Author"
        mock_manifest.category = "test"
        mock_manifest.tags = ["test", "skill"]
        mock_manifest.entry_point = "test:TestSkill"
        mock_manifest.tools = []
        mock_manifest.requirements = []
        mock_registry.get_manifest.return_value = mock_manifest
        mock_registry_class.return_value = mock_registry

        mock_pypi.return_value = None

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["info", "test"])

        assert result.exit_code == 0
        assert "Test skill" in result.output

    @patch("oclawma.skills.cli.SkillRegistry")
    @patch("oclawma.skills.cli.get_pypi_package_info")
    def test_info_pypi_skill(self, mock_pypi, mock_registry_class):
        """Test info for PyPI skill."""
        mock_registry = MagicMock()
        mock_registry.has_skill.return_value = False
        mock_registry_class.return_value = mock_registry

        mock_pypi.return_value = {
            "info": {
                "name": "oclawma-skill-docker",
                "version": "1.0.0",
                "summary": "Docker skill",
                "author": "Test Author",
            }
        }

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["info", "docker"])

        assert result.exit_code == 0

    def test_info_skill_not_found(self):
        """Test info for non-existent skill."""
        runner = CliRunner()

        with patch("oclawma.skills.cli.SkillRegistry") as mock_registry_class:
            mock_registry = MagicMock()
            mock_registry.has_skill.return_value = False
            mock_registry_class.return_value = mock_registry

            with patch("oclawma.skills.cli.get_pypi_package_info") as mock_pypi:
                mock_pypi.return_value = None

                result = runner.invoke(skills_cli, ["info", "nonexistent"])

        assert result.exit_code == 0
        assert "not found" in result.output.lower()


class TestSkillsUninstallCommand:
    """Tests for the skills uninstall command."""

    @patch("oclawma.skills.cli.get_installed_skills")
    @patch("oclawma.skills.cli.subprocess.run")
    def test_uninstall_skill_success(self, mock_run, mock_installed):
        """Test successful skill uninstall."""
        mock_installed.return_value = [{"name": "oclawma-skill-docker", "version": "1.0.0"}]
        mock_run.return_value = MagicMock(returncode=0)

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["uninstall", "docker", "--yes"])

        assert result.exit_code == 0
        assert "Uninstalling" in result.output or "uninstalled" in result.output.lower()

    @patch("oclawma.skills.cli.get_installed_skills")
    def test_uninstall_skill_not_installed(self, mock_installed):
        """Test uninstalling skill that's not installed."""
        mock_installed.return_value = []

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["uninstall", "docker", "--yes"])

        assert result.exit_code == 0
        assert "not installed" in result.output.lower()


class TestSkillsUpdateCommand:
    """Tests for the skills update command."""

    @patch("oclawma.skills.cli.get_installed_skills")
    @patch("oclawma.skills.cli.subprocess.run")
    @patch("oclawma.skills.cli.get_pypi_package_info")
    def test_update_all_skills(self, mock_info, mock_run, mock_installed):
        """Test updating all skills."""
        mock_installed.return_value = [
            {"name": "oclawma-skill-docker", "version": "1.0.0"},
            {"name": "oclawma-skill-git", "version": "1.0.0"},
        ]
        mock_run.return_value = MagicMock(returncode=0)
        mock_info.return_value = {"info": {"version": "2.0.0"}}

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["update", "--all"])

        assert result.exit_code == 0
        assert "Updating" in result.output or "updated" in result.output.lower()

    @patch("oclawma.skills.cli.get_installed_skills")
    @patch("oclawma.skills.cli.subprocess.run")
    def test_update_single_skill(self, mock_run, mock_installed):
        """Test updating single skill."""
        mock_installed.return_value = [
            {"name": "oclawma-skill-docker", "version": "1.0.0"},
        ]
        mock_run.return_value = MagicMock(returncode=0)

        runner = CliRunner()
        result = runner.invoke(skills_cli, ["update", "docker"])

        assert result.exit_code == 0

    def test_update_no_name_or_all(self):
        """Test update without name or --all flag."""
        runner = CliRunner()
        result = runner.invoke(skills_cli, ["update"])

        assert result.exit_code == 0
        assert "specify a skill name or use --all" in result.output.lower()


class TestSkillAliasCommand:
    """Tests for the skill (singular) alias commands."""

    @patch("oclawma.skills.cli.SkillRegistry")
    @patch("oclawma.skills.cli.get_pypi_package_info")
    def test_skill_info_alias(self, mock_pypi, mock_registry_class):
        """Test skill info command (alias)."""
        mock_registry = MagicMock()
        mock_registry.has_skill.return_value = True
        mock_manifest = MagicMock()
        mock_manifest.version = "1.0.0"
        mock_manifest.description = "Test skill"
        mock_manifest.author = "Test Author"
        mock_manifest.category = "test"
        mock_manifest.tags = []
        mock_manifest.entry_point = "test:TestSkill"
        mock_manifest.tools = []
        mock_manifest.requirements = []
        mock_registry.get_manifest.return_value = mock_manifest
        mock_registry_class.return_value = mock_registry
        mock_pypi.return_value = None

        runner = CliRunner()
        result = runner.invoke(skill_cli, ["info", "test"])

        assert result.exit_code == 0
        assert "Test skill" in result.output


class TestConstants:
    """Tests for module constants."""

    def test_skill_prefix(self):
        """Test the skill prefix constant."""
        assert SKILL_PREFIX == "oclawma-skill-"
        assert "docker".startswith(SKILL_PREFIX) is False
        assert (SKILL_PREFIX + "docker").startswith(SKILL_PREFIX) is True
