"""Tests for configuration wiring to components."""

from pathlib import Path

from mirdan.config import (
    EnhancementConfig,
    MirdanConfig,
    OrchestrationConfig,
    ProjectConfig,
    QualityConfig,
    ThresholdsConfig,
)
from mirdan.core.intent_analyzer import IntentAnalyzer
from mirdan.core.orchestrator import ToolAdvisor
from mirdan.core.prompt_composer import PromptComposer
from mirdan.core.quality_standards import QualityStandards
from mirdan.models import ContextBundle, Intent, TaskType


class TestVersionConsistency:
    """Tests that version metadata is consistent across the project."""

    def test_version_matches_pyproject(self) -> None:
        """mirdan.__version__ must match the dynamically resolved version.

        pyproject.toml uses dynamic versioning via hatchling, reading from
        src/mirdan/__init__.py.  This test verifies the __version__ string
        exists and follows semantic versioning.
        """
        import re

        import mirdan

        # Verify version is a valid semver string
        assert re.match(r"^\d+\.\d+\.\d+", mirdan.__version__), (
            f"mirdan.__version__ ({mirdan.__version__!r}) does not follow "
            f"semantic versioning (e.g., '0.1.0', '1.0.0-beta.1')"
        )

        # Verify pyproject.toml declares dynamic versioning
        import tomllib

        pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
        with pyproject_path.open("rb") as f:
            pyproject = tomllib.load(f)

        assert "version" in pyproject["project"].get("dynamic", []), (
            "pyproject.toml should use dynamic versioning: dynamic = ['version']"
        )


class TestIntentAnalyzerConfigWiring:
    """Tests for IntentAnalyzer configuration wiring."""

    def test_uses_project_language_when_detection_empty(self) -> None:
        """Should use project config language when detection returns None."""
        config = ProjectConfig(primary_language="python")
        analyzer = IntentAnalyzer(config)

        # Prompt with no language indicators
        intent = analyzer.analyze("add a button to the page")

        assert intent.primary_language == "python"

    def test_uses_project_frameworks_when_detection_empty(self) -> None:
        """Should use project config frameworks when detection returns empty."""
        config = ProjectConfig(frameworks=["fastapi", "pydantic"])
        analyzer = IntentAnalyzer(config)

        # Prompt with no framework indicators
        intent = analyzer.analyze("add validation to the endpoint")

        assert "fastapi" in intent.frameworks
        assert "pydantic" in intent.frameworks

    def test_detection_overrides_project_config(self) -> None:
        """Detected language/frameworks should override project config."""
        config = ProjectConfig(primary_language="python", frameworks=["django"])
        analyzer = IntentAnalyzer(config)

        # Prompt with explicit React/TypeScript indicators
        intent = analyzer.analyze("create a React component with TypeScript")

        # Detection should override config
        assert intent.primary_language in ["javascript", "typescript"]
        assert "react" in intent.frameworks

    def test_works_without_config(self) -> None:
        """Should work normally when no config provided (backward compatible)."""
        analyzer = IntentAnalyzer()  # No config

        intent = analyzer.analyze("add a button")

        # Should work, language will be None since no detection or config
        assert intent.primary_language is None


class TestQualityStandardsConfigWiring:
    """Tests for QualityStandards configuration wiring."""

    def test_strict_returns_more_standards(self) -> None:
        """Strict mode should return more standards."""
        strict_config = QualityConfig(security="strict", architecture="strict")
        permissive_config = QualityConfig(security="permissive", architecture="permissive")

        strict_standards = QualityStandards(config=strict_config)
        permissive_standards = QualityStandards(config=permissive_config)

        # Create a security-related intent
        intent = Intent(
            original_prompt="implement authentication",
            task_type=TaskType.GENERATION,
            touches_security=True,
        )

        strict_result = strict_standards.render_for_intent(intent)
        permissive_result = permissive_standards.render_for_intent(intent)

        assert len(strict_result) > len(permissive_result)

    def test_moderate_is_default(self) -> None:
        """No config should behave as moderate (backward compatible)."""
        no_config = QualityStandards()  # No config
        moderate_config = QualityStandards(config=QualityConfig())  # Default config

        intent = Intent(
            original_prompt="add a feature",
            task_type=TaskType.GENERATION,
        )

        no_config_result = no_config.render_for_intent(intent)
        moderate_result = moderate_config.render_for_intent(intent)

        # Results should be similar (both moderate behavior)
        assert len(no_config_result) == len(moderate_result)

    def test_works_without_config(self) -> None:
        """Should work normally when no config provided."""
        standards = QualityStandards()

        intent = Intent(
            original_prompt="fix the bug",
            task_type=TaskType.DEBUG,
            primary_language="python",
        )

        result = standards.render_for_intent(intent)

        assert len(result) > 0


