#!/usr/bin/env python3
"""
Comprehensive tests for GuideEnhancer (C3.3 AI Enhancement)

Tests dual-mode AI enhancement for how-to guides:
- API mode (Claude API)
- LOCAL mode (Claude Code CLI)
- Auto mode detection
- All 5 enhancement methods
"""

import json
import os
from unittest.mock import Mock, patch

import pytest

from skill_seekers.cli.guide_enhancer import (
    GuideEnhancer,
    PrerequisiteItem,
    StepEnhancement,
    TroubleshootingItem,
)


class TestGuideEnhancerModeDetection:
    """Test mode detection logic"""

    def test_auto_mode_with_api_key(self):
        """Test auto mode detects API when key present and library available"""
        with (
            patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
            patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
            patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
        ):
            mock_anthropic.Anthropic = Mock()
            enhancer = GuideEnhancer(mode="auto")
            # Will be 'api' if library available, otherwise 'local' or 'none'
            assert enhancer.mode in ["api", "local", "none"]

    def test_auto_mode_without_api_key(self):
        """Test auto mode falls back to LOCAL when no API key"""
        with patch.dict(os.environ, {}, clear=True):
            if "ANTHROPIC_API_KEY" in os.environ:
                del os.environ["ANTHROPIC_API_KEY"]

            enhancer = GuideEnhancer(mode="auto")
            assert enhancer.mode in ["local", "none"]

    def test_explicit_api_mode(self):
        """Test explicit API mode"""
        enhancer = GuideEnhancer(mode="api")
        assert enhancer.mode in ["api", "none"]  # none if no API key

    def test_explicit_local_mode(self):
        """Test explicit LOCAL mode"""
        enhancer = GuideEnhancer(mode="local")
        assert enhancer.mode in ["local", "none"]  # none if no claude CLI

    def test_explicit_none_mode(self):
        """Test explicit none mode"""
        enhancer = GuideEnhancer(mode="none")
        assert enhancer.mode == "none"

    def test_claude_cli_check(self):
        """Test Claude CLI availability check"""
        enhancer = GuideEnhancer(mode="local")
        # Should either detect claude or fall back to api/none
        assert enhancer.mode in ["local", "api", "none"]


class TestGuideEnhancerStepDescriptions:
    """Test step description enhancement"""

    def test_enhance_step_descriptions_empty_list(self):
        """Test with empty steps list"""
        enhancer = GuideEnhancer(mode="none")
        steps = []
        result = enhancer.enhance_step_descriptions(steps)
        assert result == []

    def test_enhance_step_descriptions_none_mode(self):
        """Test step descriptions in none mode returns empty"""
        enhancer = GuideEnhancer(mode="none")
        steps = [
            {
                "description": "scraper.scrape(url)",
                "code": "result = scraper.scrape(url)",
            }
        ]
        result = enhancer.enhance_step_descriptions(steps)
        assert result == []

    @patch.object(GuideEnhancer, "_call_ai")
    def test_enhance_step_descriptions_api_mode(self, mock_call):
        """Test step descriptions with API mode"""
        mock_call.return_value = json.dumps(
            {
                "step_descriptions": [
                    {
                        "step_index": 0,
                        "explanation": "Initialize the scraper with the target URL",
                        "variations": ["Use async scraper for better performance"],
                    }
                ]
            }
        )

        with (
            patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
            patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
            patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
        ):
            mock_anthropic.Anthropic = Mock()
            enhancer = GuideEnhancer(mode="api")
            if enhancer.mode != "api":
                pytest.skip("API mode not available")

            enhancer.client = Mock()  # Mock the client

            steps = [
                {
                    "description": "scraper.scrape(url)",
                    "code": "result = scraper.scrape(url)",
                }
            ]
            result = enhancer.enhance_step_descriptions(steps)

            assert len(result) == 1
            assert isinstance(result[0], StepEnhancement)
            assert result[0].step_index == 0
            assert "Initialize" in result[0].explanation
            assert len(result[0].variations) == 1

    def test_enhance_step_descriptions_malformed_json(self):
        """Test handling of malformed JSON response"""
        enhancer = GuideEnhancer(mode="none")

        with patch.object(enhancer, "_call_ai", return_value="invalid json"):
            steps = [{"description": "test", "code": "code"}]
            result = enhancer.enhance_step_descriptions(steps)
            assert result == []


