"""Shared minimum call duration checks for ingest, transcribe, and backlog repair."""

from __future__ import annotations

from datetime import datetime, timedelta
from typing import Any, Dict, Optional


def parse_datetime(value) -> Optional[datetime]:
    if value is None:
        return None
    if isinstance(value, datetime):
        return value.replace(tzinfo=None) if value.tzinfo else value
    try:
        parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
        return parsed.replace(tzinfo=None) if parsed.tzinfo else parsed
    except Exception:
        return None


def parse_duration_value(value: Any) -> Optional[int]:
    """Parse seconds from int/float, timedelta, or HH:MM:SS strings."""
    if value is None or value == "":
        return None
    if isinstance(value, timedelta):
        return max(0, int(value.total_seconds()))
    if isinstance(value, (int, float)):
        return max(0, int(value))
    text = str(value).strip()
    if not text:
        return None
    if ":" in text:
        parts = text.split(":")
        try:
            nums = [float(part) for part in parts]
            if len(nums) == 3:
                return max(0, int(nums[0] * 3600 + nums[1] * 60 + nums[2]))
            if len(nums) == 2:
                return max(0, int(nums[0] * 60 + nums[1]))
        except (TypeError, ValueError):
            pass
    try:
        return max(0, int(float(text)))
    except (TypeError, ValueError):
        return None


def call_duration_seconds(call: Dict[str, Any]) -> Optional[int]:
    """Best-effort duration from a source row or raw_calls row.

    Wall-clock (call_end - call_start) is preferred over billing fields
    (answeredtime/billsec) so short calls are not inflated at ingest.
    """
    start = call.get("call_starttime") or call.get("starttime") or call.get("call_start")
    end = call.get("call_endtime") or call.get("endtime") or call.get("call_end")
    if start and end:
        start_dt = parse_datetime(start)
        end_dt = parse_datetime(end)
        if start_dt and end_dt:
            try:
                return max(0, int((end_dt - start_dt).total_seconds()))
            except Exception:
                pass

    for key in ("duration_seconds", "call_duration_s", "duration_s", "duration"):
        raw = call.get(key)
        if raw is not None and raw != "":
            parsed = parse_duration_value(raw)
            if parsed is not None:
                return parsed

    for key in ("answeredtime", "answered_time", "talktime", "talk_time", "billsec"):
        parsed = parse_duration_value(call.get(key))
        if parsed is not None and parsed > 0:
            return parsed

    return None


def min_duration_configured(min_duration_s: int) -> bool:
    return int(min_duration_s or 0) > 0


def call_start_datetime(call: Dict[str, Any]) -> Optional[datetime]:
    return parse_datetime(
        call.get("call_starttime") or call.get("starttime") or call.get("call_start")
    )


def min_duration_applies_to_call(call: Dict[str, Any], effective_at) -> bool:
    """
    True when the min-duration policy applies to this call.

    Calls that started before ``effective_at`` are grandfathered.
    """
    if effective_at is None:
        return False
    eff = parse_datetime(effective_at)
    if eff is None:
        return False
    start = call_start_datetime(call)
    if start is None:
        return True
    return start >= eff


def min_duration_gate_seconds(call: Dict[str, Any]) -> Optional[int]:
    """Shortest reliable metadata estimate (talk time vs wall clock)."""
    candidates: list[int] = []

    start = call.get("call_starttime") or call.get("starttime") or call.get("call_start")
    end = call.get("call_endtime") or call.get("endtime") or call.get("call_end")
    if start and end:
        start_dt = parse_datetime(start)
        end_dt = parse_datetime(end)
        if start_dt and end_dt:
            try:
                candidates.append(max(0, int((end_dt - start_dt).total_seconds())))
            except Exception:
                pass

    for key in ("duration_seconds", "call_duration_s", "duration_s", "duration"):
        parsed = parse_duration_value(call.get(key))
        if parsed is not None:
            candidates.append(parsed)

    for key in ("answeredtime", "answered_time", "talktime", "talk_time", "billsec", "pulse"):
        parsed = parse_duration_value(call.get(key))
        if parsed is not None and parsed > 0:
            candidates.append(parsed)

    if not candidates:
        return None
    return min(candidates)


