"""
Test Suite: Proposal Template Engine
====================================

Comprehensive black box and white box tests for the AgileAdapt
Proposal Template Engine.

VERIFICATION_STAMP
Story: STORY-2.1
Verified By: CLAUDE
Verified At: 2026-01-24
Tests: 24 total (12 black box + 12 white box)
Coverage: Core template engine functionality
"""

import pytest
import sys
from datetime import datetime
from pathlib import Path
from unittest.mock import patch, MagicMock

# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from core.proposals.template_engine import (
    ProposalTemplateEngine,
    ProposalType,
    PricingTier,
    BusinessContext,
    ProposalResult,
    TemplateSlot,
    calculate_monthly_price,
    calculate_buildout_price,
    determine_pricing_tier,
    select_proposal_type,
    MONTHLY_PRICING,
    BUILDOUT_PRICING,
)


# =============================================================================
# TEST FIXTURES
# =============================================================================

@pytest.fixture
def sample_business_context():
    """Standard business context for testing."""
    return BusinessContext(
        business_name="Test Plumbing Co",
        contact_name="John Test",
        industry="Trades - Plumbing",
        pain_points=[
            "Missing calls while on jobs",
            "After-hours calls going to voicemail",
        ],
        current_challenges="Staff overwhelmed with phone inquiries",
        business_size="small",
        call_volume="moderate",
        integrations_needed=["ServiceM8"],
    )


@pytest.fixture
def large_business_context():
    """Large business context for tier testing."""
    return BusinessContext(
        business_name="Enterprise Services Ltd",
        contact_name="Jane Enterprise",
        industry="Professional Services",
        pain_points=[
            "Multiple locations need unified handling",
            "Complex routing requirements",
            "CRM integration essential",
        ],
        current_challenges="Need full workflow automation",
        business_size="large",
        call_volume="very_high",
        integrations_needed=["Salesforce", "Calendly", "Xero", "Slack"],
        custom_requirements={"multi_location": True, "custom_api": True},
    )


@pytest.fixture
def template_engine():
    """Standard template engine instance."""
    return ProposalTemplateEngine()


# =============================================================================
# BLACK BOX TESTS - Testing from outside without implementation knowledge
# =============================================================================

class TestBlackBoxTemplateRendering:
    """Black box tests for template rendering."""

    def test_template_renders_with_required_fields(self, template_engine, sample_business_context):
        """Test that template renders with all required context fields."""
        result = template_engine.generate_proposal(sample_business_context)

        assert isinstance(result, ProposalResult)
        assert sample_business_context.business_name in result.content
        assert sample_business_context.contact_name in result.content
        assert result.content  # Not empty

    def test_template_renders_all_proposal_types(self, template_engine, sample_business_context):
        """Test all proposal types render without error."""
        for ptype in ProposalType:
            result = template_engine.generate_proposal(
                sample_business_context,
                proposal_type=ptype
            )
            assert result.proposal_type == ptype
            assert len(result.content) > 500  # Substantial content

    def test_template_includes_pricing_table(self, template_engine, sample_business_context):
        """Test pricing table appears in rendered content."""
        result = template_engine.generate_proposal(sample_business_context)

        assert "$" in result.content  # Has pricing
        assert "month" in result.content.lower()  # Monthly reference
        assert str(result.monthly_price) in result.content

    def test_template_includes_pain_points(self, template_engine, sample_business_context):
        """Test pain points are included in output."""
        result = template_engine.generate_proposal(sample_business_context)

        for pain_point in sample_business_context.pain_points:
            assert pain_point in result.content