class TestGuideEnhancerTroubleshooting:
    """Test troubleshooting enhancement"""

    def test_enhance_troubleshooting_none_mode(self):
        """Test troubleshooting in none mode"""
        enhancer = GuideEnhancer(mode="none")
        guide_data = {
            "title": "Test Guide",
            "steps": [{"description": "test", "code": "code"}],
            "language": "python",
        }
        result = enhancer.enhance_troubleshooting(guide_data)
        assert result == []

    @patch.object(GuideEnhancer, "_call_ai")
    def test_enhance_troubleshooting_api_mode(self, mock_call):
        """Test troubleshooting with API mode"""
        mock_call.return_value = json.dumps(
            {
                "troubleshooting": [
                    {
                        "problem": "ImportError: No module named requests",
                        "symptoms": ["Import fails", "Module not found error"],
                        "diagnostic_steps": ["Check pip list", "Verify virtual env"],
                        "solution": "Run: pip install requests",
                    }
                ]
            }
        )

        with (
            patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
            patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
            patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
        ):
            mock_anthropic.Anthropic = Mock()
            enhancer = GuideEnhancer(mode="api")
            if enhancer.mode != "api":
                pytest.skip("API mode not available")

            enhancer.client = Mock()

            guide_data = {
                "title": "Test Guide",
                "steps": [{"description": "import requests", "code": "import requests"}],
                "language": "python",
            }
            result = enhancer.enhance_troubleshooting(guide_data)

            assert len(result) == 1
            assert isinstance(result[0], TroubleshootingItem)
            assert "ImportError" in result[0].problem
            assert len(result[0].symptoms) == 2
            assert len(result[0].diagnostic_steps) == 2
            assert "pip install" in result[0].solution


class TestGuideEnhancerPrerequisites:
    """Test prerequisite enhancement"""

    def test_enhance_prerequisites_empty_list(self):
        """Test with empty prerequisites"""
        enhancer = GuideEnhancer(mode="none")
        result = enhancer.enhance_prerequisites([])
        assert result == []

    def test_enhance_prerequisites_none_mode(self):
        """Test prerequisites in none mode"""
        enhancer = GuideEnhancer(mode="none")
        prereqs = ["requests", "beautifulsoup4"]
        result = enhancer.enhance_prerequisites(prereqs)
        assert result == []

    @patch.object(GuideEnhancer, "_call_ai")
    def test_enhance_prerequisites_api_mode(self, mock_call):
        """Test prerequisites with API mode"""
        mock_call.return_value = json.dumps(
            {
                "prerequisites_detailed": [
                    {
                        "name": "requests",
                        "why": "HTTP client for making web requests",
                        "setup": "pip install requests",
                    },
                    {
                        "name": "beautifulsoup4",
                        "why": "HTML/XML parser for web scraping",
                        "setup": "pip install beautifulsoup4",
                    },
                ]
            }
        )

        with (
            patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
            patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
            patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
        ):
            mock_anthropic.Anthropic = Mock()
            enhancer = GuideEnhancer(mode="api")
            if enhancer.mode != "api":
                pytest.skip("API mode not available")

            enhancer.client = Mock()

            prereqs = ["requests", "beautifulsoup4"]
            result = enhancer.enhance_prerequisites(prereqs)

            assert len(result) == 2
            assert isinstance(result[0], PrerequisiteItem)
            assert result[0].name == "requests"
            assert "HTTP client" in result[0].why
            assert "pip install" in result[0].setup


