"""Tests for cleave assessment module - pattern matching and complexity calculation."""

from __future__ import annotations

import pytest

from cleave.core.assessment import (
    MODIFIERS,
    PATTERNS,
    AssessmentResult,
    PatternMatch,
    assess_directive,
    calculate_complexity,
    detect_flags,
    detect_modifiers,
    effective_complexity,
    match_pattern,
    should_skip_interrogation,
)


class TestPatternLibrary:
    """Verify the pattern library is properly structured."""

    def test_all_seven_patterns_exist(self) -> None:
        """Should have exactly 7 core patterns."""
        expected_patterns = {
            "full_stack_crud",
            "authentication",
            "external_integration",
            "database_migration",
            "performance_optimization",
            "breaking_api_change",
            "simple_refactor",
        }
        assert set(PATTERNS.keys()) == expected_patterns

    def test_patterns_have_required_fields(self) -> None:
        """Each pattern should have all required fields."""
        required_fields = {
            "name",
            "description",
            "keywords",
            "required_any",
            "systems_base",
            "modifiers_default",
            "split_strategy",
        }
        for pattern_id, pattern in PATTERNS.items():
            for field in required_fields:
                assert field in pattern, f"Pattern {pattern_id} missing field {field}"

    def test_patterns_have_expected_components(self) -> None:
        """Most patterns should have expected_components for high confidence matching."""
        patterns_with_components = [
            "full_stack_crud",
            "authentication",
            "external_integration",
            "database_migration",
            "performance_optimization",
            "breaking_api_change",
            "simple_refactor",
        ]
        for pattern_id in patterns_with_components:
            assert "expected_components" in PATTERNS[pattern_id]


class TestMatchPattern:
    """Tests for pattern matching against directives."""

    # Full-Stack CRUD pattern
    def test_matches_full_stack_crud_explicit(self) -> None:
        """Should match explicit CRUD directive."""
        match = match_pattern("Add CRUD operations for users with React frontend and PostgreSQL")
        assert match is not None
        assert match.pattern_id == "full_stack_crud"
        assert match.confidence >= 0.80

    def test_matches_full_stack_crud_form(self) -> None:
        """Should match form-based directive."""
        match = match_pattern("Create a user registration form with API endpoint and database table")
        assert match is not None
        assert match.pattern_id == "full_stack_crud"

    # Authentication pattern
    def test_matches_authentication_jwt(self) -> None:
        """Should match JWT authentication directive."""
        match = match_pattern("Add JWT authentication with login and logout endpoints")
        assert match is not None
        assert match.pattern_id == "authentication"
        assert match.confidence >= 0.80

    def test_matches_authentication_oauth(self) -> None:
        """Should match OAuth directive."""
        match = match_pattern("Implement OAuth login with Google and password hashing")
        assert match is not None
        assert match.pattern_id == "authentication"

    # External Integration pattern
    def test_matches_external_integration_stripe(self) -> None:
        """Should match Stripe integration directive."""
        match = match_pattern("Integrate Stripe payment processing with webhook handlers")
        assert match is not None
        assert match.pattern_id == "external_integration"
        assert match.confidence >= 0.80

    def test_matches_external_integration_aws(self) -> None:
        """Should match AWS integration directive."""
        match = match_pattern("Add S3 file upload with presigned URLs and webhook callback")
        assert match is not None
        assert match.pattern_id == "external_integration"

    # Database Migration pattern
    def test_matches_database_migration(self) -> None:
        """Should match migration directive."""
        match = match_pattern("Create migration to add column to users table with rollback")
        assert match is not None
        assert match.pattern_id == "database_migration"
        assert match.confidence >= 0.80

    def test_matches_database_backfill(self) -> None:
        """Should match backfill directive."""
        match = match_pattern("Backfill the email_verified column for existing users")
        assert match is not None
        assert match.pattern_id == "database_migration"

    # Performance Optimization pattern
    def test_matches_performance_redis(self) -> None:
        """Should match Redis caching directive."""
        match = match_pattern("Add Redis caching for API responses with TTL and invalidation")
        assert match is not None
        assert match.pattern_id == "performance_optimization"
        assert match.confidence >= 0.80

    def test_matches_performance_latency(self) -> None:
        """Should match latency optimization directive."""
        # Need cache/redis keyword to reach 0.80 confidence
        match = match_pattern("Optimize slow queries with cache layer to achieve p95 latency under 100ms")
        assert match is not None
        assert match.pattern_id == "performance_optimization"

    # Breaking API Change pattern
    def test_matches_breaking_api_version(self) -> None:
        """Should match API versioning directive."""
        match = match_pattern("Create v2 API endpoint and deprecate v1 with migration path")
        assert match is not None
        assert match.pattern_id == "breaking_api_change"
        assert match.confidence >= 0.80

    def test_matches_breaking_api_deprecate(self) -> None:
        """Should match deprecation directive."""
        # Add more keywords to reach threshold
        match = match_pattern("Deprecate the old v1 user endpoint and add backward compatibility for clients")
        assert match is not None
        assert match.pattern_id == "breaking_api_change"

    # Simple Refactor pattern
    def test_matches_simple_refactor_rename(self) -> None:
        """Should match rename refactor directive."""
        # Add cleanup or reorganize to reach threshold
        match = match_pattern("Refactor: rename getUserById function to fetchUser and cleanup imports")
        assert match is not None
        assert match.pattern_id == "simple_refactor"
        assert match.confidence >= 0.80

    def test_matches_simple_refactor_extract(self) -> None:
        """Should match extract refactor directive."""
        match = match_pattern("Extract validation logic into separate module, cleanup and reorganize imports")
        assert match is not None
        assert match.pattern_id == "simple_refactor"

    # Negative cases
    def test_no_match_for_vague_directive(self) -> None:
        """Should not match vague directives with low confidence."""
        match = match_pattern("Make it better and fix issues")
        # Either no match or low confidence
        assert match is None or match.confidence < 0.80

    def test_no_match_for_unrelated_directive(self) -> None:
        """Should not match completely unrelated directives."""
        match = match_pattern("Write a poem about the ocean")
        assert match is None

    def test_confidence_penalty_for_vague_terms(self) -> None:
        """Should apply penalty for vague terms like 'improve' or 'better'."""
        # Clear match with vague term
        match = match_pattern("Improve the CRUD operations and make them better")
        if match:
            # Should have lower confidence due to vague terms
            assert match.confidence < 0.90


