"""Tests for config_fetcher module - automatic API config downloading."""

import json
from unittest.mock import Mock, patch

import httpx
import pytest

from skill_seekers.cli.config_fetcher import (
    fetch_config_from_api,
    list_available_configs,
    resolve_config_path,
)


class TestFetchConfigFromApi:
    """Tests for fetch_config_from_api function."""

    @patch("skill_seekers.cli.config_fetcher.httpx.Client")
    def test_successful_fetch(self, mock_client_class, tmp_path):
        """Test successful config download from API."""
        # Mock API responses
        mock_client = Mock()
        mock_client_class.return_value.__enter__.return_value = mock_client

        # Mock detail response
        detail_response = Mock()
        detail_response.status_code = 200
        detail_response.json.return_value = {
            "name": "react",
            "download_url": "https://api.skillseekersweb.com/api/configs/react/download",
            "category": "web-frameworks",
            "type": "unified",
        }
        detail_response.raise_for_status = Mock()

        # Mock download response
        download_response = Mock()
        download_response.json.return_value = {
            "name": "react",
            "description": "React documentation skill",
            "base_url": "https://react.dev/",
        }
        download_response.raise_for_status = Mock()

        # Setup mock to return different responses for different URLs
        def get_side_effect(url, *_args, **_kwargs):
            if "download" in url:
                return download_response
            return detail_response

        mock_client.get.side_effect = get_side_effect

        # Test fetch
        destination = str(tmp_path)
        result = fetch_config_from_api("react", destination=destination)

        # Verify
        assert result is not None
        assert result.exists()
        assert result.name == "react.json"

        # Verify file contents
        with open(result) as f:
            config = json.load(f)
        assert config["name"] == "react"
        assert "description" in config

    @patch("skill_seekers.cli.config_fetcher.httpx.Client")
    def test_config_not_found(self, mock_client_class):
        """Test handling of 404 response."""
        mock_client = Mock()
        mock_client_class.return_value.__enter__.return_value = mock_client

        # Mock 404 response
        detail_response = Mock()
        detail_response.status_code = 404
        mock_client.get.return_value = detail_response

        result = fetch_config_from_api("nonexistent")
        assert result is None

    @patch("skill_seekers.cli.config_fetcher.httpx.Client")
    def test_no_download_url(self, mock_client_class):
        """Test handling of missing download_url."""
        mock_client = Mock()
        mock_client_class.return_value.__enter__.return_value = mock_client

        # Mock response without download_url
        detail_response = Mock()
        detail_response.status_code = 200
        detail_response.json.return_value = {"name": "test"}
        detail_response.raise_for_status = Mock()
        mock_client.get.return_value = detail_response

        result = fetch_config_from_api("test")
        assert result is None

    @patch("skill_seekers.cli.config_fetcher.httpx.Client")
    def test_http_error(self, mock_client_class):
        """Test handling of HTTP errors."""
        mock_client = Mock()
        mock_client_class.return_value.__enter__.return_value = mock_client

        # Mock HTTP error
        mock_client.get.side_effect = httpx.HTTPError("Connection failed")

        result = fetch_config_from_api("react")
        assert result is None

    @patch("skill_seekers.cli.config_fetcher.httpx.Client")
    def test_json_decode_error(self, mock_client_class):
        """Test handling of invalid JSON response."""
        mock_client = Mock()
        mock_client_class.return_value.__enter__.return_value = mock_client

        # Mock response with invalid JSON
        detail_response = Mock()
        detail_response.status_code = 200
        detail_response.json.side_effect = json.JSONDecodeError("Invalid", "", 0)
        detail_response.raise_for_status = Mock()
        mock_client.get.return_value = detail_response

        result = fetch_config_from_api("react")
        assert result is None

    def test_normalize_config_name(self, tmp_path):
        """Test config name normalization (remove .json, remove configs/ prefix)."""
        with patch("skill_seekers.cli.config_fetcher.httpx.Client") as mock_client_class:
            mock_client = Mock()
            mock_client_class.return_value.__enter__.return_value = mock_client

            detail_response = Mock()
            detail_response.status_code = 200
            detail_response.json.return_value = {"download_url": "https://api.example.com/download"}
            detail_response.raise_for_status = Mock()

            download_response = Mock()
            download_response.json.return_value = {"name": "test"}
            download_response.raise_for_status = Mock()

            def get_side_effect(url, *_args, **_kwargs):
                if "download" in url:
                    return download_response
                return detail_response

            mock_client.get.side_effect = get_side_effect

            destination = str(tmp_path)

            # Test with .json extension
            result1 = fetch_config_from_api("test.json", destination=destination)
            assert result1 is not None
            assert result1.name == "test.json"

            # Test with configs/ prefix
            result2 = fetch_config_from_api("configs/test", destination=destination)
            assert result2 is not None