class TestGuideEnhancerNextSteps:
    """Test next steps enhancement"""

    def test_enhance_next_steps_none_mode(self):
        """Test next steps in none mode"""
        enhancer = GuideEnhancer(mode="none")
        guide_data = {"title": "Test Guide", "description": "Test"}
        result = enhancer.enhance_next_steps(guide_data)
        assert result == []

    @patch.object(GuideEnhancer, "_call_ai")
    def test_enhance_next_steps_api_mode(self, mock_call):
        """Test next steps with API mode"""
        mock_call.return_value = json.dumps(
            {
                "next_steps": [
                    "How to handle async workflows",
                    "How to add error handling",
                    "How to implement caching",
                ]
            }
        )

        with (
            patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
            patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
            patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
        ):
            mock_anthropic.Anthropic = Mock()
            enhancer = GuideEnhancer(mode="api")
            if enhancer.mode != "api":
                pytest.skip("API mode not available")

            enhancer.client = Mock()

            guide_data = {
                "title": "How to Scrape Docs",
                "description": "Basic scraping",
            }
            result = enhancer.enhance_next_steps(guide_data)

            assert len(result) == 3
            assert "async" in result[0].lower()
            assert "error" in result[1].lower()


class TestGuideEnhancerUseCases:
    """Test use case enhancement"""

    def test_enhance_use_cases_none_mode(self):
        """Test use cases in none mode"""
        enhancer = GuideEnhancer(mode="none")
        guide_data = {"title": "Test Guide", "description": "Test"}
        result = enhancer.enhance_use_cases(guide_data)
        assert result == []

    @patch.object(GuideEnhancer, "_call_ai")
    def test_enhance_use_cases_api_mode(self, mock_call):
        """Test use cases with API mode"""
        mock_call.return_value = json.dumps(
            {
                "use_cases": [
                    "Use when you need to automate documentation extraction",
                    "Ideal for building knowledge bases from technical docs",
                ]
            }
        )

        with (
            patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
            patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
            patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
        ):
            mock_anthropic.Anthropic = Mock()
            enhancer = GuideEnhancer(mode="api")
            if enhancer.mode != "api":
                pytest.skip("API mode not available")

            enhancer.client = Mock()

            guide_data = {
                "title": "How to Scrape Docs",
                "description": "Documentation scraping",
            }
            result = enhancer.enhance_use_cases(guide_data)

            assert len(result) == 2
            assert "automate" in result[0].lower()
            assert "knowledge base" in result[1].lower()