class TestQualityStandardsFrameworkConfig:
    """Tests for framework stringency configuration wiring."""

    def test_framework_stringency_affects_count(self) -> None:
        """Framework stringency should control number of framework standards."""
        strict_config = QualityConfig(framework="strict")
        permissive_config = QualityConfig(framework="permissive")

        strict_standards = QualityStandards(config=strict_config)
        permissive_standards = QualityStandards(config=permissive_config)

        intent = Intent(
            original_prompt="create a React component",
            task_type=TaskType.GENERATION,
            frameworks=["react"],
        )

        strict_result = strict_standards.render_for_intent(intent)
        permissive_result = permissive_standards.render_for_intent(intent)

        assert len(strict_result) > len(permissive_result)

    def test_framework_default_is_moderate(self) -> None:
        """Default framework stringency should be moderate."""
        config = QualityConfig()

        assert config.framework == "moderate"


class TestKnowledgeGraphStandardsTrigger:
    """Tests for knowledge graph standards triggered by touches_knowledge_graph."""

    def test_kg_flag_triggers_kg_standards(self) -> None:
        """touches_knowledge_graph=True should include KG standards."""
        standards = QualityStandards()
        intent = Intent(
            original_prompt="build a knowledge graph",
            task_type=TaskType.GENERATION,
            touches_knowledge_graph=True,
        )
        result = standards.render_for_intent(intent)
        result_text = " ".join(result).lower()
        assert "graph" in result_text or "entity" in result_text or "provenance" in result_text

    def test_kg_flag_false_skips_kg_standards(self) -> None:
        """touches_knowledge_graph=False should not include KG standards."""
        standards = QualityStandards()
        intent_with_kg = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            touches_knowledge_graph=True,
        )
        intent_without_kg = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            touches_knowledge_graph=False,
        )
        with_kg = standards.render_for_intent(intent_with_kg)
        without_kg = standards.render_for_intent(intent_without_kg)
        assert len(with_kg) > len(without_kg)

    def test_kg_independent_of_neo4j_framework(self) -> None:
        """KG standards should trigger on flag, not framework name."""
        standards = QualityStandards()
        # KG flag set but no neo4j framework
        intent = Intent(
            original_prompt="build a weaviate knowledge graph",
            task_type=TaskType.GENERATION,
            frameworks=["weaviate"],
            touches_knowledge_graph=True,
        )
        result = standards.render_for_intent(intent)
        result_text = " ".join(result).lower()
        assert "graph" in result_text or "entity" in result_text or "provenance" in result_text


class TestPromptComposerConfigWiring:
    """Tests for PromptComposer configuration wiring."""

    def test_include_verification_false_skips_section(self) -> None:
        """Should skip verification section when include_verification is False."""
        config = EnhancementConfig(include_verification=False)
        standards = QualityStandards()
        composer = PromptComposer(standards, config=config)

        intent = Intent(
            original_prompt="add a feature",
            task_type=TaskType.GENERATION,
        )
        context = ContextBundle()

        enhanced = composer.compose(intent, context, [])

        assert "Before Completing" not in enhanced.enhanced_text

    def test_include_tool_hints_false_skips_section(self) -> None:
        """Should skip tool hints section when include_tool_hints is False."""
        from mirdan.models import ToolRecommendation

        config = EnhancementConfig(include_tool_hints=False)
        standards = QualityStandards()
        composer = PromptComposer(standards, config=config)

        intent = Intent(
            original_prompt="add a feature",
            task_type=TaskType.GENERATION,
        )
        context = ContextBundle()
        recommendations = [ToolRecommendation(mcp="context7", action="fetch docs", reason="test")]

        enhanced = composer.compose(intent, context, recommendations)

        assert "Recommended Tools" not in enhanced.enhanced_text

    def test_minimal_verbosity_reduces_output(self) -> None:
        """Minimal verbosity should produce shorter output."""
        minimal_config = EnhancementConfig(verbosity="minimal")
        comprehensive_config = EnhancementConfig(verbosity="comprehensive")

        standards = QualityStandards()
        minimal_composer = PromptComposer(standards, config=minimal_config)
        comprehensive_composer = PromptComposer(standards, config=comprehensive_config)

        intent = Intent(
            original_prompt="implement user authentication with proper validation",
            task_type=TaskType.GENERATION,
            primary_language="python",
            touches_security=True,
        )
        context = ContextBundle()

        minimal_enhanced = minimal_composer.compose(intent, context, [])
        comprehensive_enhanced = comprehensive_composer.compose(intent, context, [])

        # Minimal should be shorter
        assert len(minimal_enhanced.enhanced_text) < len(comprehensive_enhanced.enhanced_text)

    def test_balanced_is_default(self) -> None:
        """No config should behave as balanced (backward compatible)."""
        standards = QualityStandards()
        no_config_composer = PromptComposer(standards)
        balanced_composer = PromptComposer(standards, config=EnhancementConfig())

        intent = Intent(
            original_prompt="add feature",
            task_type=TaskType.GENERATION,
        )
        context = ContextBundle()

        no_config_result = no_config_composer.compose(intent, context, [])
        balanced_result = balanced_composer.compose(intent, context, [])

        # Should produce similar output
        assert len(no_config_result.enhanced_text) == len(balanced_result.enhanced_text)


