"""
Tests for Issue #114 - Cost Allocation Engine Implementation

This module contains comprehensive tests for the cost allocation engine
that distributes operational costs across active issues according to the
algorithm defined in Issue #88.

Tests cover:
- Core allocation algorithm with equal distribution
- Edge cases (no active issues, no costs, closed periods)
- Transaction audit trail creation
- Loss carried forward handling
- Integration with existing cost and activity tracking
- CLI command functionality
- Allocation reversal capabilities
"""

import pytest
import sqlite3
import tempfile
from datetime import datetime, date, timedelta
from decimal import Decimal
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
import json
from contextlib import redirect_stdout
import io

from markitect.finance.allocation_engine import (
    AllocationEngine, TransactionManager, AllocationStatus,
    AllocationResult, IssueAllocation
)
from markitect.finance.models import FinanceModels
from markitect.finance.cost_manager import CostItemManager, CostItem
from markitect.finance.period_manager import PeriodManager, PeriodStatus
from markitect.issues.activity_tracker import IssueActivityTracker, ActivityType


def create_test_cost_item(cost_manager, name, category_id, cost_type, amount_eur, starting_from_date):
    """Helper function to create cost items with proper interface."""
    cost_item = CostItem(
        name=name,
        category_id=category_id,
        cost_type=cost_type,
        amount_eur=amount_eur,
        starting_from_date=starting_from_date
    )
    return cost_manager.create_cost_item(cost_item)


def create_unique_category(cost_manager, base_name, description="Test category"):
    """Helper function to create categories with unique names."""
    import time
    unique_name = f"{base_name}-{int(time.time()*1000000)}"
    return cost_manager.create_category(unique_name, description)


class TestTransactionManager:
    """Test suite for TransactionManager class."""

    @pytest.fixture
    def temp_db(self):
        """Create temporary database for testing."""
        with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
            db_path = f.name

        # Initialize schema
        finance_models = FinanceModels(db_path)
        finance_models.initialize_finance_schema()

        yield db_path

        # Cleanup
        Path(db_path).unlink(missing_ok=True)

    @pytest.fixture
    def transaction_manager(self, temp_db):
        """Create TransactionManager instance for testing."""
        return TransactionManager(temp_db)

    @pytest.fixture
    def sample_period(self, temp_db):
        """Create a sample period for testing."""
        period_manager = PeriodManager(temp_db)
        period_id = period_manager.create_period(
            period_start=date(2025, 10, 1),
            period_end=date(2025, 10, 31)
        )
        return period_id

    def test_transaction_manager_initialization(self, temp_db):
        """Test that TransactionManager initializes properly."""
        manager = TransactionManager(temp_db)
        assert manager.db_path == temp_db
        assert isinstance(manager.finance_models, FinanceModels)

    def test_create_allocation_transaction(self, transaction_manager, sample_period):
        """Test creating allocation transaction records."""
        transaction_id = transaction_manager.create_allocation_transaction(
            period_id=sample_period,
            amount=Decimal('15.50'),
            issue_id=123,
            transaction_date=date(2025, 10, 15),
            description="Test allocation transaction"
        )

        assert transaction_id is not None
        assert isinstance(transaction_id, int)

        # Verify transaction was created
        with transaction_manager.finance_models.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(
                'SELECT * FROM cost_transactions WHERE id = ?',
                (transaction_id,)
            )
            row = cursor.fetchone()

            assert row is not None
            assert row[1] == sample_period  # period_id
            assert row[3] == 'cost_allocated'  # transaction_type
            assert float(row[4]) == 15.50  # amount_eur
            assert row[5] == 123  # issue_id

    def test_create_loss_forward_transaction(self, transaction_manager, sample_period):
        """Test creating loss carried forward transactions."""
        # Create next period
        period_manager = PeriodManager(transaction_manager.db_path)
        next_period_id = period_manager.create_period(
            period_start=date(2025, 11, 1),
            period_end=date(2025, 11, 30)
        )

        transaction_id = transaction_manager.create_loss_forward_transaction(
            from_period_id=sample_period,
            to_period_id=next_period_id,
            amount=Decimal('25.75'),
            transaction_date=date(2025, 11, 1),
            description="Loss carried forward"
        )

        assert transaction_id is not None

        # Verify transaction was created
        with transaction_manager.finance_models.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(
                'SELECT * FROM cost_transactions WHERE id = ?',
                (transaction_id,)
            )
            row = cursor.fetchone()

            assert row is not None
            assert row[1] == next_period_id  # period_id
            assert row[3] == 'loss_forward'  # transaction_type
            assert float(row[4]) == 25.75  # amount_eur
            assert row[5] is None  # issue_id (null for loss forward)