class TestCalculateComplexity:
    """Tests for complexity calculation formula."""

    def test_base_complexity_one_system_no_modifiers(self) -> None:
        """Complexity with 1 system and no modifiers should be 2.0."""
        # (1 + 1) * (1 + 0.5 * 0) = 2 * 1 = 2.0
        assert calculate_complexity(1, []) == 2.0

    def test_complexity_two_systems_no_modifiers(self) -> None:
        """Complexity with 2 systems should be 3.0."""
        # (1 + 2) * (1 + 0.5 * 0) = 3 * 1 = 3.0
        assert calculate_complexity(2, []) == 3.0

    def test_complexity_with_one_modifier(self) -> None:
        """Adding a modifier should increase complexity by 50%."""
        # (1 + 1) * (1 + 0.5 * 1) = 2 * 1.5 = 3.0
        assert calculate_complexity(1, ["error_handling"]) == 3.0

    def test_complexity_with_multiple_modifiers(self) -> None:
        """Multiple modifiers compound the complexity."""
        # (1 + 2) * (1 + 0.5 * 3) = 3 * 2.5 = 7.5
        result = calculate_complexity(2, ["error_handling", "state_coordination", "concurrency"])
        assert result == 7.5

    def test_complexity_capped_at_100(self) -> None:
        """Complexity should be capped at 100.0 to prevent overflow."""
        # Very large numbers should be capped
        result = calculate_complexity(1000, ["a", "b", "c", "d", "e"] * 20)
        assert result == 100.0

    def test_complexity_rounding(self) -> None:
        """Complexity should be rounded to 1 decimal place."""
        # (1 + 3) * (1 + 0.5 * 1) = 4 * 1.5 = 6.0
        assert calculate_complexity(3, ["one"]) == 6.0


class TestEffectiveComplexity:
    """Tests for validation offset calculation."""

    def test_effective_complexity_with_validation(self) -> None:
        """Should add 1 when validate=True."""
        assert effective_complexity(3.0, validate=True) == 4.0

    def test_effective_complexity_without_validation(self) -> None:
        """Should not add offset when validate=False."""
        assert effective_complexity(3.0, validate=False) == 3.0


class TestDetectModifiers:
    """Tests for modifier detection from directives."""

    def test_detects_state_coordination(self) -> None:
        """Should detect state coordination keywords."""
        mods = detect_modifiers("Handle cache invalidation with eventual consistency")
        assert "state_coordination" in mods

    def test_detects_error_handling(self) -> None:
        """Should detect error handling keywords."""
        mods = detect_modifiers("Add retry logic with rollback on failure")
        assert "error_handling" in mods

    def test_detects_concurrency(self) -> None:
        """Should detect concurrency keywords."""
        mods = detect_modifiers("Handle concurrent requests with proper locking")
        assert "concurrency" in mods

    def test_detects_security_critical(self) -> None:
        """Should detect security keywords."""
        mods = detect_modifiers("Store user credentials with encryption")
        assert "security_critical" in mods

    def test_detects_multiple_modifiers(self) -> None:
        """Should detect multiple modifiers in one directive."""
        mods = detect_modifiers(
            "Add Stripe webhook with retry logic, encryption, and concurrent processing"
        )
        assert len(mods) >= 3

    def test_no_modifiers_for_simple_directive(self) -> None:
        """Should return empty list for simple directive."""
        mods = detect_modifiers("Rename function from foo to bar")
        # May have some modifiers, but likely few
        assert len(mods) <= 1


