# Copyright (c) 2026 Pointmatic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Output file writing (JSON, txt, VTT, SRT)."""

from __future__ import annotations

import json
import logging
import os
import tempfile
from pathlib import Path
from typing import Any

from tubefetch.core.models import BatchResult, FetchResult, Metadata, Transcript, VideoBundle
from tubefetch.core.options import FetchOptions
from tubefetch.utils.hashing import hash_bundle
from tubefetch.utils.time_fmt import seconds_to_srt, seconds_to_vtt
from tubefetch.utils.txt_formatter import format_transcript_txt

logger = logging.getLogger("tubefetch")


def write_metadata(metadata: Metadata, out_dir: Path) -> Path:
    """Write metadata as JSON. Returns the written file path."""
    video_dir = out_dir / metadata.video_id
    video_dir.mkdir(parents=True, exist_ok=True)
    dest = video_dir / "metadata.json"
    _atomic_write_json(dest, metadata.model_dump(mode="json"))
    return dest


def read_metadata(out_dir: Path, video_id: str) -> Metadata | None:
    """Read cached metadata.json and return a Metadata model.

    Returns None if the file does not exist or cannot be parsed.
    """
    path = Path(out_dir) / video_id / "metadata.json"
    if not path.exists():
        return None
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return Metadata.model_validate(data)
    except Exception as exc:
        logger.warning("Failed to read cached metadata for %s: %s", video_id, exc)
        return None


def read_transcript_json(out_dir: Path, video_id: str) -> Transcript | None:
    """Read cached transcript.json and return a Transcript model.

    Returns None if the file does not exist or cannot be parsed.
    """
    path = Path(out_dir) / video_id / "transcript.json"
    if not path.exists():
        return None
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return Transcript.model_validate(data)
    except Exception as exc:
        logger.warning("Failed to read cached transcript for %s: %s", video_id, exc)
        return None


def write_transcript_json(transcript: Transcript, out_dir: Path) -> Path:
    """Write transcript as JSON. Returns the written file path."""
    video_dir = out_dir / transcript.video_id
    video_dir.mkdir(parents=True, exist_ok=True)
    dest = video_dir / "transcript.json"
    _atomic_write_json(dest, transcript.model_dump(mode="json"))
    return dest


def write_transcript_txt(transcript: Transcript, out_dir: Path, options: FetchOptions | None = None) -> Path:
    """Write transcript as LLM-ready plain text. Returns the written file path.

    Args:
        transcript: Transcript model with segments.
        out_dir: Output directory.
        options: FetchOptions with txt formatting preferences. If None, uses defaults.

    Returns:
        Path to written transcript.txt file.
    """
    video_dir = out_dir / transcript.video_id
    video_dir.mkdir(parents=True, exist_ok=True)
    dest = video_dir / "transcript.txt"

    # Use formatter with options
    if options is None:
        options = FetchOptions()

    text = format_transcript_txt(
        segments=transcript.segments,
        is_generated=transcript.is_generated,
        gap_threshold=options.txt_gap_threshold,
        timestamps=options.txt_timestamps,
        raw=options.txt_raw,
    )

    _atomic_write_text(dest, text)
    return dest


def write_transcript_vtt(transcript: Transcript, out_dir: Path) -> Path:
    """Write transcript as WebVTT. Returns the written file path."""
    video_dir = out_dir / transcript.video_id
    video_dir.mkdir(parents=True, exist_ok=True)
    dest = video_dir / "transcript.vtt"

    parts = ["WEBVTT", ""]
    for seg in transcript.segments:
        start = seconds_to_vtt(seg.start)
        end = seconds_to_vtt(seg.start + seg.duration)
        parts.append(f"{start} --> {end}")
        parts.append(seg.text)
        parts.append("")

    _atomic_write_text(dest, "\n".join(parts))
    return dest


def write_transcript_srt(transcript: Transcript, out_dir: Path) -> Path:
    """Write transcript as SRT. Returns the written file path."""
    video_dir = out_dir / transcript.video_id
    video_dir.mkdir(parents=True, exist_ok=True)
    dest = video_dir / "transcript.srt"

    parts = []
    for i, seg in enumerate(transcript.segments, start=1):
        start = seconds_to_srt(seg.start)
        end = seconds_to_srt(seg.start + seg.duration)
        parts.append(str(i))
        parts.append(f"{start} --> {end}")
        parts.append(seg.text)
        parts.append("")

    _atomic_write_text(dest, "\n".join(parts))
    return dest


def write_summary(results: BatchResult, out_dir: Path) -> Path:
    """Write a batch summary as JSON. Returns the written file path."""
    out_dir = Path(out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)
    dest = out_dir / "summary.json"
    _atomic_write_json(dest, results.model_dump(mode="json"))
    return dest


def _atomic_write_json(dest: Path, data: dict[str, Any]) -> None:
    """Write JSON atomically: write to temp file, then rename."""
    dest.parent.mkdir(parents=True, exist_ok=True)
    fd, tmp_path = tempfile.mkstemp(dir=dest.parent, suffix=".tmp", prefix=".tubefetch_")
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, default=str, ensure_ascii=False)
            f.write("\n")
        os.replace(tmp_path, dest)
    except BaseException:
        os.unlink(tmp_path)
        raise


def _atomic_write_text(dest: Path, content: str) -> None:
    """Write text atomically: write to temp file, then rename."""
    dest.parent.mkdir(parents=True, exist_ok=True)
    fd, tmp_path = tempfile.mkstemp(dir=dest.parent, suffix=".tmp", prefix=".tubefetch_")
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(content)
        os.replace(tmp_path, dest)
    except BaseException:
        os.unlink(tmp_path)
        raise


def write_bundle(result: FetchResult, out_dir: Path) -> Path:
    """Write unified video bundle JSON containing all video data.

    Args:
        result: FetchResult with metadata, transcript, and errors.
        out_dir: Output directory.

    Returns:
        Path to written video_bundle.json file.
    """
    from datetime import datetime, timezone

    video_dir = out_dir / result.video_id
    video_dir.mkdir(parents=True, exist_ok=True)
    dest = video_dir / "video_bundle.json"

    # Compute content hash using hash_bundle
    content_hash = hash_bundle(result.metadata, result.transcript)

    # Get token count from transcript if available
    token_count = result.transcript.token_count if result.transcript else None

    bundle = VideoBundle(
        video_id=result.video_id,
        metadata=result.metadata,
        transcript=result.transcript,
        errors=result.errors,
        content_hash=content_hash,
        token_count=token_count,
        fetched_at=datetime.now(timezone.utc),
    )

    _atomic_write_json(dest, bundle.model_dump(mode="json"))
    logger.info("Wrote video bundle for %s", result.video_id)
    return dest