class TestAllocationEngine:
    """Test suite for AllocationEngine class."""

    @pytest.fixture
    def temp_db(self):
        """Create temporary database for testing."""
        with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
            db_path = f.name

        # Initialize schema
        finance_models = FinanceModels(db_path)
        finance_models.initialize_finance_schema()

        yield db_path

        # Cleanup
        Path(db_path).unlink(missing_ok=True)

    @pytest.fixture
    def allocation_engine(self, temp_db):
        """Create AllocationEngine instance for testing."""
        return AllocationEngine(temp_db)

    @pytest.fixture
    def sample_costs(self, temp_db):
        """Create sample cost items for testing."""
        cost_manager = CostItemManager(temp_db)

        # Create cost category
        category_id = create_unique_category(cost_manager, "Test Services", "Test cost category")

        # Create cost items
        cost_ids = []
        cost_ids.append(create_test_cost_item(
            cost_manager, "Monthly Service", category_id, "monthly",
            Decimal('20.00'), date(2025, 10, 1)
        ))

        cost_ids.append(create_test_cost_item(
            cost_manager, "One-time Setup", category_id, "one_time",
            Decimal('30.00'), date(2025, 10, 15)
        ))

        return cost_ids

    @pytest.fixture
    def sample_period_with_costs(self, temp_db, sample_costs):
        """Create a period with associated costs."""
        period_manager = PeriodManager(temp_db)
        period_id = period_manager.create_period(
            period_start=date(2025, 10, 1),
            period_end=date(2025, 10, 31)
        )
        return period_id

    @pytest.fixture
    def sample_issue_activities(self, temp_db, sample_period_with_costs):
        """Create sample issue activities for testing."""
        activity_tracker = IssueActivityTracker(temp_db)

        # Log activities for different issues
        activity_ids = []
        activity_ids.append(activity_tracker.log_activity(
            issue_id=101,
            activity_type=ActivityType.CREATED,
            activity_date=date(2025, 10, 5),
            period_id=sample_period_with_costs
        ))

        activity_ids.append(activity_tracker.log_activity(
            issue_id=102,
            activity_type=ActivityType.MODIFIED,
            activity_date=date(2025, 10, 10),
            period_id=sample_period_with_costs
        ))

        activity_ids.append(activity_tracker.log_activity(
            issue_id=103,
            activity_type=ActivityType.COMMENTED,
            activity_date=date(2025, 10, 20),
            period_id=sample_period_with_costs
        ))

        return activity_ids

    def test_allocation_engine_initialization(self, temp_db):
        """Test that AllocationEngine initializes with all required components."""
        engine = AllocationEngine(temp_db)

        assert engine.db_path == temp_db
        assert isinstance(engine.finance_models, FinanceModels)
        assert isinstance(engine.cost_manager, CostItemManager)
        assert isinstance(engine.period_manager, PeriodManager)
        assert isinstance(engine.activity_tracker, IssueActivityTracker)
        assert isinstance(engine.transaction_manager, TransactionManager)

    def test_successful_allocation(self, allocation_engine, sample_period_with_costs, sample_issue_activities):
        """Test successful cost allocation with active issues."""
        result = allocation_engine.allocate_period_costs(sample_period_with_costs)

        assert result.status == AllocationStatus.SUCCESS
        assert result.period_id == sample_period_with_costs
        assert result.total_costs == Decimal('50.00')  # 20.00 + 30.00
        assert len(result.active_issues) == 3  # Issues 101, 102, 103
        assert result.cost_per_issue == Decimal('50.00') / 3
        assert result.allocations_created == 3
        assert result.transactions_created == 3
        assert result.loss_carried_forward == Decimal('0.00')

    def test_allocation_no_active_issues(self, allocation_engine, sample_period_with_costs):
        """Test allocation when no active issues exist (should carry forward loss)."""
        result = allocation_engine.allocate_period_costs(sample_period_with_costs)

        assert result.status == AllocationStatus.NO_ACTIVE_ISSUES
        assert result.period_id == sample_period_with_costs
        assert result.total_costs == Decimal('50.00')
        assert len(result.active_issues) == 0
        assert result.cost_per_issue == Decimal('0.00')
        assert result.allocations_created == 0
        assert result.transactions_created == 0
        assert result.loss_carried_forward == Decimal('50.00')

    def test_allocation_no_costs(self, allocation_engine):
        """Test allocation when no costs exist for period."""
        # Create empty period
        period_manager = PeriodManager(allocation_engine.db_path)
        period_id = period_manager.create_period(
            period_start=date(2025, 11, 1),
            period_end=date(2025, 11, 30)
        )

        result = allocation_engine.allocate_period_costs(period_id)

        assert result.status == AllocationStatus.NO_COSTS_TO_ALLOCATE
        assert result.total_costs == Decimal('0.00')

    def test_allocation_period_closed(self, allocation_engine, sample_period_with_costs):
        """Test allocation on already closed period."""
        # First allocation (should succeed)
        result1 = allocation_engine.allocate_period_costs(sample_period_with_costs)
        assert result1.status in [AllocationStatus.SUCCESS, AllocationStatus.NO_ACTIVE_ISSUES]

        # Second allocation (should fail - period closed)
        result2 = allocation_engine.allocate_period_costs(sample_period_with_costs)
        assert result2.status == AllocationStatus.PERIOD_CLOSED

    def test_allocation_period_not_found(self, allocation_engine):
        """Test allocation with non-existent period ID."""
        result = allocation_engine.allocate_period_costs(99999)

        assert result.status == AllocationStatus.ERROR
        assert "not found" in result.message

    def test_get_issue_allocations(self, allocation_engine, sample_period_with_costs, sample_issue_activities):
        """Test retrieving allocations for a specific issue."""
        # Perform allocation first
        allocation_engine.allocate_period_costs(sample_period_with_costs)

        # Get allocations for issue 101
        allocations = allocation_engine.get_issue_allocations(101)

        assert len(allocations) == 1
        allocation = allocations[0]
        assert allocation['issue_id'] == 101
        assert allocation['period_id'] == sample_period_with_costs
        assert allocation['allocated_amount'] > 0
        assert allocation['transaction_id'] is not None

    def test_get_period_allocations(self, allocation_engine, sample_period_with_costs, sample_issue_activities):
        """Test retrieving all allocations for a specific period."""
        # Perform allocation first
        allocation_engine.allocate_period_costs(sample_period_with_costs)

        # Get all allocations for the period
        allocations = allocation_engine.get_period_allocations(sample_period_with_costs)

        assert len(allocations) == 3
        issue_ids = [alloc['issue_id'] for alloc in allocations]
        assert 101 in issue_ids
        assert 102 in issue_ids
        assert 103 in issue_ids

    def test_reverse_allocation(self, allocation_engine, sample_period_with_costs, sample_issue_activities):
        """Test reversing a cost allocation."""
        # Perform allocation first
        allocation_engine.allocate_period_costs(sample_period_with_costs)

        # Get allocation to reverse
        allocations = allocation_engine.get_issue_allocations(101)
        assert len(allocations) == 1
        allocation_id = allocations[0]['id']

        # Reverse the allocation
        success = allocation_engine.reverse_allocation(allocation_id)
        assert success is True

        # Verify allocation is removed
        allocations_after = allocation_engine.get_issue_allocations(101)
        assert len(allocations_after) == 0

    def test_reverse_nonexistent_allocation(self, allocation_engine):
        """Test reversing non-existent allocation."""
        success = allocation_engine.reverse_allocation(99999)
        assert success is False

    def test_allocation_with_carried_forward_loss(self, allocation_engine, temp_db):
        """Test allocation including loss carried forward from previous period."""
        # Create period with carried forward loss
        period_manager = PeriodManager(temp_db)
        period_id = period_manager.create_period(
            period_start=date(2025, 10, 1),
            period_end=date(2025, 10, 31),
            loss_carried_forward=Decimal('15.00')
        )

        # Create some costs
        cost_manager = CostItemManager(temp_db)
        category_id = create_unique_category(cost_manager, "Test", "Test category")
        create_test_cost_item(
            cost_manager, "Test Cost", category_id, "one_time",
            Decimal('10.00'), date(2025, 10, 15)
        )

        # Create issue activity
        activity_tracker = IssueActivityTracker(temp_db)
        activity_tracker.log_activity(
            issue_id=201,
            activity_type=ActivityType.CREATED,
            activity_date=date(2025, 10, 10),
            period_id=period_id
        )

        # Perform allocation
        result = allocation_engine.allocate_period_costs(period_id)

        assert result.status == AllocationStatus.SUCCESS
        assert result.total_costs == Decimal('25.00')  # 10.00 + 15.00 carried forward
        assert len(result.active_issues) == 1
        assert result.cost_per_issue == Decimal('25.00')


