from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

from moviepy import AudioFileClip, CompositeVideoClip, TextClip, VideoFileClip, concatenate_videoclips, vfx

from .config import AppConfig


@dataclass(frozen=True)
class Segment:
    video_path: Path
    audio_path: Path
    narration: str


def _fit_video_to_audio(video: VideoFileClip, audio: AudioFileClip) -> VideoFileClip:
    if audio.duration is None or video.duration is None:
        return video.with_audio(audio)
    if audio.duration > video.duration:
        video = video.with_effects([vfx.Loop(duration=audio.duration)])
    elif video.duration > audio.duration:
        video = video.subclipped(0, audio.duration)
    return video.with_audio(audio)


def _subtitle_clip(text: str, size: tuple[int, int], duration: float) -> TextClip:
    # MoviePy 2 uses Pillow for text rendering by default on most setups.
    return (
        TextClip(
            text=text,
            font_size=44,
            color="white",
            stroke_color="black",
            stroke_width=2,
            size=(int(size[0] * 0.92), None),
            method="caption",
        )
        .with_position(("center", "bottom"))
        .with_duration(duration)
        .with_opacity(0.95)
    )


def render_final(segments: list[Segment], cfg: AppConfig, output_path: str | Path | None = None) -> Path:
    transition_s = float(cfg.get("video.transition_seconds", 0.25))
    out = Path(output_path or str(cfg.get("video.final_output", "./final_poc.mp4")))
    out.parent.mkdir(parents=True, exist_ok=True)

    clips = []
    for seg in segments:
        v = VideoFileClip(str(seg.video_path))
        a = AudioFileClip(str(seg.audio_path))
        v2 = _fit_video_to_audio(v, a)

        w, h = v2.size
        sub = _subtitle_clip(seg.narration, (w, h), v2.duration or a.duration or 0)
        comp = CompositeVideoClip([v2, sub])
        if transition_s > 0:
            comp = comp.with_effects([vfx.FadeIn(transition_s), vfx.FadeOut(transition_s)])
        clips.append(comp)

    final = concatenate_videoclips(clips, method="compose")
    try:
        final.write_videofile(
            str(out),
            codec="libx264",
            audio_codec="aac",
            fps=clips[0].fps if clips and clips[0].fps else int(cfg.get("video.mock_fps", 24)),
            threads=4,
            preset="medium",
        )
    finally:
        final.close()
        for c in clips:
            c.close()
    return out
