"""Helpers for Master Panel error call retry/remove actions."""

from __future__ import annotations

import json
import logging
import os
from typing import Any, Dict, List, Sequence, Tuple

import pika

from feature_entitlements import module_active
from min_duration_util import evaluate_min_duration_for_ingest, purge_unprocessed_if_below_min
from usage_allocation_util import evaluate_usage_allocation

logger = logging.getLogger(__name__)

_ERROR_RETRY_OPTIONAL_COLS = (
    "call_starttime",
    "call_endtime",
    "duration_seconds",
    "answeredtime",
    "pulse",
    "answered_time",
    "talktime",
    "billsec",
)


def columns_for_error_retry(existing_cols: set) -> List[str]:
    """Build a safe SELECT column list for error-call retry (schema varies by BID)."""
    wanted = ["callid", "fileurl", "status", "transcription_status", *_ERROR_RETRY_OPTIONAL_COLS]
    return [col for col in wanted if col in existing_cols]


def build_retry_update_sets(existing_cols: set) -> List[str]:
    """SQL SET fragments for a full manual retry reset."""
    sets = ["status = 0"]
    if "transcription_status" in existing_cols:
        sets.append("transcription_status = 'pending'")
    if "transcription_requested" in existing_cols:
        sets.append("transcription_requested = 1")
    if "selected_for_processing" in existing_cols:
        sets.append("selected_for_processing = 0")
    return sets


def _load_bid_min_duration_settings(cursor, bid: str) -> Tuple[int, Any]:
    """Return (min_duration_s, effective_at) using the same rules as ingest/orchestrator."""
    cursor.execute(
        """
        SELECT min_call_duration_s, min_call_duration_effective_at,
               min_duration_filter_enabled, allow_min_duration_filter
        FROM business_pipeline_config
        WHERE bid = %s
        LIMIT 1
        """,
        (str(bid),),
    )
    cfg = cursor.fetchone() or {}
    min_s = max(0, int(cfg.get("min_call_duration_s") or 0))
    if not module_active(cfg, "min_duration_filter_enabled"):
        return 0, cfg.get("min_call_duration_effective_at")
    return min_s, cfg.get("min_call_duration_effective_at")


def queue_calls_for_stt(
    bid: str,
    calls: Sequence[Dict[str, Any]],
    *,
    db_conn_factory,
) -> Dict[str, int]:
    """
    Publish STT jobs for manually retried calls.
    Returns counts: queued, skipped_usage, skipped_no_url, skipped_min_duration, failed.
    """
    bid = str(bid).strip()
    stats = {
        "queued": 0,
        "skipped_usage": 0,
        "skipped_no_url": 0,
        "skipped_min_duration": 0,
        "failed": 0,
    }
    if not calls:
        return stats

    host = os.getenv("RABBITMQ_HOST", "localhost")
    queue = os.getenv("RABBITMQ_QUEUE", "stt_jobs")
    raw_table = f"{bid}_raw_calls"

    with db_conn_factory() as conn:
        with conn.cursor() as cursor:
            usage = evaluate_usage_allocation(cursor, bid)
            if usage.get("blocked"):
                stats["skipped_usage"] = len(calls)
                return stats
            min_duration_s, effective_at = _load_bid_min_duration_settings(cursor, bid)

    try:
        rmq_conn = pika.BlockingConnection(pika.ConnectionParameters(host=host))
        channel = rmq_conn.channel()
        channel.queue_declare(queue=queue, durable=True)
    except Exception as exc:
        logger.warning("STT queue unavailable for BID %s retry: %s", bid, exc)
        stats["failed"] = len(calls)
        return stats

    try:
        with db_conn_factory() as conn:
            with conn.cursor() as cursor:
                for call in calls:
                    call_id = str(call.get("callid") or "").strip()
                    recording_url = str(call.get("fileurl") or "").strip()
                    if not call_id:
                        continue
                    if not recording_url:
                        stats["skipped_no_url"] += 1
                        continue

                    if min_duration_s > 0:
                        skip_min, _reason, probed = evaluate_min_duration_for_ingest(
                            call,
                            min_duration_s,
                            effective_at,
                            recording_url,
                            probe_audio=True,
                        )
                        if skip_min:
                            purge_unprocessed_if_below_min(
                                cursor,
                                bid,
                                call_id,
                                call,
                                min_duration_s,
                                effective_at,
                                audio_duration_s=probed,
                            )
                            stats["skipped_min_duration"] += 1
                            continue

                    cursor.execute(
                        f"""
                        UPDATE `{raw_table}`
                        SET status = 1, selected_for_processing = 0
                        WHERE callid = %s
                          AND status IN (0, -2)
                        """,
                        (call_id,),
                    )
                    if cursor.rowcount < 1:
                        stats["failed"] += 1
                        continue

                    job_payload = {
                        "bid": bid,
                        "call_id": call_id,
                        "recording_url": recording_url,
                    }
                    try:
                        channel.basic_publish(
                            exchange="",
                            routing_key=queue,
                            body=json.dumps(job_payload),
                            properties=pika.BasicProperties(delivery_mode=2),
                        )
                        stats["queued"] += 1
                    except Exception as exc:
                        logger.warning("[%s] Failed to publish STT retry job: %s", call_id, exc)
                        cursor.execute(
                            f"UPDATE `{raw_table}` SET status = 0 WHERE callid = %s AND status = 1",
                            (call_id,),
                        )
                        stats["failed"] += 1
                conn.commit()
    finally:
        try:
            rmq_conn.close()
        except Exception:
            pass

    return stats