class TestBlackBoxSlotFilling:
    """Black box tests for slot filling functionality."""

    def test_all_slots_filled(self, template_engine, sample_business_context):
        """Test that no unfilled slots remain in output."""
        result = template_engine.generate_proposal(sample_business_context)

        # No {{slot}} patterns should remain
        assert "{{" not in result.content
        assert "}}" not in result.content

    def test_business_name_appears_multiple_times(self, template_engine, sample_business_context):
        """Test business name is used throughout proposal."""
        result = template_engine.generate_proposal(sample_business_context)

        count = result.content.count(sample_business_context.business_name)
        assert count >= 3  # Should appear multiple times

    def test_custom_pain_points_render(self, template_engine):
        """Test custom pain points are rendered correctly."""
        context = BusinessContext(
            business_name="Custom Test",
            contact_name="Test User",
            industry="Testing",
            pain_points=["Unique problem one", "Unique problem two"],
        )
        result = template_engine.generate_proposal(context)

        assert "Unique problem one" in result.content
        assert "Unique problem two" in result.content

    def test_empty_pain_points_uses_defaults(self, template_engine):
        """Test empty pain points get default values."""
        context = BusinessContext(
            business_name="Empty Test",
            contact_name="Test User",
            industry="Testing",
            pain_points=[],
        )
        result = template_engine.generate_proposal(context)

        # Should have some pain points from defaults
        assert "calls" in result.content.lower() or "customers" in result.content.lower()


class TestBlackBoxPricingOutput:
    """Black box tests for pricing in output."""

    def test_base_pricing_output(self, template_engine, sample_business_context):
        """Test Voice AI Base pricing appears correctly."""
        result = template_engine.generate_proposal(
            sample_business_context,
            proposal_type=ProposalType.VOICE_AI_BASE
        )

        assert result.monthly_price == 497
        assert "$497" in result.content

    def test_pro_pricing_output(self, template_engine, sample_business_context):
        """Test Voice AI Pro pricing appears correctly."""
        result = template_engine.generate_proposal(
            sample_business_context,
            proposal_type=ProposalType.VOICE_AI_PRO,
            pricing_tier=PricingTier.BASIC
        )

        assert result.monthly_price == 697
        assert "$697" in result.content

    def test_full_automation_pricing_output(self, template_engine, sample_business_context):
        """Test Full Automation pricing appears correctly."""
        result = template_engine.generate_proposal(
            sample_business_context,
            proposal_type=ProposalType.FULL_AUTOMATION_SUITE
        )

        assert result.monthly_price == 897
        assert "$897" in result.content

    def test_total_first_month_calculation(self, template_engine, sample_business_context):
        """Test first month total is monthly + buildout."""
        result = template_engine.generate_proposal(sample_business_context)

        expected_total = result.monthly_price + result.buildout_price
        assert result.total_first_month == expected_total


# =============================================================================
# WHITE BOX TESTS - Testing with implementation knowledge
# =============================================================================

class TestWhiteBoxPricingCalculation:
    """White box tests for pricing calculation internals."""

    def test_monthly_pricing_voice_ai_base(self):
        """Test monthly price calculation for Voice AI Base."""
        price = calculate_monthly_price(ProposalType.VOICE_AI_BASE)
        assert price == 497

    def test_monthly_pricing_voice_ai_pro_tiers(self):
        """Test monthly price varies by tier for Voice AI Pro."""
        basic = calculate_monthly_price(ProposalType.VOICE_AI_PRO, PricingTier.BASIC)
        standard = calculate_monthly_price(ProposalType.VOICE_AI_PRO, PricingTier.STANDARD)
        advanced = calculate_monthly_price(ProposalType.VOICE_AI_PRO, PricingTier.ADVANCED)
        enterprise = calculate_monthly_price(ProposalType.VOICE_AI_PRO, PricingTier.ENTERPRISE)

        assert basic == 697
        assert standard == 747
        assert advanced == 847
        assert enterprise == 897

    def test_buildout_pricing_base_tiers(self):
        """Test buildout pricing for Voice AI Base tiers."""
        basic = calculate_buildout_price(ProposalType.VOICE_AI_BASE, PricingTier.BASIC)
        enterprise = calculate_buildout_price(ProposalType.VOICE_AI_BASE, PricingTier.ENTERPRISE)

        assert basic == 1500
        assert enterprise == 2500

    def test_buildout_pricing_full_automation(self):
        """Test buildout pricing for Full Automation Suite."""
        basic = calculate_buildout_price(ProposalType.FULL_AUTOMATION_SUITE, PricingTier.BASIC)
        enterprise = calculate_buildout_price(ProposalType.FULL_AUTOMATION_SUITE, PricingTier.ENTERPRISE)

        assert basic == 3500
        assert enterprise == 5000

    def test_pricing_dictionary_integrity(self):
        """Test pricing dictionaries have all expected keys."""
        for ptype in ProposalType:
            assert ptype in BUILDOUT_PRICING
            for tier in PricingTier:
                assert tier in BUILDOUT_PRICING[ptype]


