"""
SST-Driven Tests for DatabaseConstitutionService.

Tests the token-budgeted context assembly service that loads
constitution items from database with priority-based allocation.

Spec: flows/automation/agentic/enrichment/docs-alignment.yaml
Phase: 5 - Context Assembly Enhancement

Usage:
    pytest lightwave/ai/constitution/tests/test_database_service.py -v
    pytest lightwave/ai/constitution/tests/test_database_service.py -v -k "budget"
    pytest lightwave/ai/constitution/tests/test_database_service.py -v -k "rejection"
"""

from __future__ import annotations

from lightwave.ai.constitution.database_service import (
    BUDGET_ALLOCATION,
    AssembledContext,
    ConstitutionItem,
    TokenBudget,
)

# =============================================================================
# TOKEN BUDGET TESTS
# =============================================================================


class TestTokenBudget:
    """Tests for TokenBudget allocation and consumption."""

    def test_budget_allocation_initialization(self):
        """Budget allocates tokens based on BUDGET_ALLOCATION shares."""
        budget = TokenBudget(total_budget=100000)

        # Verify allocations match the defined shares
        # Priority 1000 gets everything (0.0 share means "remainder")
        assert budget.allocated[1000] == 100000

        # Other priorities get their defined percentages
        assert budget.allocated[900] == 25000  # 25%
        assert budget.allocated[800] == 20000  # 20%
        assert budget.allocated[700] == 15000  # 15%
        assert budget.allocated[600] == 25000  # 25%
        assert budget.allocated[500] == 15000  # 15%

    def test_budget_can_fit(self):
        """can_fit correctly checks if tokens fit in budget tier."""
        budget = TokenBudget(total_budget=10000)

        # Priority 900 gets 2500 tokens (25%)
        assert budget.can_fit(900, 2000) is True
        assert budget.can_fit(900, 2500) is True
        assert budget.can_fit(900, 2501) is False

    def test_budget_consume(self):
        """consume correctly deducts tokens from budget tier."""
        budget = TokenBudget(total_budget=10000)

        # Consume 1000 tokens from priority 900
        assert budget.consume(900, 1000) is True
        assert budget.used[900] == 1000

        # Remaining should be 1500
        assert budget.remaining(900) == 1500

        # Try to consume more than remaining
        assert budget.consume(900, 2000) is False
        assert budget.used[900] == 1000  # Unchanged

    def test_budget_remaining(self):
        """remaining correctly calculates available tokens."""
        budget = TokenBudget(total_budget=10000)

        # Initially, priority 900 has 2500 available
        assert budget.remaining(900) == 2500

        budget.consume(900, 1000)
        assert budget.remaining(900) == 1500

    def test_budget_allocation_sums_correctly(self):
        """Total of non-absolute allocations equals total budget."""
        budget = TokenBudget(total_budget=100000)

        # Sum of priorities 500-900 should equal 100% of budget
        tier_sum = sum(budget.allocated[p] for p in [500, 600, 700, 800, 900])
        assert tier_sum == 100000


# =============================================================================
# CONSTITUTION ITEM TESTS
# =============================================================================


class TestConstitutionItem:
    """Tests for ConstitutionItem data class."""

    def test_constitution_item_creation(self):
        """ConstitutionItem can be created with all fields."""
        item = ConstitutionItem(
            id="test-123",
            category="prompt",
            title="Test Prompt",
            content="This is test content",
            priority=900,
            token_count=100,
            metadata={"key": "value"},
        )

        assert item.id == "test-123"
        assert item.category == "prompt"
        assert item.priority == 900
        assert item.token_count == 100

    def test_constitution_item_default_metadata(self):
        """ConstitutionItem has empty dict as default metadata."""
        item = ConstitutionItem(
            id="test-123",
            category="rule",
            title="Test Rule",
            content="Content",
            priority=800,
            token_count=50,
        )

        assert item.metadata == {}


# =============================================================================
# ASSEMBLED CONTEXT TESTS
# =============================================================================


