"""Task queue for intelligent evaluations (任务队列).

Platform scans executing evals every minute and enqueues tasks for
OpenClaw workers to pick up. Tasks are prioritized by:
1. Time slot due (时段到期)
2. Session deficit (欠账多)
3. Wait time (等待时间长)
"""

from datetime import datetime, timedelta
from typing import Optional

from sqlmodel import Session, select

from agenteval.intelligent_eval.models import IntelligentEval, IntelligentEvalStatus
from agenteval.storage.db import (
    IntelligentEvalDB,
    IntelligentEvalSessionDB,
    IntelligentEvalTaskQueueDB,
    as_utc,
    utc_now,
)


def _is_slot_due(slot: dict, current_offset: timedelta) -> bool:
    """Check if a time slot is due (时段到期).

    Args:
        slot: Time slot from plan.time_distribution (e.g., {"time_slot": "8-10h", "sessions": 2})
        current_offset: Time elapsed since eval started

    Returns:
        True if the slot's start time has passed
    """
    time_slot = slot.get("time_slot", "")
    if not time_slot:
        return False

    # Parse time slot (e.g., "8-10h" -> 8 hours)
    try:
        start_hour = int(time_slot.split("-")[0].replace("h", ""))
        slot_start = timedelta(hours=start_hour)
        return current_offset >= slot_start
    except (ValueError, IndexError):
        return False


def _calculate_session_deficit(eval_db: IntelligentEvalDB, session: Session) -> int:
    """Calculate session deficit (欠账).

    Returns:
        Number of sessions that should exist but don't
    """
    if not eval_db.plan:
        return 0

    plan = eval_db.get_plan()
    time_distribution = plan.get("time_distribution", [])
    estimated_sessions = plan.get("estimated_sessions", 0)

    # Count current sessions
    current_sessions = session.exec(
        select(IntelligentEvalSessionDB).where(IntelligentEvalSessionDB.eval_id == eval_db.id)
    ).all()
    current_count = len(current_sessions)

    # Calculate how many sessions should exist by now
    current_offset = utc_now() - as_utc(eval_db.started_at) if eval_db.started_at else timedelta(0)
    should_have = 0

    for slot in time_distribution:
        if _is_slot_due(slot, current_offset):
            should_have += slot.get("sessions", 0)

    # Deficit = should have - current
    deficit = max(0, should_have - current_count)
    return deficit


def _calculate_priority(eval_db: IntelligentEvalDB, session: Session) -> int:
    """Calculate task priority (越小越优先).

    Priority rules:
    - Base priority: 100
    - Time slot due: -50
    - Session deficit: -10 per session
    - Wait time: -1 per 10 minutes (max -20)
    """
    priority = 100

    # Check if any time slot is due
    if eval_db.plan and eval_db.started_at:
        plan = eval_db.get_plan()
        time_distribution = plan.get("time_distribution", [])
        current_offset = utc_now() - as_utc(eval_db.started_at)

        for slot in time_distribution:
            if _is_slot_due(slot, current_offset):
                priority -= 50
                break

    # Session deficit
    deficit = _calculate_session_deficit(eval_db, session)
    priority -= deficit * 10

    # Wait time
    if eval_db.started_at:
        wait_minutes = (utc_now() - as_utc(eval_db.started_at)).total_seconds() / 60
        priority -= min(int(wait_minutes / 10), 20)

    return max(priority, 1)


def _get_attention_reason(eval_db: IntelligentEvalDB, session: Session) -> Optional[str]:
    """Determine why this eval needs attention.

    Returns:
        Reason string, or None if no attention needed
    """
    if not eval_db.plan or not eval_db.started_at:
        return None

    plan = eval_db.get_plan()
    time_distribution = plan.get("time_distribution", [])
    current_offset = utc_now() - as_utc(eval_db.started_at)

    # Check if any time slot is due
    for slot in time_distribution:
        if _is_slot_due(slot, current_offset):
            deficit = _calculate_session_deficit(eval_db, session)
            if deficit > 0:
                return "slot_due"

    # Check if all sessions completed (need analysis)
    sessions = session.exec(
        select(IntelligentEvalSessionDB).where(IntelligentEvalSessionDB.eval_id == eval_db.id)
    ).all()

    if sessions and all(s.status == "completed" for s in sessions):
        estimated_sessions = plan.get("estimated_sessions", 0)
        if len(sessions) >= estimated_sessions:
            return "all_sessions_completed"

    return None


def _has_pending_task(eval_id: str, session: Session) -> bool:
    """Check if eval already has a pending task (去重)."""
    existing = session.exec(
        select(IntelligentEvalTaskQueueDB).where(
            IntelligentEvalTaskQueueDB.eval_id == eval_id,
            IntelligentEvalTaskQueueDB.status == "pending",
        )
    ).first()
    return existing is not None


def scan_and_enqueue_tasks(session: Session) -> int:
    """Scan all executing evals and enqueue tasks.

    Returns:
        Number of tasks enqueued
    """
    # Get all executing evals
    evals = session.exec(
        select(IntelligentEvalDB).where(IntelligentEvalDB.status == IntelligentEvalStatus.EXECUTING.value)
    ).all()

    enqueued = 0

    for eval_db in evals:
        # Check if eval needs attention
        reason = _get_attention_reason(eval_db, session)
        if reason is None:
            continue

        # Check if already has pending task (去重)
        if _has_pending_task(eval_db.id, session):
            continue

        # Calculate priority
        priority = _calculate_priority(eval_db, session)

        # Create task
        task = IntelligentEvalTaskQueueDB(
            eval_id=eval_db.id,
            status="pending",
            priority=priority,
            reason=reason,
            created_at=utc_now(),
            updated_at=utc_now(),
        )
        session.add(task)
        enqueued += 1

    session.commit()
    return enqueued


def get_next_task(session: Session) -> Optional[IntelligentEvalTaskQueueDB]:
    """Get next pending task (highest priority).

    Returns:
        Task with lowest priority value (highest priority), or None
    """
    task = session.exec(
        select(IntelligentEvalTaskQueueDB)
        .where(IntelligentEvalTaskQueueDB.status == "pending")
        .order_by(IntelligentEvalTaskQueueDB.priority, IntelligentEvalTaskQueueDB.created_at)
        .limit(1)
    ).first()
    return task


def assign_task(task_id: str, cron_id: str, session: Session) -> bool:
    """Assign a task to a cron.

    Returns:
        True if assigned successfully, False if task not found or already assigned
    """
    task = session.get(IntelligentEvalTaskQueueDB, task_id)
    if task is None or task.status != "pending":
        return False

    task.status = "assigned"
    task.assigned_cron_id = cron_id
    task.assigned_at = utc_now()
    task.updated_at = utc_now()
    session.commit()
    return True


def complete_task(task_id: str, success: bool, error: Optional[str], session: Session) -> bool:
    """Mark a task as completed or failed.

    Returns:
        True if completed successfully, False if task not found
    """
    task = session.get(IntelligentEvalTaskQueueDB, task_id)
    if task is None:
        return False

    task.status = "completed" if success else "failed"
    task.completed_at = utc_now()
    task.error = error
    task.updated_at = utc_now()
    session.commit()
    return True