class TestAllocationIntegration:
    """Integration tests for allocation engine with other components."""

    @pytest.fixture
    def temp_db(self):
        """Create temporary database for testing."""
        with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
            db_path = f.name

        # Initialize schema
        finance_models = FinanceModels(db_path)
        finance_models.initialize_finance_schema()

        yield db_path

        # Cleanup
        Path(db_path).unlink(missing_ok=True)

    def test_complete_allocation_workflow(self, temp_db):
        """Test complete workflow from cost creation to allocation."""
        # Step 1: Create cost categories and items
        cost_manager = CostItemManager(temp_db)
        category_id = create_unique_category(cost_manager, "Infrastructure", "Server and hosting costs")

        monthly_cost_id = create_test_cost_item(
            cost_manager, "Server Hosting", category_id, "monthly",
            Decimal('25.00'), date(2025, 10, 1)
        )

        oneoff_cost_id = create_test_cost_item(
            cost_manager, "SSL Certificate", category_id, "one_time",
            Decimal('15.00'), date(2025, 10, 10)
        )

        # Step 2: Create period
        period_manager = PeriodManager(temp_db)
        period_id = period_manager.create_period(
            period_start=date(2025, 10, 1),
            period_end=date(2025, 10, 31)
        )

        # Step 3: Log issue activities
        activity_tracker = IssueActivityTracker(temp_db)
        activity_tracker.log_activity(
            issue_id=301,
            activity_type=ActivityType.CREATED,
            activity_date=date(2025, 10, 5),
            period_id=period_id
        )

        activity_tracker.log_activity(
            issue_id=302,
            activity_type=ActivityType.MODIFIED,
            activity_date=date(2025, 10, 12),
            period_id=period_id
        )

        # Step 4: Perform allocation
        allocation_engine = AllocationEngine(temp_db)
        result = allocation_engine.allocate_period_costs(period_id)

        # Verify allocation success
        assert result.status == AllocationStatus.SUCCESS
        assert result.total_costs == Decimal('40.00')
        assert len(result.active_issues) == 2
        assert result.cost_per_issue == Decimal('20.00')

        # Step 5: Verify database state
        # Check allocations exist
        allocations_301 = allocation_engine.get_issue_allocations(301)
        allocations_302 = allocation_engine.get_issue_allocations(302)

        assert len(allocations_301) == 1
        assert len(allocations_302) == 1
        assert allocations_301[0]['allocated_amount'] == 20.00
        assert allocations_302[0]['allocated_amount'] == 20.00

        # Check transactions exist
        with allocation_engine.finance_models.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(
                'SELECT COUNT(*) FROM cost_transactions WHERE transaction_type = "cost_allocated"'
            )
            transaction_count = cursor.fetchone()[0]
            assert transaction_count == 2

        # Check period is closed
        period_data = period_manager.get_period_by_id(period_id)
        periods = [period_data] if period_data else []
        assert len(periods) == 1
        assert periods[0]['status'] == PeriodStatus.CLOSED.value
        assert float(periods[0]['total_costs']) == 40.00
        assert periods[0]['active_issues_count'] == 2
        assert float(periods[0]['cost_per_issue']) == 20.00

    def test_multi_period_allocation_workflow(self, temp_db):
        """Test allocation across multiple periods with loss carry forward."""
        # Create cost items
        cost_manager = CostItemManager(temp_db)
        category_id = create_unique_category(cost_manager, "Services", "Monthly services")

        create_test_cost_item(
            cost_manager, "Monthly Service", category_id, "monthly",
            Decimal('30.00'), date(2025, 10, 1)
        )

        # Create periods
        period_manager = PeriodManager(temp_db)
        period1_id = period_manager.create_period(
            period_start=date(2025, 10, 1),
            period_end=date(2025, 10, 31)
        )

        period2_id = period_manager.create_period(
            period_start=date(2025, 11, 1),
            period_end=date(2025, 11, 30)
        )

        # Period 1: No activities (should carry forward loss)
        allocation_engine = AllocationEngine(temp_db)
        result1 = allocation_engine.allocate_period_costs(period1_id)

        assert result1.status == AllocationStatus.NO_ACTIVE_ISSUES
        assert result1.loss_carried_forward == Decimal('30.00')

        # Period 2: Add activity and allocate
        activity_tracker = IssueActivityTracker(temp_db)
        activity_tracker.log_activity(
            issue_id=401,
            activity_type=ActivityType.CREATED,
            activity_date=date(2025, 11, 15),
            period_id=period2_id
        )

        # Manually set carried forward for period 2 (simulating automatic carry forward)
        with allocation_engine.finance_models.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(
                'UPDATE cost_periods SET loss_carried_forward = ? WHERE id = ?',
                (float(Decimal('30.00')), period2_id)
            )

        result2 = allocation_engine.allocate_period_costs(period2_id)

        assert result2.status == AllocationStatus.SUCCESS
        assert result2.total_costs == Decimal('60.00')  # 30.00 current + 30.00 carried forward
        assert len(result2.active_issues) == 1
        assert result2.cost_per_issue == Decimal('60.00')