class TestToolAdvisorConfigWiring:
    """Tests for ToolAdvisor configuration wiring."""

    def test_prefer_mcps_reorders_recommendations(self) -> None:
        """Should reorder recommendations based on prefer_mcps."""
        # Prefer filesystem first, then enyal
        config = OrchestrationConfig(prefer_mcps=["filesystem", "enyal"])
        orchestrator = ToolAdvisor(config)

        intent = Intent(
            original_prompt="create a React component",
            task_type=TaskType.GENERATION,
            frameworks=["react"],
            uses_external_framework=True,
        )

        recommendations = orchestrator.suggest_tools(intent)

        # Find positions of filesystem and enyal recommendations
        mcp_order = [r.mcp for r in recommendations]

        # filesystem should appear before context7 (if both present)
        if "filesystem" in mcp_order and "context7" in mcp_order:
            assert mcp_order.index("filesystem") < mcp_order.index("context7")

    def test_works_without_config(self) -> None:
        """Should work normally when no config provided (backward compatible)."""
        orchestrator = ToolAdvisor()  # No config

        intent = Intent(
            original_prompt="add authentication",
            task_type=TaskType.GENERATION,
        )

        recommendations = orchestrator.suggest_tools(intent)

        # Should return recommendations
        assert len(recommendations) >= 0  # May be empty depending on intent


class TestServerIntegration:
    """Integration tests for full config wiring."""

    def test_all_components_work_with_default_config(self) -> None:
        """All components should work with default MirdanConfig."""
        config = MirdanConfig()

        # Initialize all components as server.py does
        intent_analyzer = IntentAnalyzer(config.project)
        quality_standards = QualityStandards(config=config.quality)
        prompt_composer = PromptComposer(quality_standards, config=config.enhancement)
        mcp_orchestrator = ToolAdvisor(config.orchestration)

        # Run through the flow
        prompt = "add user authentication with JWT"
        intent = intent_analyzer.analyze(prompt)
        recommendations = mcp_orchestrator.suggest_tools(intent)
        context = ContextBundle()
        enhanced = prompt_composer.compose(intent, context, recommendations)

        # Should complete without error
        assert enhanced.enhanced_text is not None
        assert len(enhanced.enhanced_text) > len(prompt)

    def test_custom_config_affects_all_components(self) -> None:
        """Custom config values should propagate to all components."""
        config = MirdanConfig()
        config.project = ProjectConfig(primary_language="python", frameworks=["fastapi"])
        config.quality = QualityConfig(security="strict")
        config.enhancement = EnhancementConfig(verbosity="comprehensive", include_verification=True)
        config.orchestration = OrchestrationConfig(prefer_mcps=["enyal", "filesystem"])

        intent_analyzer = IntentAnalyzer(config.project)
        quality_standards = QualityStandards(config=config.quality)
        PromptComposer(quality_standards, config=config.enhancement)
        mcp_orchestrator = ToolAdvisor(config.orchestration)

        # Analyze a generic prompt - should get python/fastapi from config
        intent = intent_analyzer.analyze("add validation to the endpoint")

        # Project config should provide defaults
        assert intent.primary_language == "python"

        # Get recommendations and verify ordering
        recommendations = mcp_orchestrator.suggest_tools(intent)
        if len(recommendations) >= 2:
            # enyal should be preferred
            mcp_names = [r.mcp for r in recommendations]
            if "enyal" in mcp_names:
                # Enyal should appear early due to preference
                enyal_idx = mcp_names.index("enyal")
                assert enyal_idx <= 1  # Should be first or second