class TestListAvailableConfigs:
    """Tests for list_available_configs function."""

    @patch("skill_seekers.cli.config_fetcher.httpx.Client")
    def test_successful_list(self, mock_client_class):
        """Test successful config listing."""
        mock_client = Mock()
        mock_client_class.return_value.__enter__.return_value = mock_client

        # Mock API response
        response = Mock()
        response.json.return_value = {
            "configs": [
                {"name": "react"},
                {"name": "vue"},
                {"name": "godot"},
            ],
            "total": 3,
        }
        response.raise_for_status = Mock()
        mock_client.get.return_value = response

        result = list_available_configs()
        assert len(result) == 3
        assert "react" in result
        assert "vue" in result
        assert "godot" in result

    @patch("skill_seekers.cli.config_fetcher.httpx.Client")
    def test_category_filter(self, mock_client_class):
        """Test listing with category filter."""
        mock_client = Mock()
        mock_client_class.return_value.__enter__.return_value = mock_client

        response = Mock()
        response.json.return_value = {
            "configs": [{"name": "react"}, {"name": "vue"}],
            "total": 2,
        }
        response.raise_for_status = Mock()
        mock_client.get.return_value = response

        result = list_available_configs(category="web-frameworks")
        assert len(result) == 2

        # Verify category parameter was passed
        mock_client.get.assert_called_once()
        call_args = mock_client.get.call_args
        assert "params" in call_args.kwargs
        assert call_args.kwargs["params"]["category"] == "web-frameworks"

    @patch("skill_seekers.cli.config_fetcher.httpx.Client")
    def test_api_error(self, mock_client_class):
        """Test handling of API errors."""
        mock_client = Mock()
        mock_client_class.return_value.__enter__.return_value = mock_client

        # Mock error
        mock_client.get.side_effect = httpx.HTTPError("Connection failed")

        result = list_available_configs()
        assert result == []


class TestResolveConfigPath:
    """Tests for resolve_config_path function."""

    def test_exact_path_exists(self, tmp_path):
        """Test resolution when exact path exists."""
        # Create test config file
        config_file = tmp_path / "test.json"
        config_file.write_text('{"name": "test"}')

        result = resolve_config_path(str(config_file), auto_fetch=False)
        assert result is not None
        assert result.exists()
        assert result.name == "test.json"

    def test_with_configs_prefix(self, tmp_path):
        """Test resolution with configs/ prefix."""
        # Create configs directory and file
        configs_dir = tmp_path / "configs"
        configs_dir.mkdir()
        config_file = configs_dir / "test.json"
        config_file.write_text('{"name": "test"}')

        # Change to tmp_path for relative path testing
        import os

        original_cwd = os.getcwd()
        try:
            os.chdir(tmp_path)
            result = resolve_config_path("test.json", auto_fetch=False)
            assert result is not None
            assert result.exists()
            assert result.name == "test.json"
        finally:
            os.chdir(original_cwd)

    def test_auto_fetch_disabled(self):
        """Test that auto-fetch doesn't run when disabled."""
        result = resolve_config_path("nonexistent.json", auto_fetch=False)
        assert result is None

    @patch("skill_seekers.cli.config_fetcher.fetch_config_from_api")
    def test_auto_fetch_enabled(self, mock_fetch, tmp_path):
        """Test that auto-fetch runs when enabled."""
        # Use a name that does NOT exist locally (react.json exists in configs/)
        mock_config = tmp_path / "configs" / "obscure_framework.json"
        mock_config.parent.mkdir(exist_ok=True)
        mock_config.write_text('{"name": "obscure_framework"}')
        mock_fetch.return_value = mock_config

        result = resolve_config_path("obscure_framework.json", auto_fetch=True)

        # Verify fetch was called
        mock_fetch.assert_called_once_with("obscure_framework", destination="configs")
        assert result is not None
        assert result.exists()

    @patch("skill_seekers.cli.config_fetcher.fetch_config_from_api")
    def test_auto_fetch_failed(self, mock_fetch):
        """Test handling when auto-fetch fails."""
        # Mock fetch to return None (failed)
        mock_fetch.return_value = None

        result = resolve_config_path("nonexistent.json", auto_fetch=True)
        assert result is None

    def test_config_name_normalization(self, tmp_path):
        """Test various config name formats."""
        configs_dir = tmp_path / "configs"
        configs_dir.mkdir()
        config_file = configs_dir / "react.json"
        config_file.write_text('{"name": "react"}')

        import os

        original_cwd = os.getcwd()
        try:
            os.chdir(tmp_path)

            # All of these should resolve to the same file
            test_cases = ["react.json", "configs/react.json"]

            for config_name in test_cases:
                result = resolve_config_path(config_name, auto_fetch=False)
                assert result is not None, f"Failed for {config_name}"
                assert result.exists()
                assert result.name == "react.json"
        finally:
            os.chdir(original_cwd)


@pytest.mark.integration
class TestConfigFetcherIntegration:
    """Integration tests that hit real API (marked as integration)."""

    def test_fetch_real_config(self, tmp_path):
        """Test fetching a real config from API."""
        destination = str(tmp_path)
        result = fetch_config_from_api("godot", destination=destination, timeout=10.0)

        if result:  # Only assert if fetch succeeded (API might be down)
            assert result.exists()
            assert result.name == "godot.json"

            with open(result) as f:
                config = json.load(f)
            assert config["name"] == "godot"
            assert "description" in config

    def test_list_real_configs(self):
        """Test listing real configs from API."""
        result = list_available_configs(timeout=10.0)

        if result:  # Only assert if API is available
            assert len(result) > 0
            assert isinstance(result, list)
            assert all(isinstance(cfg, str) for cfg in result)