def should_skip_at_ingest(
    call: Dict[str, Any],
    min_duration_s: int,
    effective_at=None,
    *,
    audio_duration_s: Optional[float] = None,
) -> bool:
    """
    True when a call should not be ingested or processed.

    When ``audio_duration_s`` is provided (WAV probe), it is the authority.
    Otherwise uses the shortest metadata estimate from ``min_duration_gate_seconds``.
    """
    min_s = int(min_duration_s or 0)
    if min_s <= 0:
        return False
    if not min_duration_applies_to_call(call, effective_at):
        return False
    if audio_duration_s is not None:
        return float(audio_duration_s) < float(min_s)
    duration = min_duration_gate_seconds(call)
    if duration is None:
        return True
    return duration < min_s


def evaluate_min_duration_for_ingest(
    call: Dict[str, Any],
    min_duration_s: int,
    effective_at,
    recording_url: Optional[str],
    *,
    probe_audio: bool = True,
) -> tuple[bool, str, Optional[float]]:
    """
    Decide whether a call may be ingested/queued.

    Returns ``(should_skip, reason, probed_audio_seconds)``.
    When min duration applies and probing is enabled, the recording URL must
    be probed; unreachable audio blocks ingest (no DB row).
    """
    min_s = int(min_duration_s or 0)
    if min_s <= 0 or not min_duration_applies_to_call(call, effective_at):
        return False, "", None

    from wav_probe_util import probe_wav_duration_from_url, recording_url_probe_ready

    url = str(recording_url or call.get("fileurl") or "").strip()
    if probe_audio:
        if not recording_url_probe_ready(url):
            return True, "recording URL not ready for duration probe", None
        probed = probe_wav_duration_from_url(url)
        if probed is None:
            return True, "recording not reachable or not a readable WAV", None
        if probed < min_s:
            return True, f"actual audio {probed:.1f}s < min {min_s}s", probed
        return False, "", probed

    if should_skip_at_ingest(call, min_s, effective_at):
        return True, skip_reason(call, min_s), None
    return False, "", None


def is_below_min_duration(
    call: Dict[str, Any],
    min_duration_s: int,
    effective_at=None,
) -> bool:
    """Alias for ingest/STT min-duration gate."""
    return should_skip_at_ingest(call, min_duration_s, effective_at)


def skip_reason(
    call: Dict[str, Any],
    min_duration_s: int,
    *,
    audio_duration_s: Optional[float] = None,
) -> str:
    if audio_duration_s is not None:
        return f"actual audio {float(audio_duration_s):.1f}s < min {int(min_duration_s)}s"
    duration = min_duration_gate_seconds(call)
    if duration is None:
        return f"duration unknown (min {int(min_duration_s)}s required)"
    return f"duration {duration}s < min {int(min_duration_s)}s"


def purge_unprocessed_if_below_min(
    cursor,
    bid: str,
    callid: str,
    call: Dict[str, Any],
    min_duration_s: int,
    effective_at=None,
    *,
    audio_duration_s: Optional[float] = None,
) -> bool:
    """
    Remove or terminal-mark calls below minimum duration.

    Only affects calls on or after ``effective_at``. Unprocessed rows are
    deleted. Rows that already have transcript or analytics are marked
    ``skipped_short`` so they stay out of the dashboard and STT queue.

    Returns True when an action was taken.
    """
    if not should_skip_at_ingest(
        call,
        min_duration_s,
        effective_at,
        audio_duration_s=audio_duration_s,
    ):
        return False

    callid = str(callid or call.get("callid") or "").strip()
    if not callid:
        return False

    raw_table = f"{bid}_raw_calls"
    sarvam_table = f"{bid}_sarvamresponse"
    analytics_table = f"{bid}_callanalytics"

    cursor.execute(
        f"""
        SELECT 1 FROM `{sarvam_table}`
        WHERE callid = %s
          AND transcript IS NOT NULL
          AND TRIM(transcript) != ''
        LIMIT 1
        """,
        (callid,),
    )
    has_transcript = bool(cursor.fetchone())

    cursor.execute(
        f"SELECT 1 FROM `{analytics_table}` WHERE callid = %s LIMIT 1",
        (callid,),
    )
    has_analytics = bool(cursor.fetchone())

    if has_transcript or has_analytics:
        cursor.execute(
            f"""
            UPDATE `{raw_table}`
            SET transcription_status = 'skipped_short',
                status = -2
            WHERE callid = %s
            """,
            (callid,),
        )
        return True

    cursor.execute(f"DELETE FROM `{raw_table}` WHERE callid = %s", (callid,))
    return True