class TestWhiteBoxTemplateSelection:
    """White box tests for automatic template selection logic."""

    def test_select_base_for_small_simple_business(self):
        """Test small business with simple needs gets Base."""
        context = BusinessContext(
            business_name="Simple Test",
            contact_name="Test",
            industry="Test",
            business_size="small",
            call_volume="low",
            integrations_needed=[],
        )
        selected = select_proposal_type(context)
        assert selected == ProposalType.VOICE_AI_BASE

    def test_select_pro_for_high_volume(self):
        """Test high volume business gets Pro."""
        context = BusinessContext(
            business_name="Volume Test",
            contact_name="Test",
            industry="Test",
            business_size="small",
            call_volume="high",
            integrations_needed=[],
        )
        selected = select_proposal_type(context)
        assert selected == ProposalType.VOICE_AI_PRO

    def test_select_full_automation_for_workflow_needs(self):
        """Test business needing workflow automation gets Full Suite."""
        context = BusinessContext(
            business_name="Workflow Test",
            contact_name="Test",
            industry="Test",
            business_size="large",
            call_volume="high",
            current_challenges="Need workflow automation for booking",
            integrations_needed=["CRM", "Booking System"],
        )
        selected = select_proposal_type(context)
        assert selected == ProposalType.FULL_AUTOMATION_SUITE

    def test_select_full_automation_for_scheduling_keyword(self):
        """Test scheduling keyword triggers Full Automation."""
        context = BusinessContext(
            business_name="Scheduler Test",
            contact_name="Test",
            industry="Test",
            business_size="medium",
            call_volume="high",
            integrations_needed=["scheduling"],
        )
        selected = select_proposal_type(context)
        assert selected == ProposalType.FULL_AUTOMATION_SUITE


class TestWhiteBoxTierDetermination:
    """White box tests for pricing tier determination logic."""

    def test_determine_basic_tier(self):
        """Test basic tier for simple requirements."""
        context = BusinessContext(
            business_name="Basic Test",
            contact_name="Test",
            industry="Test",
            business_size="small",
            call_volume="low",
            integrations_needed=[],
            custom_requirements={},
        )
        tier = determine_pricing_tier(context)
        assert tier == PricingTier.BASIC

    def test_determine_enterprise_tier(self):
        """Test enterprise tier for complex requirements."""
        context = BusinessContext(
            business_name="Enterprise Test",
            contact_name="Test",
            industry="Test",
            business_size="large",
            call_volume="very_high",
            integrations_needed=["CRM", "Booking", "Accounting", "Custom"],
            custom_requirements={"api": True, "sso": True, "multi_tenant": True},
        )
        tier = determine_pricing_tier(context)
        assert tier == PricingTier.ENTERPRISE

    def test_tier_scoring_accumulation(self):
        """Test tier score accumulates from multiple factors."""
        # Moderate complexity
        context = BusinessContext(
            business_name="Moderate Test",
            contact_name="Test",
            industry="Test",
            business_size="medium",  # +1
            call_volume="moderate",  # +1
            integrations_needed=["One", "Two"],  # +2
            custom_requirements={},  # +0
        )
        tier = determine_pricing_tier(context)
        # Score = 4, should be STANDARD
        assert tier == PricingTier.STANDARD