class TestDetectFlags:
    """Tests for special flag detection."""

    def test_detects_iamverysmart(self) -> None:
        """Should detect iamverysmart flag."""
        flags = detect_flags("Add auth system iamverysmart")
        assert flags["iamverysmart"] is True

    def test_detects_robust_mode(self) -> None:
        """Should detect cleave-robust flag."""
        flags = detect_flags("Build API cleave-robust")
        assert flags["robust"] is True

    def test_detects_robust_underscore(self) -> None:
        """Should detect cleave_robust with underscore."""
        flags = detect_flags("Build API cleave_robust")
        assert flags["robust"] is True

    def test_no_flags_by_default(self) -> None:
        """Should return False for both flags by default."""
        flags = detect_flags("Normal directive without special flags")
        assert flags["iamverysmart"] is False
        assert flags["robust"] is False


class TestAssessDirective:
    """Tests for the main assess_directive function."""

    def test_assess_fast_path_with_pattern(self) -> None:
        """Should use fast-path when pattern matches."""
        result = assess_directive("Add JWT authentication with bcrypt password hashing")
        assert result.method == "fast-path"
        assert result.pattern == "Authentication System"
        assert result.confidence >= 0.80

    def test_assess_sequential_fallback(self) -> None:
        """Should fall back to sequential when no pattern matches."""
        result = assess_directive("Build a complex system with many parts")
        assert result.method == "sequential"
        assert result.pattern is None
        assert result.decision == "needs_assessment"

    def test_assess_decision_execute(self) -> None:
        """Should recommend execute for low complexity."""
        result = assess_directive("Refactor: rename function foo to bar", threshold=5.0)
        if result.method == "fast-path":
            assert result.decision == "execute"

    def test_assess_decision_cleave(self) -> None:
        """Should recommend cleave for high complexity."""
        result = assess_directive(
            "Add full-stack CRUD with React, Express API, and PostgreSQL database"
        )
        # Full-stack CRUD has base systems=3, so complexity is high
        assert result.complexity >= 3.0

    def test_assess_empty_directive_raises(self) -> None:
        """Should raise ValueError for empty directive."""
        with pytest.raises(ValueError, match="empty"):
            assess_directive("")

    def test_assess_whitespace_directive_raises(self) -> None:
        """Should raise ValueError for whitespace-only directive."""
        with pytest.raises(ValueError, match="empty"):
            assess_directive("   \n\t  ")

    def test_assess_includes_modifiers(self) -> None:
        """Should include detected modifiers in result."""
        result = assess_directive(
            "Add authentication with retry logic and error handling"
        )
        assert len(result.modifiers) >= 1


class TestShouldSkipInterrogation:
    """Tests for Tier 0 (trivial task) detection."""

    def test_skip_for_high_confidence_simple_refactor(self) -> None:
        """Should skip interrogation for high-confidence simple refactor."""
        result = AssessmentResult(
            complexity=1.5,
            systems=1,
            modifiers=[],
            method="fast-path",
            pattern="Simple Refactor",
            confidence=0.92,
            decision="execute",
        )
        assert should_skip_interrogation(result, threshold=3.0) is True

    def test_no_skip_for_low_confidence(self) -> None:
        """Should not skip interrogation for low confidence matches."""
        result = AssessmentResult(
            complexity=1.5,
            systems=1,
            modifiers=[],
            method="fast-path",
            pattern="Simple Refactor",
            confidence=0.85,  # Below 0.90 threshold
            decision="execute",
        )
        assert should_skip_interrogation(result, threshold=3.0) is False

    def test_no_skip_for_complex_tasks(self) -> None:
        """Should not skip interrogation if complexity exceeds threshold."""
        result = AssessmentResult(
            complexity=5.0,
            systems=3,
            modifiers=["state_coordination"],
            method="fast-path",
            pattern="Simple Refactor",
            confidence=0.92,
            decision="cleave",
        )
        # effective_complexity = 5.0 + 1 = 6.0 > threshold
        assert should_skip_interrogation(result, threshold=3.0) is False

    def test_no_skip_for_non_refactor_patterns(self) -> None:
        """Should not skip interrogation for patterns other than Simple Refactor."""
        result = AssessmentResult(
            complexity=1.5,
            systems=1,
            modifiers=[],
            method="fast-path",
            pattern="Authentication System",  # Not Simple Refactor
            confidence=0.92,
            decision="execute",
        )
        assert should_skip_interrogation(result, threshold=3.0) is False


class TestPatternMatchDataclass:
    """Tests for PatternMatch dataclass."""

    def test_pattern_match_creation(self) -> None:
        """Should create PatternMatch with all fields."""
        pm = PatternMatch(
            pattern_id="authentication",
            name="Authentication System",
            confidence=0.85,
            keywords_matched=["auth", "jwt"],
            systems=2,
            modifiers=["security_critical"],
        )
        assert pm.pattern_id == "authentication"
        assert pm.confidence == 0.85
        assert len(pm.keywords_matched) == 2


class TestAssessmentResultDataclass:
    """Tests for AssessmentResult dataclass."""

    def test_assessment_result_defaults(self) -> None:
        """Should have correct default values."""
        result = AssessmentResult(
            complexity=3.0,
            systems=2,
            modifiers=["error_handling"],
            method="fast-path",
        )
        assert result.pattern is None
        assert result.confidence == 0.0
        assert result.decision == ""
        assert result.skip_interrogation is False
