"""Description generator: Create YouTube description with chapters, citations, and attributions."""

import re
from dataclasses import dataclass
from pathlib import Path
from typing import Optional

from .models import (
    Attribution,
    Citation,
    ProjectConfig,
    SlideDefinition,
    VideoSource,
)
from .transcriber import TranscribedWord


@dataclass
class ChapterMarker:
    """A chapter marker with timestamp and title."""

    slide_id: str
    timestamp: float
    title: str


def _format_timestamp(seconds: float) -> str:
    """Format seconds as M:SS or H:MM:SS for YouTube chapters."""
    if seconds < 0:
        return "0:00"

    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)

    if hours > 0:
        return f"{hours}:{minutes:02d}:{secs:02d}"
    else:
        return f"{minutes}:{secs:02d}"


def _extract_chapter_title(
    manuscript_text: str, slide_id: str, slides: dict[str, SlideDefinition]
) -> str:
    """
    Extract a chapter title for a slide.

    Tries to find meaningful title from:
    1. First sentence/line after the slide marker
    2. Falls back to slide ID if nothing useful found
    """
    # Find the marker and text after it
    pattern = rf"\[{re.escape(slide_id)}\]\s*(.+?)(?=\[S\d+\]|\[video:|\[narration:|\Z)"
    match = re.search(pattern, manuscript_text, re.DOTALL)

    if match:
        text = match.group(1).strip()
        # Remove any other markers from the text
        text = re.sub(r"\[[^\]]+\]", "", text).strip()

        if text:
            # Take first line or first sentence
            first_line = text.split("\n")[0].strip()
            # Truncate if too long
            if len(first_line) > 50:
                # Try to break at word boundary
                truncated = first_line[:47]
                last_space = truncated.rfind(" ")
                if last_space > 30:
                    truncated = truncated[:last_space]
                first_line = truncated + "..."

            if first_line:
                return first_line

    # Fallback to slide number
    slide_num = slide_id[1:] if slide_id.startswith("S") else slide_id
    return f"Section {slide_num}"


def _align_citation_to_transcription(
    citation: Citation,
    transcription: list[TranscribedWord],
    manuscript_text: str,
) -> float:
    """
    Align a citation to the transcription to find its timestamp.

    Uses the context text following the citation to find the approximate
    position in the audio.

    Returns timestamp in seconds, or -1 if not found.
    """
    if not transcription or not citation.context:
        return -1.0

    # Get more context from the manuscript for better matching
    # Find the citation in the manuscript and get surrounding text
    pattern = rf"\[cite:{re.escape(citation.reference)}\]\s*(.{{0,200}})"
    match = re.search(pattern, manuscript_text, re.DOTALL)

    if not match:
        return -1.0

    context_text = match.group(1).strip()
    # Clean up: remove markers, normalize whitespace
    context_text = re.sub(r"\[[^\]]+\]", "", context_text)
    context_text = " ".join(context_text.split())

    if not context_text:
        return -1.0

    # Normalize for matching
    context_words = context_text.lower().split()[:10]  # Use up to 10 words
    if not context_words:
        return -1.0

    # Build normalized transcription
    trans_words = [(w.word.lower(), w.start) for w in transcription]

    # Simple sliding window match
    best_match_score = 0
    best_match_time = -1.0

    for i in range(len(trans_words) - len(context_words) + 1):
        matches = 0
        for j, ctx_word in enumerate(context_words):
            trans_word = trans_words[i + j][0]
            # Allow partial matches for longer words
            if ctx_word == trans_word:
                matches += 1
            elif len(ctx_word) >= 4 and (
                ctx_word in trans_word or trans_word in ctx_word
            ):
                matches += 0.5

        score = matches / len(context_words)
        if score > best_match_score and score >= 0.5:
            best_match_score = score
            best_match_time = trans_words[i][1]

    return best_match_time


def generate_chapters(
    manuscript_text: str,
    slides: dict[str, SlideDefinition],
    marker_timings: list,  # List of MarkerTiming from transformer
    min_chapter_duration: float = 30.0,
) -> list[ChapterMarker]:
    """
    Generate chapter markers from slide timings.

    Args:
        manuscript_text: The manuscript content
        slides: Slide definitions
        marker_timings: Aligned marker timings from the transformer
        min_chapter_duration: Minimum seconds between chapters (merges short ones)

    Returns:
        List of ChapterMarker objects
    """
    chapters = []

    # Build timing lookup
    timing_lookup = {
        t.marker_id: t.timestamp for t in marker_timings if t.timestamp >= 0
    }

    # Process slides in order
    slide_ids = sorted(
        [s for s in slides.keys() if s.startswith("S")],
        key=lambda x: int(x[1:]) if x[1:].isdigit() else 0,
    )

    for slide_id in slide_ids:
        if slide_id not in timing_lookup:
            continue
        timestamp = timing_lookup[slide_id]
        title = _extract_chapter_title(manuscript_text, slide_id, slides)
        if chapters and (timestamp - chapters[-1].timestamp) < min_chapter_duration:
            continue  # Skip this chapter, previous one covers it

        chapters.append(
            ChapterMarker(
                slide_id=slide_id,
                timestamp=timestamp,
                title=title,
            )
        )

    # Ensure first chapter starts at 0:00
    if chapters and chapters[0].timestamp > 0:
        chapters[0] = ChapterMarker(
            slide_id=chapters[0].slide_id,
            timestamp=0.0,
            title=chapters[0].title,
        )

    return chapters


