from __future__ import annotations

import json

from sqlalchemy import select, text

from services.shared.core import utc_now_iso
from services.shared.sql_models import AgentRow


def parse_list_json(raw: str | None) -> list[str]:
    if not raw:
        return []
    try:
        data = json.loads(raw)
    except (TypeError, ValueError):
        return []
    if isinstance(data, list):
        return [str(item) for item in data]
    return []


def _agent_matches(
    agent: AgentRow,
    *,
    level: str,
    tenant_id: str | None,
    required_skills: list[str],
    exclude_agent_ids: set[str],
) -> bool:
    if agent.agent_id in exclude_agent_ids:
        return False
    if not agent.enabled:
        return False
    if agent.level != level:
        return False
    if agent.status != "AVAILABLE":
        return False
    tenant_ids = parse_list_json(agent.tenant_ids_json)
    if tenant_id and tenant_ids and tenant_id not in tenant_ids:
        return False
    skills = set(parse_list_json(agent.skills_json))
    if required_skills and not set(required_skills).issubset(skills):
        return False
    return True


def select_candidate_agents(
    session,
    *,
    level: str,
    tenant_id: str | None,
    required_skills: list[str] | None = None,
    exclude_agent_ids: list[str] | None = None,
) -> list[AgentRow]:
    exclude = set(exclude_agent_ids or [])
    skills = required_skills or []
    rows = session.execute(
        select(AgentRow).where(AgentRow.level == level, AgentRow.enabled.is_(True))
    ).scalars().all()
    candidates = [
        row
        for row in rows
        if _agent_matches(row, level=level, tenant_id=tenant_id, required_skills=skills, exclude_agent_ids=exclude)
    ]
    candidates.sort(key=lambda a: (a.updated_at, a.calls_handled_count))
    return candidates


def reserve_agent(
    session,
    *,
    call_id: str,
    level: str,
    tenant_id: str | None,
    required_skills: list[str] | None = None,
    exclude_agent_ids: list[str] | None = None,
) -> AgentRow | None:
    candidates = select_candidate_agents(
        session,
        level=level,
        tenant_id=tenant_id,
        required_skills=required_skills,
        exclude_agent_ids=exclude_agent_ids,
    )
    now = utc_now_iso()
    for candidate in candidates:
        result = session.execute(
            text(
                """
                UPDATE agents SET status='RESERVED', current_call_id=:call_id, updated_at=:now
                WHERE agent_id=:agent_id AND status='AVAILABLE'
                """
            ),
            {"call_id": call_id, "now": now, "agent_id": candidate.agent_id},
        )
        if result.rowcount == 1:
            session.commit()
            session.refresh(candidate)
            return candidate
        session.rollback()
    return None


def release_agent_by_call_id(session, *, call_id: str) -> AgentRow | None:
    agent = session.execute(
        select(AgentRow).where(AgentRow.current_call_id == call_id)
    ).scalar_one_or_none()
    if agent is None:
        return None
    now = utc_now_iso()
    agent.status = "AVAILABLE"
    agent.current_call_id = None
    agent.calls_handled_count = (agent.calls_handled_count or 0) + 1
    agent.updated_at = now
    session.commit()
    return agent


def release_agent_by_id(session, *, agent_id: str, next_status: str = "AVAILABLE") -> AgentRow | None:
    agent = session.execute(
        select(AgentRow).where(AgentRow.agent_id == agent_id)
    ).scalar_one_or_none()
    if agent is None:
        return None
    now = utc_now_iso()
    agent.status = next_status
    agent.current_call_id = None
    agent.updated_at = now
    session.commit()
    return agent