class TestNewConfigFields:
    """Tests for new config fields added in P0/P1 fix."""

    def test_thresholds_arch_defaults(self) -> None:
        """Architecture threshold fields should have correct defaults."""
        t = ThresholdsConfig()
        assert t.arch_max_function_length == 30
        assert t.arch_max_file_length == 300
        assert t.arch_max_nesting_depth == 4
        assert t.arch_max_class_methods == 10

    def test_thresholds_arch_custom_values(self) -> None:
        """Architecture thresholds should accept custom values."""
        t = ThresholdsConfig(
            arch_max_function_length=50,
            arch_max_file_length=500,
            arch_max_nesting_depth=6,
            arch_max_class_methods=15,
        )
        assert t.arch_max_function_length == 50
        assert t.arch_max_file_length == 500
        assert t.arch_max_nesting_depth == 6
        assert t.arch_max_class_methods == 15

    def test_quality_config_language_default(self) -> None:
        """QualityConfig.language should default to moderate."""
        q = QualityConfig()
        assert q.language == "moderate"

    def test_quality_config_language_accepts_valid(self) -> None:
        """QualityConfig.language should accept strict/moderate/permissive."""
        for level in ("strict", "moderate", "permissive"):
            q = QualityConfig(language=level)
            assert q.language == level

    def test_project_config_github_defaults(self) -> None:
        """ProjectConfig github fields should default to empty."""
        p = ProjectConfig()
        assert p.github_owner == ""
        assert p.github_repo == ""

    def test_project_config_github_custom(self) -> None:
        """ProjectConfig github fields should accept values."""
        p = ProjectConfig(github_owner="myorg", github_repo="myrepo")
        assert p.github_owner == "myorg"
        assert p.github_repo == "myrepo"

    def test_backward_compatible_mirdan_config(self) -> None:
        """MirdanConfig with no new fields should still work."""
        config = MirdanConfig()
        assert config.thresholds.arch_max_function_length == 30
        assert config.quality.language == "moderate"
        assert config.project.github_owner == ""

    def test_thresholds_file_overrides_default(self) -> None:
        """Default ThresholdsConfig has empty file_overrides."""
        t = ThresholdsConfig()
        assert t.file_overrides == []

    def test_thresholds_file_overrides_creation(self) -> None:
        """ThresholdsConfig should accept file_overrides."""
        from mirdan.config import FileThresholdOverride

        t = ThresholdsConfig(
            file_overrides=[
                FileThresholdOverride(pattern="tests/**", arch_max_function_length=50),
            ]
        )
        assert len(t.file_overrides) == 1
        assert t.file_overrides[0].pattern == "tests/**"
        assert t.file_overrides[0].arch_max_function_length == 50

    def test_thresholds_backward_compatible(self) -> None:
        """Existing configs without file_overrides still load correctly."""
        t = ThresholdsConfig(arch_max_function_length=40)
        assert t.arch_max_function_length == 40
        assert t.file_overrides == []


class TestQualityStandardsLanguageConfig:
    """Tests for language stringency affecting language principles count."""

    def test_strict_produces_more_language_standards(self) -> None:
        """Strict language config should produce more language standards."""
        strict = QualityStandards(config=QualityConfig(language="strict"))
        permissive = QualityStandards(config=QualityConfig(language="permissive"))

        intent = Intent(
            original_prompt="implement a feature",
            task_type=TaskType.GENERATION,
            primary_language="python",
        )

        strict_result = strict.render_for_intent(intent)
        permissive_result = permissive.render_for_intent(intent)
        assert len(strict_result) > len(permissive_result)

    def test_default_is_moderate_three_standards(self) -> None:
        """Default (moderate) should return 3 language principles."""
        standards = QualityStandards()  # No config = moderate
        intent = Intent(
            original_prompt="implement a feature",
            task_type=TaskType.GENERATION,
            primary_language="python",
        )
        result = standards.render_for_intent(intent)
        # Moderate = 3 language principles + architecture standards
        # Check we get at least 3 (language) + some architecture
        assert len(result) >= 3