class TestAssembledContext:
    """Tests for AssembledContext result class."""

    def test_assembled_context_total_tokens(self):
        """total_tokens correctly sums item token counts."""
        items = [
            ConstitutionItem(id="1", category="prompt", title="P1", content="c", priority=1000, token_count=100),
            ConstitutionItem(id="2", category="rule", title="R1", content="c", priority=800, token_count=200),
            ConstitutionItem(id="3", category="reference", title="Ref1", content="c", priority=500, token_count=150),
        ]

        context = AssembledContext(
            agent_type="test-agent",
            items=items,
        )

        assert context.total_tokens == 450

    def test_assembled_context_empty_items(self):
        """AssembledContext with no items has zero total tokens."""
        context = AssembledContext(agent_type="test-agent")
        assert context.total_tokens == 0
        assert context.items == []


# =============================================================================
# SPEC ALIGNMENT TESTS
# =============================================================================


class TestSpecAlignment:
    """Tests verifying alignment with SST specifications."""

    def test_budget_allocation_matches_spec(self):
        """BUDGET_ALLOCATION matches the plan document specification."""
        expected = {
            1000: 0.0,  # Absolute constraints - always included
            900: 0.25,  # Active task context - 25%
            800: 0.20,  # Relevant rules - 20%
            700: 0.15,  # Test breadcrumbs - 15%
            600: 0.25,  # Second brain knowledge - 25%
            500: 0.15,  # Reference documents - 15%
        }

        assert BUDGET_ALLOCATION == expected

    def test_priority_tiers_documented(self):
        """All documented priority tiers exist in BUDGET_ALLOCATION."""
        required_priorities = [1000, 900, 800, 700, 600, 500]

        for priority in required_priorities:
            assert priority in BUDGET_ALLOCATION, f"Missing priority {priority}"

    def test_allocation_shares_sum_to_100_percent(self):
        """Non-absolute allocations sum to 100%."""
        non_absolute_shares = [share for priority, share in BUDGET_ALLOCATION.items() if share > 0]

        total = sum(non_absolute_shares)
        assert abs(total - 1.0) < 0.01, f"Shares sum to {total}, expected 1.0"


# =============================================================================
# REJECTION TESTS
# =============================================================================


class TestRejection:
    """Tests that verify invalid scenarios are handled correctly."""

    def test_budget_consume_negative_allowed(self):
        """Negative token consumption is currently allowed (no validation).

        NOTE: This documents current behavior. Consider adding validation
        to reject negative values if this becomes a problem.
        """
        budget = TokenBudget(total_budget=10000)

        # Currently, negative consumption is allowed and adjusts the used count
        # This test documents the current behavior
        result = budget.consume(900, -100)

        # Implementation currently allows negative consumption
        assert result is True
        assert budget.used.get(900, 0) == -100

    def test_budget_unknown_priority(self):
        """Unknown priority tier returns 0 remaining."""
        budget = TokenBudget(total_budget=10000)

        # Priority 999 doesn't exist in BUDGET_ALLOCATION
        assert budget.remaining(999) == 0
        assert budget.can_fit(999, 1) is False

    def test_zero_budget_allocation(self):
        """Zero total budget allocates zero to all tiers."""
        budget = TokenBudget(total_budget=0)

        for priority in [500, 600, 700, 800, 900]:
            assert budget.allocated[priority] == 0
            assert budget.can_fit(priority, 1) is False


# =============================================================================
# BOUNDARY TESTS
# =============================================================================


class TestBoundaries:
    """Tests for boundary conditions."""

    def test_exact_budget_consumption(self):
        """Consuming exactly the allocated amount succeeds."""
        budget = TokenBudget(total_budget=10000)

        # Priority 900 gets exactly 2500
        assert budget.can_fit(900, 2500) is True
        assert budget.consume(900, 2500) is True
        assert budget.remaining(900) == 0

    def test_one_over_budget_fails(self):
        """Consuming one token over allocation fails."""
        budget = TokenBudget(total_budget=10000)

        # Priority 900 gets 2500, trying 2501 should fail
        assert budget.can_fit(900, 2501) is False

    def test_large_budget(self):
        """Large budget values don't overflow."""
        budget = TokenBudget(total_budget=1_000_000)

        assert budget.allocated[900] == 250_000
        assert budget.consume(900, 250_000) is True

    def test_small_budget_rounds_down(self):
        """Small budgets round down allocations."""
        budget = TokenBudget(total_budget=100)

        # 25% of 100 = 25
        assert budget.allocated[900] == 25
        # 15% of 100 = 15
        assert budget.allocated[700] == 15