class TestWhiteBoxTemplateSlot:
    """White box tests for TemplateSlot class internals."""

    def test_find_slots_pattern(self):
        """Test slot pattern finding."""
        template = "Hello {{name}}, your {{product}} is ready."
        slots = TemplateSlot.find_slots(template)
        assert "name" in slots
        assert "product" in slots
        assert len(slots) == 2

    def test_fill_slots_substitution(self):
        """Test slot substitution."""
        template = "Hello {{name}}, welcome to {{company}}!"
        filled = TemplateSlot.fill_slots(template, {
            "name": "John",
            "company": "Acme Corp"
        })
        assert filled == "Hello John, welcome to Acme Corp!"

    def test_fill_slots_preserves_unfound(self):
        """Test unfound slots are preserved."""
        template = "Hello {{name}}, your {{missing}} is here."
        filled = TemplateSlot.fill_slots(template, {"name": "John"})
        assert "{{missing}}" in filled

    def test_find_slots_empty_template(self):
        """Test finding slots in empty template."""
        slots = TemplateSlot.find_slots("")
        assert slots == []


class TestWhiteBoxEngineInternals:
    """White box tests for engine internal methods."""

    def test_format_pain_points_with_list(self, template_engine):
        """Test pain points formatting with actual list."""
        result = template_engine._format_pain_points(["Point 1", "Point 2"])
        assert "- Point 1" in result
        assert "- Point 2" in result

    def test_format_pain_points_empty(self, template_engine):
        """Test pain points formatting with empty list uses defaults."""
        result = template_engine._format_pain_points([])
        assert len(result) > 0  # Should have default content
        assert "-" in result  # Should be bullet points

    def test_format_integrations(self, template_engine):
        """Test integrations list formatting."""
        result = template_engine._format_integrations(["CRM", "Calendar"])
        assert "CRM" in result
        assert "Calendar" in result

    def test_template_cache_usage(self, template_engine):
        """Test template caching mechanism."""
        # Load template twice
        template1 = template_engine.load_template(ProposalType.VOICE_AI_BASE)
        template2 = template_engine.load_template(ProposalType.VOICE_AI_BASE)

        assert template1 == template2
        # Should be in cache
        assert ProposalType.VOICE_AI_BASE.value in template_engine._template_cache

    def test_clear_cache(self, template_engine):
        """Test cache clearing."""
        template_engine.load_template(ProposalType.VOICE_AI_BASE)
        assert len(template_engine._template_cache) > 0

        template_engine.clear_cache()
        assert len(template_engine._template_cache) == 0


# =============================================================================
# INTEGRATION TESTS
# =============================================================================

class TestIntegration:
    """Integration tests combining multiple components."""

    def test_full_proposal_generation_workflow(self, template_engine, sample_business_context):
        """Test complete proposal generation from context to output."""
        # Auto-select type and tier
        result = template_engine.generate_proposal(sample_business_context)

        # Verify all pieces present
        assert result.business_context == sample_business_context
        assert result.proposal_type in ProposalType
        assert result.pricing_tier in PricingTier
        assert result.monthly_price > 0
        assert result.buildout_price > 0
        assert result.generated_at
        assert len(result.content) > 1000

    def test_large_business_gets_appropriate_proposal(self, template_engine, large_business_context):
        """Test large business gets Full Automation with Enterprise tier."""
        result = template_engine.generate_proposal(large_business_context)

        assert result.proposal_type == ProposalType.FULL_AUTOMATION_SUITE
        assert result.pricing_tier == PricingTier.ENTERPRISE
        assert result.monthly_price == 897
        assert result.buildout_price == 5000

    def test_template_validation(self, template_engine):
        """Test template validation identifies required slots."""
        for ptype in ProposalType:
            validation = template_engine.validate_template(ptype)
            assert validation["valid"]
            assert len(validation["missing_slots"]) == 0