class TestGuideEnhancerFullWorkflow:
    """Test complete guide enhancement workflow"""

    def test_enhance_guide_none_mode(self):
        """Test full guide enhancement in none mode"""
        enhancer = GuideEnhancer(mode="none")

        guide_data = {
            "title": "How to Scrape Documentation",
            "steps": [
                {"description": "Import libraries", "code": "import requests"},
                {"description": "Create scraper", "code": "scraper = Scraper()"},
            ],
            "language": "python",
            "prerequisites": ["requests"],
            "description": "Basic scraping guide",
        }

        result = enhancer.enhance_guide(guide_data)

        # In none mode, should return original guide
        assert result["title"] == guide_data["title"]
        assert len(result["steps"]) == 2

    @patch.object(GuideEnhancer, "_call_ai")
    def test_enhance_guide_api_mode_success(self, mock_call):
        """Test successful full guide enhancement via API"""
        mock_call.return_value = json.dumps(
            {
                "step_descriptions": [
                    {
                        "step_index": 0,
                        "explanation": "Import required libraries",
                        "variations": [],
                    },
                    {
                        "step_index": 1,
                        "explanation": "Initialize scraper instance",
                        "variations": [],
                    },
                ],
                "troubleshooting": [
                    {
                        "problem": "Import error",
                        "symptoms": ["Module not found"],
                        "diagnostic_steps": ["Check installation"],
                        "solution": "pip install requests",
                    }
                ],
                "prerequisites_detailed": [
                    {
                        "name": "requests",
                        "why": "HTTP client",
                        "setup": "pip install requests",
                    }
                ],
                "next_steps": ["How to add authentication"],
                "use_cases": ["Automate documentation extraction"],
            }
        )

        with (
            patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
            patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
            patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
        ):
            mock_anthropic.Anthropic = Mock()
            enhancer = GuideEnhancer(mode="api")
            if enhancer.mode != "api":
                pytest.skip("API mode not available")

            enhancer.client = Mock()

            guide_data = {
                "title": "How to Scrape Documentation",
                "steps": [
                    {"description": "Import libraries", "code": "import requests"},
                    {"description": "Create scraper", "code": "scraper = Scraper()"},
                ],
                "language": "python",
                "prerequisites": ["requests"],
                "description": "Basic scraping guide",
            }

            result = enhancer.enhance_guide(guide_data)

            # Check enhancements were applied
            assert "step_enhancements" in result
            assert "troubleshooting_detailed" in result
            assert "prerequisites_detailed" in result
            assert "next_steps_detailed" in result
            assert "use_cases" in result

    def test_enhance_guide_error_fallback(self):
        """Test graceful fallback on enhancement error"""
        enhancer = GuideEnhancer(mode="none")

        with patch.object(enhancer, "enhance_guide", side_effect=Exception("API error")):
            guide_data = {
                "title": "Test",
                "steps": [],
                "language": "python",
                "prerequisites": [],
                "description": "Test",
            }

            # Should not raise exception - graceful fallback
            try:
                enhancer = GuideEnhancer(mode="none")
                result = enhancer.enhance_guide(guide_data)
                # In none mode with error, returns original
                assert result["title"] == guide_data["title"]
            except Exception:
                pytest.fail("Should handle errors gracefully")


class TestGuideEnhancerLocalMode:
    """Test LOCAL mode (Claude Code CLI)"""

    @patch.object(GuideEnhancer, "_call_ai")
    def test_call_ai_local_success(self, mock_call_ai):
        """Test successful LOCAL mode call via AgentClient"""
        mock_call_ai.return_value = json.dumps(
            {
                "step_descriptions": [],
                "troubleshooting": [],
                "prerequisites_detailed": [],
                "next_steps": [],
                "use_cases": [],
            }
        )

        enhancer = GuideEnhancer(mode="local")
        prompt = "Test prompt"
        result = enhancer._call_ai(prompt)

        assert result is not None
        assert mock_call_ai.called

    @patch.object(GuideEnhancer, "_call_ai")
    def test_call_ai_local_timeout(self, mock_call_ai):
        """Test LOCAL mode timeout handling via AgentClient"""
        mock_call_ai.return_value = None

        enhancer = GuideEnhancer(mode="local")
        prompt = "Test prompt"
        result = enhancer._call_ai(prompt)

        assert result is None


class TestGuideEnhancerPromptGeneration:
    """Test prompt generation"""

    def test_create_enhancement_prompt(self):
        """Test comprehensive enhancement prompt generation"""
        enhancer = GuideEnhancer(mode="none")

        guide_data = {
            "title": "How to Test",
            "steps": [{"description": "Write test", "code": "def test_example(): pass"}],
            "language": "python",
            "prerequisites": ["pytest"],
        }

        prompt = enhancer._create_enhancement_prompt(guide_data)

        assert "How to Test" in prompt
        assert "pytest" in prompt
        assert "STEP_DESCRIPTIONS" in prompt
        assert "TROUBLESHOOTING" in prompt
        assert "PREREQUISITES" in prompt
        assert "NEXT_STEPS" in prompt
        assert "USE_CASES" in prompt
        assert "JSON" in prompt

    def test_format_steps_for_prompt(self):
        """Test step formatting for prompts"""
        enhancer = GuideEnhancer(mode="none")

        steps = [
            {"description": "Import", "code": "import requests"},
            {"description": "Create", "code": "obj = Object()"},
        ]

        formatted = enhancer._format_steps_for_prompt(steps)

        assert "Step 1" in formatted
        assert "Step 2" in formatted
        assert "import requests" in formatted
        assert "obj = Object()" in formatted

    def test_format_steps_empty(self):
        """Test formatting empty steps list"""
        enhancer = GuideEnhancer(mode="none")
        formatted = enhancer._format_steps_for_prompt([])
        assert formatted == "No steps provided"