class TestAllocationCLI:
    """Test suite for allocation CLI commands."""

    @pytest.fixture
    def temp_db(self):
        """Create temporary database for testing."""
        with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
            db_path = f.name

        # Initialize schema
        finance_models = FinanceModels(db_path)
        finance_models.initialize_finance_schema()

        yield db_path

        # Cleanup
        Path(db_path).unlink(missing_ok=True)

    @pytest.fixture
    def setup_test_data(self, temp_db):
        """Set up test data for CLI testing."""
        # Create costs and period
        cost_manager = CostItemManager(temp_db)
        category_id = create_unique_category(cost_manager, "Test", "Test category")
        create_test_cost_item(
            cost_manager, "Test Cost", category_id, "one_time",
            Decimal('45.00'), date(2025, 10, 15)
        )

        period_manager = PeriodManager(temp_db)
        period_id = period_manager.create_period(
            period_start=date(2025, 10, 1),
            period_end=date(2025, 10, 31)
        )

        # Create issue activities
        activity_tracker = IssueActivityTracker(temp_db)
        activity_tracker.log_activity(
            issue_id=501,
            activity_type=ActivityType.CREATED,
            activity_date=date(2025, 10, 5),
            period_id=period_id
        )

        activity_tracker.log_activity(
            issue_id=502,
            activity_type=ActivityType.MODIFIED,
            activity_date=date(2025, 10, 15),
            period_id=period_id
        )

        activity_tracker.log_activity(
            issue_id=503,
            activity_type=ActivityType.COMMENTED,
            activity_date=date(2025, 10, 25),
            period_id=period_id
        )

        return period_id

    def test_cli_allocation_period_command(self, temp_db, setup_test_data):
        """Test CLI allocation period command."""
        from markitect.finance.cli import allocate_period
        from click.testing import CliRunner

        runner = CliRunner()

        # Mock configuration manager
        with patch('markitect.finance.cli.ConfigurationManager') as mock_config_manager:
            mock_config = Mock()
            mock_config.get_current_config.return_value = {'database_path': temp_db}
            mock_config_manager.return_value = mock_config

            result = runner.invoke(allocate_period, [str(setup_test_data)])

            assert result.exit_code == 0
            assert "Cost Allocation Complete" in result.output
            assert "Total Costs Allocated: €45.00" in result.output
            assert "Active Issues: 3" in result.output
            assert "Cost Per Issue: €15.00" in result.output

    def test_cli_show_allocations_command(self, temp_db, setup_test_data):
        """Test CLI show allocations command."""
        from markitect.finance.cli import show_allocations
        from click.testing import CliRunner

        # First perform allocation
        allocation_engine = AllocationEngine(temp_db)
        allocation_engine.allocate_period_costs(setup_test_data)

        runner = CliRunner()

        # Mock configuration manager
        with patch('markitect.finance.cli.ConfigurationManager') as mock_config_manager:
            mock_config = Mock()
            mock_config.get_current_config.return_value = {'database_path': temp_db}
            mock_config_manager.return_value = mock_config

            # Test issue allocations
            result = runner.invoke(show_allocations, ['issue:501'])

            assert result.exit_code == 0
            assert "Cost Allocations for Issue #501" in result.output
            assert "€15.00" in result.output

            # Test period allocations
            result = runner.invoke(show_allocations, [f'period:{setup_test_data}'])

            assert result.exit_code == 0
            assert f"Cost Allocations for Period {setup_test_data}" in result.output
            assert "Total: 3 allocations" in result.output


