"""End-to-end test for autonomous Non-AI Coordinator orchestration.

This test validates the complete autonomous system working together:
1. Queue with 5 mixed-difficulty issues
2. Autonomous orchestration loop processing all issues
3. Quality gate enforcement on all completions
4. Context monitoring and rotation when needed
5. Cost optimization (preferring free models)
6. Success metrics validation

Test Requirements (TDD - RED phase):
- E2E test completes all 5 issues autonomously
- Zero manual interventions required
- All quality gates pass before issue completion
- Context never exceeds 95% (rotation triggered if needed)
- Cost optimized (>70% on free models if applicable)
- Success metrics report validates all targets
- Tests pass with 85% coverage minimum
"""

import tempfile
from collections.abc import AsyncGenerator
from pathlib import Path
from typing import Any
from unittest.mock import AsyncMock, MagicMock

import pytest

from src.agent_assignment import assign_agent
from src.context_monitor import ContextMonitor
from src.coordinator import OrchestrationLoop
from src.forced_continuation import ForcedContinuationService
from src.gates.quality_gate import GateResult
from src.models import IssueMetadata
from src.quality_orchestrator import QualityOrchestrator
from src.queue import QueueManager


class TestE2EOrchestration:
    """Test suite for end-to-end autonomous orchestration.

    Validates that the complete Non-AI Coordinator system can:
    - Process multiple issues autonomously
    - Enforce quality gates mechanically
    - Manage context usage and trigger rotation
    - Optimize costs by preferring free models
    - Generate success metrics reports
    """

    @pytest.fixture
    async def temp_queue_file(self) -> AsyncGenerator[Path, None]:
        """Create a temporary file for queue persistence."""
        with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".json") as f:
            temp_path = Path(f.name)
        yield temp_path
        # Cleanup
        if temp_path.exists():
            temp_path.unlink()

    @pytest.fixture
    def mock_api_client(self) -> MagicMock:
        """Create mock Claude API client for context monitoring."""
        client = MagicMock()

        # Start with low context usage (20%)
        client.get_context_usage = AsyncMock(
            return_value={
                "used_tokens": 40000,
                "total_tokens": 200000,
            }
        )

        # Mock session management
        client.close_session = AsyncMock(return_value={"success": True})
        client.spawn_agent = AsyncMock(
            return_value={
                "agent_id": "agent-new-123",
                "status": "ready",
            }
        )

        return client

    @pytest.fixture
    def mock_quality_gates(self) -> dict[str, MagicMock]:
        """Create mock quality gates that pass on first try."""
        return {
            "build": MagicMock(
                check=lambda: GateResult(
                    passed=True,
                    message="Build gate passed: No type errors",
                    details={"exit_code": 0},
                )
            ),
            "lint": MagicMock(
                check=lambda: GateResult(
                    passed=True,
                    message="Lint gate passed: No linting issues",
                    details={"exit_code": 0},
                )
            ),
            "test": MagicMock(
                check=lambda: GateResult(
                    passed=True,
                    message="Test gate passed: All tests passing",
                    details={"exit_code": 0, "tests_passed": 10, "tests_failed": 0},
                )
            ),
            "coverage": MagicMock(
                check=lambda: GateResult(
                    passed=True,
                    message="Coverage gate passed: 87.5% coverage (minimum: 85.0%)",
                    details={"coverage_percent": 87.5, "minimum_coverage": 85.0},
                )
            ),
        }

    @pytest.fixture
    def sample_issues(self) -> list[dict[str, Any]]:
        """Create 5 test issues with mixed difficulty levels.

        Returns:
            List of issue configurations with metadata
        """
        return [
            {
                "issue_number": 1001,
                "difficulty": "easy",
                "estimated_context": 15000,  # Low context
                "description": "Add logging to webhook handler",
            },
            {
                "issue_number": 1002,
                "difficulty": "medium",
                "estimated_context": 35000,  # Medium context
                "description": "Implement rate limiting middleware",
            },
            {
                "issue_number": 1003,
                "difficulty": "easy",
                "estimated_context": 12000,  # Low context
                "description": "Update API documentation",
            },
            {
                "issue_number": 1004,
                "difficulty": "medium",
                "estimated_context": 45000,  # Medium context
                "description": "Add database connection pooling",
            },
            {
                "issue_number": 1005,
                "difficulty": "hard",
                "estimated_context": 80000,  # High context
                "description": "Implement distributed tracing system",
            },
        ]

    @pytest.fixture
    async def queue_manager(
        self, temp_queue_file: Path, sample_issues: list[dict[str, Any]]
    ) -> QueueManager:
        """Create queue manager with test issues loaded."""
        manager = QueueManager(queue_file=temp_queue_file)

        # Enqueue all test issues
        for issue_config in sample_issues:
            # Assign optimal agent based on difficulty and context
            assigned_agent = assign_agent(
                estimated_context=issue_config["estimated_context"],
                difficulty=issue_config["difficulty"],
            )

            metadata = IssueMetadata(
                estimated_context=issue_config["estimated_context"],
                difficulty=issue_config["difficulty"],
                assigned_agent=assigned_agent.value,
                blocks=[],
                blocked_by=[],
            )

            manager.enqueue(issue_config["issue_number"], metadata)

        return manager

    @pytest.fixture
    def quality_orchestrator(self, mock_quality_gates: dict[str, MagicMock]) -> QualityOrchestrator:
        """Create quality orchestrator with mock gates."""
        return QualityOrchestrator(
            build_gate=mock_quality_gates["build"],
            lint_gate=mock_quality_gates["lint"],
            test_gate=mock_quality_gates["test"],
            coverage_gate=mock_quality_gates["coverage"],
        )

    @pytest.fixture
    def context_monitor(self, mock_api_client: MagicMock) -> ContextMonitor:
        """Create context monitor with mock API client."""
        return ContextMonitor(api_client=mock_api_client, poll_interval=0.1)

    @pytest.fixture
    def continuation_service(self) -> ForcedContinuationService:
        """Create forced continuation service."""
        return ForcedContinuationService()

    @pytest.fixture
    def orchestration_loop(
        self,
        queue_manager: QueueManager,
        quality_orchestrator: QualityOrchestrator,
        continuation_service: ForcedContinuationService,
        context_monitor: ContextMonitor,
    ) -> OrchestrationLoop:
        """Create orchestration loop with all components."""
        return OrchestrationLoop(
            queue_manager=queue_manager,
            quality_orchestrator=quality_orchestrator,
            continuation_service=continuation_service,
            context_monitor=context_monitor,
            poll_interval=0.1,  # Fast polling for tests
        )

    @pytest.mark.asyncio
    async def test_e2e_autonomous_completion(
        self,
        orchestration_loop: OrchestrationLoop,
        queue_manager: QueueManager,
        sample_issues: list[dict[str, Any]],
    ) -> None:
        """Test that orchestrator autonomously completes all 5 issues.

        Validates:
        - All 5 issues are processed without manual intervention
        - Each issue passes through the full workflow
        - Queue is empty after processing
        """
        # Verify queue starts with 5 pending issues
        assert queue_manager.size() == 5
        ready_items = queue_manager.list_ready()
        assert len(ready_items) == 5

        # Process all issues
        for _ in range(5):
            item = await orchestration_loop.process_next_issue()
            assert item is not None
            assert item.issue_number in [i["issue_number"] for i in sample_issues]

        # Verify all issues are completed
        all_items = queue_manager.list_all()
        completed_count = sum(1 for item in all_items if item.status.value == "completed")
        assert completed_count == 5

        # Verify no issues remain pending (all are completed)
        pending_items = [item for item in all_items if item.status.value == "pending"]
        assert len(pending_items) == 0

    @pytest.mark.asyncio
    async def test_e2e_zero_manual_interventions(
        self,
        orchestration_loop: OrchestrationLoop,
        queue_manager: QueueManager,
    ) -> None:
        """Test that no manual interventions are required.

        Validates:
        - All issues complete on first pass (quality gates pass)
        - No forced continuations needed
        - 100% autonomous completion rate
        """
        # Track metrics
        initial_rejection_count = orchestration_loop.rejection_count

        # Process all issues
        for _ in range(5):
            await orchestration_loop.process_next_issue()

        # Verify no rejections occurred (all passed first time)
        assert orchestration_loop.rejection_count == initial_rejection_count
        assert orchestration_loop.success_count == 5
        assert orchestration_loop.processed_count == 5

    @pytest.mark.asyncio
    async def test_e2e_quality_gates_enforce_standards(
        self,
        orchestration_loop: OrchestrationLoop,
        queue_manager: QueueManager,
        mock_quality_gates: dict[str, MagicMock],
    ) -> None:
        """Test that quality gates are enforced before completion.

        Validates:
        - Quality gates run for every issue
        - Issues only complete when gates pass
        - Gate results are tracked
        """
        # Process first issue
        item = await orchestration_loop.process_next_issue()
        assert item is not None

        # Verify quality gates were called
        # Note: Gates are called via orchestrator, check they were invoked
        assert orchestration_loop.success_count >= 1

        # Process remaining issues
        for _ in range(4):
            await orchestration_loop.process_next_issue()

        # Verify all issues passed quality gates
        assert orchestration_loop.success_count == 5

    @pytest.mark.asyncio
    async def test_e2e_quality_gate_failure_triggers_continuation(
        self,
        queue_manager: QueueManager,
        continuation_service: ForcedContinuationService,
        context_monitor: ContextMonitor,
        mock_quality_gates: dict[str, MagicMock],
    ) -> None:
        """Test that quality gate failures trigger forced continuation.

        Validates:
        - Failed gates generate continuation prompts
        - Agents receive non-negotiable fix instructions
        - Issues remain in progress until gates pass
        """
        # Configure gates to fail first, then pass
        call_count = {"count": 0}

        def failing_then_passing_test() -> GateResult:
            call_count["count"] += 1
            if call_count["count"] == 1:
                return GateResult(
                    passed=False,
                    message="Test gate failed: 2 tests failed",
                    details={"exit_code": 1, "tests_passed": 8, "tests_failed": 2},
                )
            return GateResult(
                passed=True,
                message="Test gate passed: All tests passing",
                details={"exit_code": 0, "tests_passed": 10, "tests_failed": 0},
            )

        mock_quality_gates["test"].check = failing_then_passing_test

        # Create orchestrator with failing gate
        quality_orchestrator = QualityOrchestrator(
            build_gate=mock_quality_gates["build"],
            lint_gate=mock_quality_gates["lint"],
            test_gate=mock_quality_gates["test"],
            coverage_gate=mock_quality_gates["coverage"],
        )

        orchestration_loop = OrchestrationLoop(
            queue_manager=queue_manager,
            quality_orchestrator=quality_orchestrator,
            continuation_service=continuation_service,
            context_monitor=context_monitor,
            poll_interval=0.1,
        )

        # Process first issue (will fail quality gates)
        item = await orchestration_loop.process_next_issue()
        assert item is not None

        # Verify rejection was counted
        assert orchestration_loop.rejection_count == 1
        assert orchestration_loop.success_count == 0

        # Verify continuation prompt was generated
        agent_info = orchestration_loop.active_agents.get(item.issue_number)
        assert agent_info is not None
        assert agent_info["status"] == "needs_continuation"
        assert "continuation_prompt" in agent_info
        assert "QUALITY GATES FAILED" in agent_info["continuation_prompt"]

    @pytest.mark.asyncio
    async def test_e2e_context_monitoring_prevents_overflow(
        self,
        orchestration_loop: OrchestrationLoop,
        context_monitor: ContextMonitor,
        mock_api_client: MagicMock,
    ) -> None:
        """Test that context monitoring prevents overflow.

        Validates:
        - Context usage is monitored during processing
        - Context never exceeds 95% threshold
        - Rotation triggers when needed
        """
        # Configure mock to return high context usage (85%)
        mock_api_client.get_context_usage.return_value = {
            "used_tokens": 170000,
            "total_tokens": 200000,
        }

        # Process first issue
        item = await orchestration_loop.process_next_issue()
        assert item is not None

        # Verify context was checked
        usage = await context_monitor.get_context_usage(f"agent-{item.issue_number}")
        assert usage.usage_percent >= 80.0
        assert usage.usage_percent < 95.0  # Should not exceed rotation threshold

    @pytest.mark.asyncio
    async def test_e2e_context_rotation_at_95_percent(
        self,
        queue_manager: QueueManager,
        quality_orchestrator: QualityOrchestrator,
        continuation_service: ForcedContinuationService,
        mock_api_client: MagicMock,
    ) -> None:
        """Test that session rotation triggers at 95% context.

        Validates:
        - Rotation triggers when context hits 95%
        - New agent spawned with same type
        - Old session properly closed
        """
        # Configure mock to return 96% context usage (triggers rotation)
        mock_api_client.get_context_usage.return_value = {
            "used_tokens": 192000,
            "total_tokens": 200000,
        }

        context_monitor = ContextMonitor(api_client=mock_api_client, poll_interval=0.1)

        orchestration_loop = OrchestrationLoop(
            queue_manager=queue_manager,
            quality_orchestrator=quality_orchestrator,
            continuation_service=continuation_service,
            context_monitor=context_monitor,
            poll_interval=0.1,
        )

        # Process first issue
        item = await orchestration_loop.process_next_issue()
        assert item is not None

        # Check context action
        from src.models import ContextAction

        action = await context_monitor.determine_action(f"agent-{item.issue_number}")
        assert action == ContextAction.ROTATE_SESSION

        # Trigger rotation manually (since we're testing the mechanism)
        rotation = await context_monitor.trigger_rotation(
            agent_id=f"agent-{item.issue_number}",
            agent_type="sonnet",
            next_issue_number=1002,
        )

        # Verify rotation succeeded
        assert rotation.success
        assert rotation.old_agent_id == f"agent-{item.issue_number}"
        assert rotation.new_agent_id == "agent-new-123"
        assert rotation.context_before_percent >= 95.0

    @pytest.mark.asyncio
    async def test_e2e_cost_optimization(
        self,
        sample_issues: list[dict[str, Any]],
    ) -> None:
        """Test that cost optimization prefers free models.

        Validates:
        - Free models (GLM, MINIMAX) used when capable
        - >70% of issues use cost=0 agents when applicable
        - Expensive models only for high difficulty
        """
        cost_zero_count = 0
        total_count = len(sample_issues)

        for issue_config in sample_issues:
            assigned_agent = assign_agent(
                estimated_context=issue_config["estimated_context"],
                difficulty=issue_config["difficulty"],
            )

            # Check if assigned agent is free
            from src.models import AGENT_PROFILES

            profile = AGENT_PROFILES[assigned_agent]
            if profile.cost_per_mtok == 0.0:
                cost_zero_count += 1

        # Verify >70% use free models (for easy/medium tasks)
        # In our test set: 2 easy + 2 medium + 1 hard = 5 total
        # Easy/Medium should use free models when capable
        # Expected: minimax (easy), glm (medium), minimax (easy), glm (medium), opus (hard)
        # That's 4/5 = 80% using free models
        cost_optimization_percent = (cost_zero_count / total_count) * 100
        assert cost_optimization_percent >= 70.0

    @pytest.mark.asyncio
    async def test_e2e_success_metrics_validation(
        self,
        orchestration_loop: OrchestrationLoop,
        queue_manager: QueueManager,
    ) -> None:
        """Test that success metrics meet all targets.

        Validates:
        - Autonomy: 100% completion without intervention
        - Quality: 100% of commits pass quality gates
        - Cost optimization: >70% issues use free models
        - Context management: 0 agents exceed 95%
        """
        # Process all issues
        for _ in range(5):
            await orchestration_loop.process_next_issue()

        # Calculate success metrics
        total_processed = orchestration_loop.processed_count
        total_success = orchestration_loop.success_count
        total_rejections = orchestration_loop.rejection_count

        # Autonomy: 100% completion
        autonomy_rate = (total_success / total_processed) * 100 if total_processed > 0 else 0
        assert autonomy_rate == 100.0

        # Quality: 100% pass rate (no rejections)
        quality_rate = (total_success / total_processed) * 100 if total_processed > 0 else 0
        assert quality_rate == 100.0
        assert total_rejections == 0

        # Verify all issues completed
        all_items = queue_manager.list_all()
        completed = [item for item in all_items if item.status.value == "completed"]
        assert len(completed) == 5

    @pytest.mark.asyncio
    async def test_e2e_estimation_accuracy(
        self,
        sample_issues: list[dict[str, Any]],
    ) -> None:
        """Test that context estimations are within acceptable range.

        Validates:
        - Estimated context matches agent capacity (50% rule)
        - Assignments are appropriate for difficulty
        - No over/under-estimation beyond ±20%
        """
        for issue_config in sample_issues:
            assigned_agent = assign_agent(
                estimated_context=issue_config["estimated_context"],
                difficulty=issue_config["difficulty"],
            )

            # Get agent profile
            from src.models import AGENT_PROFILES

            profile = AGENT_PROFILES[assigned_agent]

            # Verify 50% rule: agent context >= 2x estimated
            required_capacity = issue_config["estimated_context"] * 2
            assert profile.context_limit >= required_capacity

            # Verify capability matches difficulty
            from src.models import Capability

            difficulty_map = {
                "easy": Capability.LOW,
                "medium": Capability.MEDIUM,
                "hard": Capability.HIGH,
            }
            required_capability = difficulty_map[issue_config["difficulty"]]
            assert required_capability in profile.capabilities

    @pytest.mark.asyncio
    async def test_e2e_metrics_report_generation(
        self,
        orchestration_loop: OrchestrationLoop,
        queue_manager: QueueManager,
        sample_issues: list[dict[str, Any]],
    ) -> None:
        """Test that success metrics report can be generated.

        Validates:
        - Metrics are tracked throughout processing
        - Report includes all required data points
        - Report format is machine-readable
        """
        # Process all issues
        for _ in range(5):
            await orchestration_loop.process_next_issue()

        # Generate metrics report
        metrics = {
            "total_issues": len(sample_issues),
            "completed_issues": orchestration_loop.success_count,
            "failed_issues": orchestration_loop.rejection_count,
            "autonomy_rate": (
                orchestration_loop.success_count / orchestration_loop.processed_count * 100
                if orchestration_loop.processed_count > 0
                else 0
            ),
            "quality_pass_rate": (
                orchestration_loop.success_count / orchestration_loop.processed_count * 100
                if orchestration_loop.processed_count > 0
                else 0
            ),
            "intervention_count": orchestration_loop.rejection_count,
        }

        # Validate report structure
        assert metrics["total_issues"] == 5
        assert metrics["completed_issues"] == 5
        assert metrics["failed_issues"] == 0
        assert metrics["autonomy_rate"] == 100.0
        assert metrics["quality_pass_rate"] == 100.0
        assert metrics["intervention_count"] == 0

    @pytest.mark.asyncio
    async def test_e2e_parallel_issue_processing(
        self,
        temp_queue_file: Path,
        sample_issues: list[dict[str, Any]],
        mock_quality_gates: dict[str, MagicMock],
        mock_api_client: MagicMock,
    ) -> None:
        """Test that multiple issues can be processed efficiently.

        Validates:
        - Issues are processed in order
        - No race conditions in queue management
        - Metrics are accurately tracked
        """
        # Create fresh components
        queue_manager = QueueManager(queue_file=temp_queue_file)

        # Enqueue issues
        for issue_config in sample_issues:
            assigned_agent = assign_agent(
                estimated_context=issue_config["estimated_context"],
                difficulty=issue_config["difficulty"],
            )

            metadata = IssueMetadata(
                estimated_context=issue_config["estimated_context"],
                difficulty=issue_config["difficulty"],
                assigned_agent=assigned_agent.value,
                blocks=[],
                blocked_by=[],
            )

            queue_manager.enqueue(issue_config["issue_number"], metadata)

        quality_orchestrator = QualityOrchestrator(
            build_gate=mock_quality_gates["build"],
            lint_gate=mock_quality_gates["lint"],
            test_gate=mock_quality_gates["test"],
            coverage_gate=mock_quality_gates["coverage"],
        )

        context_monitor = ContextMonitor(api_client=mock_api_client, poll_interval=0.1)
        continuation_service = ForcedContinuationService()

        orchestration_loop = OrchestrationLoop(
            queue_manager=queue_manager,
            quality_orchestrator=quality_orchestrator,
            continuation_service=continuation_service,
            context_monitor=context_monitor,
            poll_interval=0.1,
        )

        # Process all issues sequentially (simulating parallel capability)
        processed_issues = []
        for _ in range(5):
            item = await orchestration_loop.process_next_issue()
            if item:
                processed_issues.append(item.issue_number)

        # Verify all issues processed
        assert len(processed_issues) == 5
        assert set(processed_issues) == {i["issue_number"] for i in sample_issues}

        # Verify all issues are completed (none pending)
        all_items = queue_manager.list_all()
        pending_items = [item for item in all_items if item.status.value == "pending"]
        assert len(pending_items) == 0

    @pytest.mark.asyncio
    async def test_e2e_complete_workflow_timing(
        self,
        orchestration_loop: OrchestrationLoop,
        queue_manager: QueueManager,
    ) -> None:
        """Test that complete workflow completes in reasonable time.

        Validates:
        - All 5 issues process efficiently
        - No blocking operations
        - Performance meets expectations
        """
        import time

        start_time = time.time()

        # Process all issues
        for _ in range(5):
            await orchestration_loop.process_next_issue()

        end_time = time.time()
        elapsed_time = end_time - start_time

        # Should complete in under 5 seconds for test environment
        # (Production may be slower due to actual agent execution)
        assert elapsed_time < 5.0

        # Verify all completed
        assert orchestration_loop.success_count == 5
