from __future__ import annotations

import json
import sys
from datetime import datetime
from typing import Any

from sqlalchemy import select

from services.shared.models import AsteriskEventOut, VoiceCallActionOut, VoiceLiveCallOut
from services.shared.sql_models import (
    AsteriskCallActionLogRow,
    AsteriskCallLinkRow,
    AsteriskEventLogRow,
    VoiceEventRow,
)


def _bridge_app():
    return sys.modules["services.asterisk_bridge_service.app"]


def to_out(row: AsteriskEventLogRow) -> AsteriskEventOut:
    return AsteriskEventOut(
        bridge_event_id=row.bridge_event_id,
        ami_event_name=row.ami_event_name,
        call_id=row.call_id,
        linked_id=row.linked_id,
        interaction_id=row.interaction_id,
        recording_id=row.recording_id,
        forward_status=row.forward_status,
        payload=json.loads(row.payload_json or "{}"),
        last_error=row.last_error,
        created_at=row.created_at,
        updated_at=row.updated_at,
    )


def action_to_out(row: AsteriskCallActionLogRow) -> VoiceCallActionOut:
    return VoiceCallActionOut(
        action_id=row.action_id,
        call_id=row.call_id,
        interaction_id=row.interaction_id,
        action_type=row.action_type,
        actor_user=row.actor_user,
        actor_role=row.actor_role,
        request=json.loads(row.request_json or "{}"),
        result_status=row.result_status,  # type: ignore[arg-type]
        ami_action_id=row.ami_action_id,
        error=row.error,
        created_at=row.created_at,
    )


def safe_json_loads(raw: str | None) -> dict[str, Any]:
    try:
        payload = json.loads(raw or "{}")
    except Exception:  # noqa: BLE001
        return {}
    return payload if isinstance(payload, dict) else {}


def latest_voice_event_row(
    session,
    *,
    call_id: str,
    event_type: str,
    include_reconciled: bool = True,
) -> VoiceEventRow | None:
    rows = session.execute(
        select(VoiceEventRow)
        .where(VoiceEventRow.call_id == call_id)
        .where(VoiceEventRow.event_type == event_type)
        .order_by(VoiceEventRow.id.desc())
        .limit(20)
    ).scalars().all()
    if include_reconciled:
        return rows[0] if rows else None
    for row in rows:
        payload = safe_json_loads(row.payload_json)
        if payload.get("reconciled"):
            continue
        return row
    return None


def latest_successful_action_row(
    session,
    *,
    call_id: str,
    action_types: tuple[str, ...],
) -> AsteriskCallActionLogRow | None:
    rows = session.execute(
        select(AsteriskCallActionLogRow)
        .where(AsteriskCallActionLogRow.call_id == call_id)
        .where(AsteriskCallActionLogRow.result_status == "ok")
        .order_by(AsteriskCallActionLogRow.id.desc())
        .limit(20)
    ).scalars().all()
    for row in rows:
        if row.action_type in action_types:
            return row
    return None


def latest_iso(*values: str | None) -> str | None:
    bridge = _bridge_app()
    parsed: list[tuple[datetime, str]] = []
    for value in values:
        iso = str(value or "").strip()
        dt = bridge._parse_iso(iso)
        if dt is None:
            continue
        parsed.append((dt, iso))
    if not parsed:
        return None
    parsed.sort(key=lambda item: item[0], reverse=True)
    return parsed[0][1]


def hangup_cause_for_call(session, *, call_id: str) -> str | None:
    row = latest_voice_event_row(
        session,
        call_id=call_id,
        event_type="call.ended",
        include_reconciled=False,
    ) or latest_voice_event_row(
        session,
        call_id=call_id,
        event_type="call.ended",
        include_reconciled=True,
    )
    if row is None:
        return None
    payload = safe_json_loads(row.payload_json)
    value = str(payload.get("hangup_cause") or "").strip()
    return value or None


def terminal_action_for_call(
    session,
    *,
    call_id: str,
    row: AsteriskCallLinkRow,
) -> tuple[str | None, str | None, str | None]:
    bridge = _bridge_app()
    action = latest_successful_action_row(
        session,
        call_id=call_id,
        action_types=("blind-transfer", "hangup"),
    )
    if action is not None:
        request_payload = safe_json_loads(action.request_json)
        target = None
        if action.action_type == "blind-transfer":
            target = str(
                request_payload.get("target_value")
                or request_payload.get("resolved_extension")
                or ""
            ).strip() or None
        return action.action_type, action.created_at, target
    if bridge._call_is_ended(row):
        return "ended", row.ended_at or row.updated_at, None
    return None, None, None


def live_call_to_out(session, row: AsteriskCallLinkRow) -> VoiceLiveCallOut:
    bridge = _bridge_app()
    has_recording = bridge._has_recording(session, call_id=row.call_id)
    terminal_action, terminal_at, terminal_target = terminal_action_for_call(
        session,
        call_id=row.call_id,
        row=row,
    )
    last_transition_at = latest_iso(
        terminal_at,
        row.connected_at,
        row.claimed_at,
        row.started_at,
        row.updated_at,
    )
    return VoiceLiveCallOut(
        call_id=row.call_id,
        interaction_id=row.interaction_id,
        queue_id=row.queue_id,
        queue_code=row.queue_code,
        caller_number=row.caller_number,
        caller_name=row.caller_name,
        status=row.status,
        telephony_status=row.telephony_status,  # type: ignore[arg-type]
        claimed_by_user=row.claimed_by_user,
        claimed_at=row.claimed_at,
        operator_extension=row.operator_extension,
        channel_name=row.channel_name,
        started_at=row.started_at,
        connected_at=row.connected_at,
        ended_at=row.ended_at,
        updated_at=row.updated_at,
        last_transition_at=last_transition_at,
        hangup_cause=hangup_cause_for_call(session, call_id=row.call_id),
        terminal_action=terminal_action,
        terminal_target=terminal_target,
        voice_session_id=row.voice_session_id,
        ai_session_id=row.ai_session_id,
        ai_state=row.ai_state,  # type: ignore[arg-type]
        ai_handoff_reason=row.ai_handoff_reason,
        ai_last_model_at=row.ai_last_model_at,
        has_recording=has_recording,
    )