def collect_attributions(
    videos: dict[str, VideoSource],
    video_events: list = None,
) -> list[tuple[str, Attribution]]:
    """
    Collect all video attributions.

    Returns list of (video_id, Attribution) tuples for videos that have attribution.
    Only includes videos that are actually used in the project (via video_events)
    or videos from shared assets that have attribution.
    """
    attributions = []

    # Get set of used video IDs from events
    used_video_ids = set()
    if video_events:
        for event in video_events:
            used_video_ids.add(event.video_id)

    for video_id, video_source in videos.items():
        if video_source.attribution:
            # Include if used in video or if it's a shared asset
            if video_id in used_video_ids or video_source.is_shared:
                attributions.append((video_id, video_source.attribution))

    return attributions


def generate_description(
    config: ProjectConfig,
    manuscript_text: str,
    slides: dict[str, SlideDefinition],
    videos: dict[str, VideoSource],
    marker_timings: list,
    transcription: list[TranscribedWord] = None,
    video_events: list = None,
    citations: list[Citation] = None,
    include_chapters: bool = True,
    include_citations: bool = True,
    include_attributions: bool = True,
) -> str:
    """
    Generate complete YouTube description.

    Combines:
    - Video description from project.json
    - Chapter markers (optional)
    - Citations from manuscript (optional)
    - Stock footage attributions (optional)
    - Footer from project.json

    Returns formatted description text.
    """
    sections = []

    # 1. Video description
    if config.description:
        sections.append(config.description.strip())

    # 2. Chapters
    if include_chapters:
        chapters = generate_chapters(manuscript_text, slides, marker_timings)
        if chapters:
            chapter_lines = ["CHAPTERS", ""]
            for ch in chapters:
                chapter_lines.append(f"{_format_timestamp(ch.timestamp)} {ch.title}")
            sections.append("\n".join(chapter_lines))

    # 3. Citations/References
    if include_citations:
        citations = citations or []
        if citations and transcription:
            # Align citations to get timestamps
            for citation in citations:
                citation.timestamp = _align_citation_to_transcription(
                    citation, transcription, manuscript_text
                )

        if citations:
            ref_lines = ["REFERENCES", ""]
            for citation in citations:
                if citation.timestamp >= 0:
                    ref_lines.append(
                        f"{_format_timestamp(citation.timestamp)} - {citation.reference}"
                    )
                else:
                    ref_lines.append(f"- {citation.reference}")
            sections.append("\n".join(ref_lines))

    # 4. Stock footage attributions
    if include_attributions:
        attributions = collect_attributions(videos, video_events)
        if attributions:
            attr_lines = ["STOCK FOOTAGE", ""]
            for video_id, attr in attributions:
                # Format: "Description by Creator via Source: URL"
                line = f"{video_id.replace('_', ' ').title()} by {attr.creator} via {attr.source.title()}"
                if attr.url:
                    line += f": {attr.url}"
                attr_lines.append(line)
            sections.append("\n".join(attr_lines))

    # 5. Footer
    if config.footer:
        sections.append(config.footer.strip())

    # Join sections with double newlines
    return "\n\n".join(sections)


def write_description_file(
    output_path: Path,
    config: ProjectConfig,
    manuscript_text: str,
    slides: dict[str, SlideDefinition],
    videos: dict[str, VideoSource],
    marker_timings: list,
    transcription: list[TranscribedWord] = None,
    video_events: list = None,
    citations: list[Citation] = None,
) -> str:
    """
    Generate and write YouTube description to file.

    Args:
        output_path: Path to write description (e.g., out/description_youtube.txt)
        config: Project configuration
        manuscript_text: Manuscript content
        slides: Slide definitions
        videos: Video definitions
        marker_timings: Aligned marker timings
        transcription: Word-level transcription (optional, for citation timestamps)
        video_events: Video events from render plan (optional, for attribution filtering)
        citations: Pre-extracted citations (optional, loaded from citations.json)

    Returns:
        The generated description text
    """
    description = generate_description(
        config=config,
        manuscript_text=manuscript_text,
        slides=slides,
        videos=videos,
        marker_timings=marker_timings,
        transcription=transcription,
        video_events=video_events,
        citations=citations,
    )

    # Ensure output directory exists
    output_path.parent.mkdir(parents=True, exist_ok=True)

    # Write description
    output_path.write_text(description, encoding="utf-8")

    return description