class TestAllocationEdgeCases:
    """Test edge cases and error conditions."""

    @pytest.fixture
    def temp_db(self):
        """Create temporary database for testing."""
        with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
            db_path = f.name

        # Initialize schema
        finance_models = FinanceModels(db_path)
        finance_models.initialize_finance_schema()

        yield db_path

        # Cleanup
        Path(db_path).unlink(missing_ok=True)

    def test_allocation_with_very_small_amounts(self, temp_db):
        """Test allocation with very small cost amounts."""
        # Create tiny cost
        cost_manager = CostItemManager(temp_db)
        category_id = cost_manager.create_category("Test", "Test")
        create_test_cost_item(
            cost_manager, "Tiny Cost", category_id, "one_time",
            Decimal('0.01'), date(2025, 10, 15)  # 1 cent
        )

        # Create period and activities
        period_manager = PeriodManager(temp_db)
        period_id = period_manager.create_period(
            period_start=date(2025, 10, 1),
            period_end=date(2025, 10, 31)
        )

        activity_tracker = IssueActivityTracker(temp_db)
        activity_tracker.log_activity(
            issue_id=601,
            activity_type=ActivityType.CREATED,
            period_id=period_id
        )

        activity_tracker.log_activity(
            issue_id=602,
            activity_type=ActivityType.MODIFIED,
            period_id=period_id
        )

        activity_tracker.log_activity(
            issue_id=603,
            activity_type=ActivityType.COMMENTED,
            period_id=period_id
        )

        # Perform allocation
        allocation_engine = AllocationEngine(temp_db)
        result = allocation_engine.allocate_period_costs(period_id)

        assert result.status == AllocationStatus.SUCCESS
        assert result.total_costs == Decimal('0.01')
        # With 3 issues, each gets 0.01/3 = 0.0033... which should be handled properly
        expected_per_issue = Decimal('0.01') / 3
        assert abs(result.cost_per_issue - expected_per_issue) < Decimal('0.0001')

    def test_allocation_with_duplicate_activities_same_issue(self, temp_db):
        """Test that duplicate activities for same issue don't create multiple allocations."""
        # Create cost and period
        cost_manager = CostItemManager(temp_db)
        category_id = cost_manager.create_category("Test", "Test")
        create_test_cost_item(
            cost_manager, "Test Cost", category_id, "one_time",
            Decimal('30.00'), date(2025, 10, 15)
        )

        period_manager = PeriodManager(temp_db)
        period_id = period_manager.create_period(
            period_start=date(2025, 10, 1),
            period_end=date(2025, 10, 31)
        )

        # Create multiple activities for same issue
        activity_tracker = IssueActivityTracker(temp_db)
        activity_tracker.log_activity(
            issue_id=701,
            activity_type=ActivityType.CREATED,
            activity_date=date(2025, 10, 5),
            period_id=period_id
        )

        activity_tracker.log_activity(
            issue_id=701,  # Same issue
            activity_type=ActivityType.MODIFIED,
            activity_date=date(2025, 10, 10),
            period_id=period_id
        )

        activity_tracker.log_activity(
            issue_id=701,  # Same issue again
            activity_type=ActivityType.COMMENTED,
            activity_date=date(2025, 10, 15),
            period_id=period_id
        )

        # Perform allocation
        allocation_engine = AllocationEngine(temp_db)
        result = allocation_engine.allocate_period_costs(period_id)

        assert result.status == AllocationStatus.SUCCESS
        assert len(result.active_issues) == 1  # Only one unique issue
        assert result.active_issues[0] == 701
        assert result.cost_per_issue == Decimal('30.00')  # Full amount to single issue
        assert result.allocations_created == 1  # Only one allocation

    def test_database_constraint_violations(self, temp_db):
        """Test handling of database constraint violations."""
        allocation_engine = AllocationEngine(temp_db)

        # Try to create duplicate allocation manually
        with allocation_engine.finance_models.get_connection() as conn:
            cursor = conn.cursor()

            # Create period first
            cursor.execute('''
                INSERT INTO cost_periods (period_start, period_end)
                VALUES ('2025-10-01', '2025-10-31')
            ''')
            period_id = cursor.lastrowid

            # Create first allocation
            cursor.execute('''
                INSERT INTO issue_cost_allocations
                (issue_id, period_id, allocated_amount, allocation_date)
                VALUES (801, ?, 10.00, '2025-10-15')
            ''', (period_id,))

            # Try to create duplicate (should fail due to unique constraint)
            with pytest.raises(sqlite3.IntegrityError):
                cursor.execute('''
                    INSERT INTO issue_cost_allocations
                    (issue_id, period_id, allocated_amount, allocation_date)
                    VALUES (801, ?, 20.00, '2025-10-16')
                ''', (period_id,))


if __name__ == '__main__':
    pytest.main([__file__])