class TestGuideEnhancerResponseParsing:
    """Test response parsing"""

    def test_parse_enhancement_response_valid_json(self):
        """Test parsing valid JSON response"""
        enhancer = GuideEnhancer(mode="none")

        response = json.dumps(
            {
                "step_descriptions": [{"step_index": 0, "explanation": "Test", "variations": []}],
                "troubleshooting": [],
                "prerequisites_detailed": [],
                "next_steps": [],
                "use_cases": [],
            }
        )

        guide_data = {
            "title": "Test",
            "steps": [{"description": "Test", "code": "test"}],
            "language": "python",
        }

        result = enhancer._parse_enhancement_response(response, guide_data)

        assert "step_enhancements" in result
        assert len(result["step_enhancements"]) == 1

    def test_parse_enhancement_response_with_extra_text(self):
        """Test parsing JSON embedded in text"""
        enhancer = GuideEnhancer(mode="none")

        json_data = {
            "step_descriptions": [],
            "troubleshooting": [],
            "prerequisites_detailed": [],
            "next_steps": [],
            "use_cases": [],
        }

        response = f"Here's the result:\n{json.dumps(json_data)}\nDone!"

        guide_data = {"title": "Test", "steps": [], "language": "python"}
        result = enhancer._parse_enhancement_response(response, guide_data)

        # Should extract JSON successfully
        assert "title" in result

    def test_parse_enhancement_response_invalid_json(self):
        """Test handling invalid JSON"""
        enhancer = GuideEnhancer(mode="none")

        response = "This is not valid JSON"
        guide_data = {"title": "Test", "steps": [], "language": "python"}

        result = enhancer._parse_enhancement_response(response, guide_data)

        # Should return original guide_data on parse error
        assert result["title"] == "Test"


class TestEnhanceViaLocalRegression:
    """Regression for CBB-01: _enhance_via_local must call the real _call_ai.

    The bug called a non-existent _call_claude_local; the AttributeError was
    swallowed by enhance_guide's broad except, so LOCAL-mode enhancement
    silently returned the guide unenhanced.
    """

    def test_enhance_via_local_routes_through_call_ai(self):
        enhancer = GuideEnhancer(mode="none")
        guide = {"title": "X", "steps": [], "language": "python"}
        with patch.object(GuideEnhancer, "_call_ai", return_value="") as mock_ai:
            result = enhancer._enhance_via_local(guide)
        mock_ai.assert_called_once()
        # Falsy AI response → original guide returned unchanged (no AttributeError).
        assert result == guide

    def test_call_claude_local_method_does_not_exist(self):
        assert not hasattr(GuideEnhancer, "_call_claude_local")


if __name__ == "__main__":
    pytest.main([__file__, "-v"])


class TestNoEnhancerNameCollisions:
    """unified_enhancer must not re-introduce same-named shadow classes for
    the canonical per-feature enhancers (they had zero importers but shadowed
    guide_enhancer.GuideEnhancer, ai_enhancer.PatternEnhancer, etc.)."""

    def test_unified_enhancer_defines_no_colliding_names(self):
        import skill_seekers.cli.unified_enhancer as ue

        for name in ("PatternEnhancer", "TestExampleEnhancer", "GuideEnhancer", "ConfigEnhancer"):
            assert not hasattr(ue, name), (
                f"unified_enhancer defines {name}, colliding with the canonical "
                f"class of the same name in its own module"
            )

    def test_canonical_classes_importable(self):
        from skill_seekers.cli.ai_enhancer import PatternEnhancer, TestExampleEnhancer  # noqa: F401
        from skill_seekers.cli.guide_enhancer import GuideEnhancer  # noqa: F401
