#!/usr/bin/env python3
from __future__ import annotations

import json
import re
import sys
from pathlib import Path

from _media_utils import die, extract_video_id, run_passthrough

RANGE_RE = re.compile(r"\b(\d{2}:\d{2}:\d{2})-(\d{2}:\d{2}:\d{2})\b")

USAGE = """\
Usage:
  download-from-videotz.py [--dry-run] <plan-file> [output-dir]

Examples:
  download-from-videotz.py assets/videotz.txt
  download-from-videotz.py --dry-run assets/videotz.txt clips
"""


def parse_args(argv: list[str]) -> tuple[bool, str, str | None]:
    dry_run = False
    if argv and argv[0] == "--dry-run":
        dry_run = True
        argv = argv[1:]

    if not 1 <= len(argv) <= 2:
        print(USAGE.rstrip())
        raise SystemExit(1)

    output_dir = argv[1] if len(argv) == 2 else None
    return dry_run, argv[0], output_dir


def parse_hms(timestamp: str) -> int:
    hours, minutes, seconds = (int(part) for part in timestamp.split(":"))
    return (hours * 3600) + (minutes * 60) + seconds


def format_hms(total_seconds: int) -> str:
    hours = total_seconds // 3600
    minutes = (total_seconds % 3600) // 60
    seconds = total_seconds % 60
    return f"{hours:02d}:{minutes:02d}:{seconds:02d}"


def extract_url(text: str, input_file: str) -> str:
    for line in text.splitlines():
        if line.startswith("Ссылка:"):
            url = line.split(":", 1)[1].strip()
            if url:
                return url
            break
    die(f"could not find 'Ссылка:' in {input_file}")


def extract_ranges(text: str, input_file: str) -> list[tuple[int, int]]:
    ranges: list[tuple[int, int]] = []
    for line in text.splitlines():
        if "Источник:" not in line:
            continue
        for start_text, end_text in RANGE_RE.findall(line):
            ranges.append((parse_hms(start_text), parse_hms(end_text)))

    if not ranges:
        die(f"no source ranges found in {input_file}")

    return sorted(ranges)


def parse_json_range(item: object, input_file: str) -> list[tuple[int, int]]:
    if isinstance(item, str):
        return [(parse_hms(start_text), parse_hms(end_text)) for start_text, end_text in RANGE_RE.findall(item)]

    if isinstance(item, dict):
        start_text = item.get("start")
        end_text = item.get("end")
        if isinstance(start_text, str) and isinstance(end_text, str):
            return [(parse_hms(start_text), parse_hms(end_text))]

        ranges: list[tuple[int, int]] = []
        for value in item.values():
            ranges.extend(parse_json_range(value, input_file))
        return ranges

    if isinstance(item, list):
        ranges: list[tuple[int, int]] = []
        for value in item:
            ranges.extend(parse_json_range(value, input_file))
        return ranges

    return []


def extract_url_from_json(plan: dict[str, object], input_file: str) -> str:
    source_url = plan.get("source_url")
    if isinstance(source_url, str) and source_url.strip():
        return source_url.strip()

    source = plan.get("source")
    if isinstance(source, dict):
        url = source.get("url")
        if isinstance(url, str) and url.strip():
            return url.strip()

    die(f"could not find 'source_url' in {input_file}")


def extract_ranges_from_json(plan: dict[str, object], input_file: str) -> list[tuple[int, int]]:
    ranges: list[tuple[int, int]] = []

    scenes = plan.get("scenes")
    if isinstance(scenes, list):
        for scene in scenes:
            if not isinstance(scene, dict):
                continue
            for key in ("source_ranges", "source_range", "sources", "source"):
                if key in scene:
                    ranges.extend(parse_json_range(scene[key], input_file))

    if "source_ranges" in plan:
        ranges.extend(parse_json_range(plan["source_ranges"], input_file))

    if not ranges:
        die(f"no source ranges found in {input_file}")

    return sorted(ranges)


def merge_ranges(ranges: list[tuple[int, int]]) -> list[tuple[int, int]]:
    merged: list[list[int]] = []
    for start, end in ranges:
        if not merged or start > merged[-1][1]:
            merged.append([start, end])
            continue
        if end > merged[-1][1]:
            merged[-1][1] = end

    return [(start, end) for start, end in merged]


def read_input_text(input_path: Path) -> str:
    for encoding in ("utf-8-sig", "cp1251"):
        try:
            return input_path.read_text(encoding=encoding)
        except UnicodeDecodeError:
            continue
    die(f"could not decode input file: {input_path}")


def read_plan(input_path: Path, input_file: str) -> tuple[str, list[tuple[int, int]]]:
    text = read_input_text(input_path)
    stripped = text.lstrip()
    if stripped.startswith("{"):
        plan = json.loads(text)
        if not isinstance(plan, dict):
            die(f"expected a JSON object in {input_file}")
        return extract_url_from_json(plan, input_file), extract_ranges_from_json(plan, input_file)

    return extract_url(text, input_file), extract_ranges(text, input_file)


def main(argv: list[str]) -> int:
    dry_run, input_file, output_dir_arg = parse_args(argv)
    input_path = Path(input_file)
    if not input_path.is_file():
        die(f"input file not found: {input_file}")

    clip_script = Path(__file__).with_name("download-clip.py")
    if not clip_script.is_file():
        die(f"missing helper: {clip_script}")

    url, raw_ranges = read_plan(input_path, input_file)
    video_id = extract_video_id(url) or "video"

    output_dir = Path(output_dir_arg) if output_dir_arg else Path("segments") / video_id
    merged_ranges = merge_ranges(raw_ranges)

    if not dry_run:
        output_dir.mkdir(parents=True, exist_ok=True)

    for index, (start_seconds, end_seconds) in enumerate(merged_ranges, start=1):
        start_text = format_hms(start_seconds)
        end_text = format_hms(end_seconds)
        output_file = output_dir / f"{index:02d}_{start_text.replace(':', '-')}_to_{end_text.replace(':', '-')}.mp4"
        print(f"[{index:02d}] {start_text}-{end_text} -> {output_file}", flush=True)

        command = [sys.executable, str(clip_script)]
        if dry_run:
            command.append("--dry-run")
        command.extend([url, start_text, end_text, str(output_file)])
        run_passthrough(command)

    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