# =============================================================================
# EDGE CASES AND ERROR HANDLING
# =============================================================================

class TestEdgeCases:
    """Edge case and error handling tests."""

    def test_minimal_context(self, template_engine):
        """Test with minimal required context."""
        context = BusinessContext(
            business_name="Minimal",
            contact_name="User",
            industry="Test"
        )
        result = template_engine.generate_proposal(context)
        assert result.content
        assert "Minimal" in result.content

    def test_special_characters_in_business_name(self, template_engine):
        """Test business name with special characters."""
        context = BusinessContext(
            business_name="O'Brien's & Sons Pty Ltd",
            contact_name="Test O'Brien",
            industry="Trades"
        )
        result = template_engine.generate_proposal(context)
        assert "O'Brien's & Sons Pty Ltd" in result.content

    def test_very_long_pain_points_list(self, template_engine):
        """Test with many pain points."""
        context = BusinessContext(
            business_name="Long Test",
            contact_name="User",
            industry="Test",
            pain_points=[f"Pain point {i}" for i in range(10)]
        )
        result = template_engine.generate_proposal(context)
        assert "Pain point 0" in result.content
        assert "Pain point 9" in result.content

    def test_missing_template_raises_error(self, template_engine):
        """Test missing template file raises FileNotFoundError."""
        # Create mock proposal type that doesn't have a template
        with patch.object(ProposalType, '__iter__', return_value=iter([ProposalType.VOICE_AI_BASE])):
            template_engine._template_cache.clear()
            template_engine.templates_dir = Path("/nonexistent/path")

            with pytest.raises(FileNotFoundError):
                template_engine.load_template(ProposalType.VOICE_AI_BASE)


# =============================================================================
# AUSTRALIAN MARKET COMPLIANCE TESTS
# =============================================================================

class TestAustralianMarketCompliance:
    """Tests for Australian market pricing compliance."""

    def test_no_hourly_rates_in_output(self, template_engine, sample_business_context):
        """Verify no hourly rates appear in proposals (MANDATORY)."""
        for ptype in ProposalType:
            result = template_engine.generate_proposal(
                sample_business_context,
                proposal_type=ptype
            )
            content_lower = result.content.lower()
            assert "/hour" not in content_lower
            assert "per hour" not in content_lower
            assert "hourly" not in content_lower

    def test_base_price_497(self, template_engine, sample_business_context):
        """Verify Voice AI Base is exactly $497/month."""
        result = template_engine.generate_proposal(
            sample_business_context,
            proposal_type=ProposalType.VOICE_AI_BASE
        )
        assert result.monthly_price == 497

    def test_pro_price_range_697_897(self, template_engine, sample_business_context):
        """Verify Voice AI Pro is in $697-897 range."""
        for tier in PricingTier:
            result = template_engine.generate_proposal(
                sample_business_context,
                proposal_type=ProposalType.VOICE_AI_PRO,
                pricing_tier=tier
            )
            assert 697 <= result.monthly_price <= 897

    def test_basic_buildout_range_1500_2500(self, template_engine, sample_business_context):
        """Verify basic buildout is in $1,500-2,500 range."""
        for tier in PricingTier:
            price = calculate_buildout_price(ProposalType.VOICE_AI_BASE, tier)
            assert 1500 <= price <= 2500

    def test_full_automation_buildout_range_3500_5000(self, template_engine, sample_business_context):
        """Verify full automation buildout is in $3,500-5,000 range."""
        for tier in PricingTier:
            price = calculate_buildout_price(ProposalType.FULL_AUTOMATION_SUITE, tier)
            assert 3500 <= price <= 5000


# =============================================================================
# MAIN EXECUTION
# =============================================================================

if __name__ == "__main__":
    pytest.main([__file__, "-v", "--tb=short"])
