#!/usr/bin/env python3
"""
Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com

Tests for CommandExecutionJob progress behavior.
"""

from __future__ import annotations

import asyncio
import uuid
from typing import Any, Dict, List

import pytest

from mcp_proxy_adapter.commands.base import Command, CommandResult
from mcp_proxy_adapter.commands.command_registry import registry
from mcp_proxy_adapter.commands.queue.jobs import CommandExecutionJob
from mcp_proxy_adapter.integrations.queuemgr_integration import (
    QueueJobResult,
    QueueManagerIntegration,
)


class NoProgressRegularCommand(Command):
    """Command without duration/steps; should not force progress=50."""

    name = "no_progress_regular_command"
    descr = "Regular queued command without explicit progress updates"
    use_queue = True
    result_class = CommandResult

    async def execute(self, **kwargs: Any) -> CommandResult:
        """Simulate regular work without progress tracker updates."""
        await asyncio.sleep(1.2)
        return CommandResult(success=True, data={"mode": "regular"})

    @classmethod
    def get_schema(cls) -> Dict[str, Any]:
        """Return permissive schema for tests."""
        return {"type": "object", "properties": {}, "additionalProperties": True}


class DurationProgressProbeCommand(Command):
    """Command for duration/steps branch progress tracking."""

    name = "duration_progress_probe_command"
    descr = "Duration-based queued command for progress verification"
    use_queue = True
    result_class = CommandResult

    async def execute(
        self,
        duration: int = 4,
        steps: int = 8,
        **kwargs: Any,
    ) -> CommandResult:
        """Run for a while so CommandExecutionJob can update progress."""
        step_sleep = max(0.1, float(duration) / float(steps))
        for _ in range(steps):
            await asyncio.sleep(step_sleep)
        return CommandResult(success=True, data={"mode": "duration"})

    @classmethod
    def get_schema(cls) -> Dict[str, Any]:
        """Return schema with duration/steps fields."""
        return {
            "type": "object",
            "properties": {
                "duration": {"type": "integer", "minimum": 1, "default": 4},
                "steps": {"type": "integer", "minimum": 1, "default": 8},
            },
            "additionalProperties": True,
        }


def _safe_register_test_command(command_cls: type[Command]) -> None:
    """Register command if absent."""
    try:
        registry.get_command(command_cls.name)
    except KeyError:
        registry.register(command_cls, "custom")


def _auto_register_progress_test_commands() -> None:
    """Auto-register test commands when module is imported in child process."""
    _safe_register_test_command(NoProgressRegularCommand)
    _safe_register_test_command(DurationProgressProbeCommand)


_auto_register_progress_test_commands()


async def _collect_job_statuses(
    queue_manager: QueueManagerIntegration,
    job_id: str,
    timeout_seconds: float = 20.0,
    poll_interval_seconds: float = 0.2,
) -> List[QueueJobResult]:
    """Collect job status snapshots until terminal state."""
    samples: List[QueueJobResult] = []
    elapsed = 0.0
    while elapsed < timeout_seconds:
        status_obj = await queue_manager.get_job_status(job_id)
        samples.append(status_obj)
        if status_obj.status in ("completed", "failed", "stopped", "deleted"):
            break
        await asyncio.sleep(poll_interval_seconds)
        elapsed += poll_interval_seconds
    return samples


@pytest.mark.asyncio
async def test_regular_command_does_not_report_forced_fifty_progress() -> None:
    """Regular branch must not emit artificial progress=50."""
    _safe_register_test_command(NoProgressRegularCommand)

    queue_manager = QueueManagerIntegration(in_memory=True, max_concurrent_jobs=2)
    await queue_manager.start()
    try:
        job_id = f"progress-regular-{uuid.uuid4()}"
        job_params = {
            "command": NoProgressRegularCommand.name,
            "params": {},
            "context": {},
            "auto_import_modules": ["tests.test_command_execution_job_progress"],
        }
        await queue_manager.add_job(CommandExecutionJob, job_id, job_params)
        await queue_manager.start_job(job_id)

        samples = await _collect_job_statuses(queue_manager, job_id)
        assert samples, "Expected at least one status sample"
        assert samples[-1].status == "completed", "Job should complete successfully"

        progress_values = [sample.progress for sample in samples]
        assert (
            50 not in progress_values
        ), "Artificial progress=50 detected in regular command path"
        assert all(0 <= p <= 100 for p in progress_values), "Progress out of range"
    finally:
        await queue_manager.stop()


@pytest.mark.asyncio
async def test_duration_branch_reports_increasing_progress() -> None:
    """Duration/steps branch should report multiple increasing progress values."""
    _safe_register_test_command(DurationProgressProbeCommand)

    queue_manager = QueueManagerIntegration(in_memory=True, max_concurrent_jobs=2)
    await queue_manager.start()
    try:
        job_id = f"progress-duration-{uuid.uuid4()}"
        job_params = {
            "command": DurationProgressProbeCommand.name,
            "params": {"duration": 4, "steps": 8},
            "context": {},
            "auto_import_modules": ["tests.test_command_execution_job_progress"],
        }
        await queue_manager.add_job(CommandExecutionJob, job_id, job_params)
        await queue_manager.start_job(job_id)

        samples = await _collect_job_statuses(queue_manager, job_id)
        assert samples, "Expected at least one status sample"
        assert samples[-1].status == "completed", "Job should complete successfully"

        progress_values = [sample.progress for sample in samples]
        intermediate = [p for p in progress_values if 0 < p < 100]
        unique_intermediate = sorted(set(intermediate))
        assert (
            len(unique_intermediate) >= 2
        ), "Expected multiple intermediate progress updates in duration branch"
        assert progress_values[-1] == 100, "Final progress must be 100"
    finally:
        await queue_manager.stop()
