"""
src/pipeline/trailer_analyzer.py — Reference trailer → list[TrailerBeat]

Responsibility:
  1. Run PySceneDetect on the REFERENCE TRAILER (not the source movie)
     to detect cut boundaries → raw beat intervals
  2. Fingerprint the midpoint frame of each beat (for Vibe Check)
  3. Transcribe dialogue per beat via Whisper (optional, injected)
  4. Optionally classify BeatType via the LLM dramaturg (injected)

Returns: list[TrailerBeat] ready to feed into run_matching().
"""

from __future__ import annotations

import logging
from dataclasses import replace
from pathlib import Path
from typing import Callable, Sequence

from src.core.config import AppConfig
from src.core.models import BeatType, DialogueLine, TrailerBeat
from src.cv.fingerprinting import fingerprint_frame
from src.cv.frame_extractor import grab_midpoint_frame, open_video

logger = logging.getLogger(__name__)

# Injection type aliases — keeps this module free of hard audio/LLM imports
TranscribeCallback = Callable[[Path, float, float, float], list[DialogueLine]]
ClassifyCallback   = Callable[[list[TrailerBeat]], list[TrailerBeat]]


# ---------------------------------------------------------------------------
# Step 1: Scene detection on the reference trailer
# ---------------------------------------------------------------------------

def _detect_trailer_beats(cfg: AppConfig) -> list[tuple[float, float, int, int]]:
    """
    Run PySceneDetect on the reference trailer.

    Returns list of (start_s, end_s, start_frame, end_frame).
    Uses the same ContentDetector thresholds as the source movie.
    """
    try:
        from scenedetect import open_video as sd_open_video, SceneManager
        from scenedetect.detectors import ContentDetector
    except ImportError:
        raise ImportError("pip install scenedetect[opencv]")

    trailer_path = cfg.paths.reference_trailer
    video   = sd_open_video(str(trailer_path))
    manager = SceneManager()
    manager.add_detector(
        ContentDetector(
            threshold=cfg.scene_detection.content_threshold,
            min_scene_len=int(
                cfg.scene_detection.min_scene_duration_s * video.frame_rate
            ),
        )
    )

    logger.info("Detecting beats in reference trailer: %s …", trailer_path.name)
    manager.detect_scenes(video=video, show_progress=False)

    raw = manager.get_scene_list()
    result = [
        (s.get_seconds(), e.get_seconds(), s.get_frames(), e.get_frames())
        for s, e in raw
    ]
    logger.info("Detected %d beats in reference trailer.", len(result))
    return result


# ---------------------------------------------------------------------------
# Step 2: Fingerprint beats
# ---------------------------------------------------------------------------

def _fingerprint_beats(
    raw_beats: list[tuple[float, float, int, int]],
    cfg: AppConfig,
) -> list[TrailerBeat]:
    """Extract midpoint frame for each beat and compute fingerprints."""
    vc_cfg       = cfg.cv.vibe_check
    trailer_path = cfg.paths.reference_trailer
    beats: list[TrailerBeat] = []

    with open_video(trailer_path) as cap:
        for idx, (start_s, end_s, start_frame, end_frame) in enumerate(raw_beats):
            frame = grab_midpoint_frame(cap, start_s, end_s)

            if frame is None:
                logger.warning("Beat %d: midpoint frame decode failed.", idx)
                beats.append(TrailerBeat(
                    beat_id=idx,
                    trailer_path=trailer_path,
                    start_s=start_s, end_s=end_s,
                    start_frame=start_frame, end_frame=end_frame,
                ))
                continue

            luma_b, sat_b, phash = fingerprint_frame(frame, vc_cfg)
            beats.append(TrailerBeat(
                beat_id=idx,
                trailer_path=trailer_path,
                start_s=start_s, end_s=end_s,
                start_frame=start_frame, end_frame=end_frame,
                luma_hist=luma_b,
                sat_hist=sat_b,
                phash=phash,
            ))

    return beats


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def analyze_reference_trailer(
    cfg: AppConfig,
    transcribe_callback: TranscribeCallback | None = None,
    classify_callback:   ClassifyCallback   | None = None,
) -> list[TrailerBeat]:
    """
    Full reference-trailer analysis pipeline.

    Args:
        cfg:                   Application configuration.
        transcribe_callback:   Optional fn(path, start_s, end_s, offset_s)
                               → list[DialogueLine]. Injected to keep this
                               module free of faster-whisper imports.
        classify_callback:     Optional fn(beats) → beats with BeatType set.
                               Injected to keep this module LLM-free.

    Returns:
        List of TrailerBeat objects with fingerprints (and optionally
        dialogue + BeatType) populated.
    """
    # Step 1 — cut detection
    raw_beats = _detect_trailer_beats(cfg)

    # Step 2 — fingerprint
    beats = _fingerprint_beats(raw_beats, cfg)

    # Step 3 — dialogue (optional)
    if transcribe_callback is not None:
        enriched: list[TrailerBeat] = []
        for beat in beats:
            try:
                lines = transcribe_callback(
                    beat.trailer_path,
                    beat.start_s,
                    beat.end_s,
                    beat.start_s,   # time_offset so timestamps are absolute
                )
                enriched.append(replace(beat, dialogue=tuple(lines)))
            except Exception as exc:
                logger.warning("Beat %d transcription failed: %s", beat.beat_id, exc)
                enriched.append(beat)
        beats = enriched

    # Step 4 — LLM dramaturgy (optional)
    if classify_callback is not None:
        try:
            beats = classify_callback(beats)
        except Exception as exc:
            logger.warning("Beat classification failed: %s — keeping UNKNOWN.", exc)

    logger.info(
        "Trailer analysis complete: %d beats, %d with dialogue, %d classified.",
        len(beats),
        sum(1 for b in beats if b.dialogue),
        sum(1 for b in beats if b.beat_type != BeatType.UNKNOWN),
    )
    return beats
