#!/usr/bin/env python3
"""Download every ru.vodka MP3 sequentially and safely resume after a stop."""

from __future__ import annotations

import argparse
import json
import os
import sys
from pathlib import Path
from urllib.parse import urlencode
from urllib.error import URLError
from urllib.request import Request, urlopen

BASE_URL = "https://ru.vodka"
USER_AGENT = "ru.vodka-command-downloader/1.0"
CHUNK_SIZE = 1024 * 256


def request(url: str):
    return urlopen(Request(url, headers={"User-Agent": USER_AGENT}), timeout=60)


def fetch_manifest(quality: int, from_track: int, to_track: int) -> dict:
    params = {"quality": quality}
    if from_track:
        params["from_track"] = from_track
    if to_track:
        params["to_track"] = to_track
    url = f"{BASE_URL}/api/download-manifest.php?{urlencode(params)}"
    with request(url) as response:
        return json.load(response)


def existing_size(path: Path) -> int:
    try:
        return path.stat().st_size
    except FileNotFoundError:
        return -1


def download_one(url: str, destination: Path, expected_bytes: int) -> None:
    part = destination.with_name(destination.name + ".part")
    try:
        part.unlink()
    except FileNotFoundError:
        pass

    with request(url) as response, part.open("wb") as output:
        while True:
            chunk = response.read(CHUNK_SIZE)
            if not chunk:
                break
            output.write(chunk)

    actual_bytes = existing_size(part)
    if actual_bytes != expected_bytes:
        raise RuntimeError(
            f"size check failed: expected {expected_bytes} bytes, got {actual_bytes} bytes"
        )
    os.replace(part, destination)


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Download all ru.vodka tracks. Completed files with the expected size are skipped."
    )
    parser.add_argument("--quality", choices=(128, 320), type=int, default=320)
    parser.add_argument("--from-track", type=int, default=0, help="First (highest) track number to download")
    parser.add_argument("--to-track", type=int, default=0, help="Last (lowest) track number to download")
    parser.add_argument("--output", type=Path, default=None, help="Destination folder")
    args = parser.parse_args()

    if args.from_track < 0 or args.to_track < 0:
        parser.error("track numbers must be positive")
    if args.from_track and args.to_track and args.from_track < args.to_track:
        args.from_track, args.to_track = args.to_track, args.from_track

    output_dir = args.output or Path.cwd()
    output_dir.mkdir(parents=True, exist_ok=True)

    try:
        manifest = fetch_manifest(args.quality, args.from_track, args.to_track)
    except (URLError, OSError, json.JSONDecodeError) as error:
        print(f"Could not get the download list: {error}", file=sys.stderr)
        return 1

    files = manifest.get("files") or []
    if not files:
        print("The download list is empty.", file=sys.stderr)
        return 1

    print(f"ru.vodka: {len(files)} tracks, {args.quality} kbps, newest first")
    print(f"Folder: {output_dir}")
    downloaded = 0
    skipped = 0

    for position, item in enumerate(files, start=1):
        filename = Path(str(item.get("filename", "")).replace("\\", "/")).name
        expected_bytes = int(item.get("bytes") or 0)
        if not filename or expected_bytes <= 0:
            print(f"[{position}/{len(files)}] Invalid manifest item, stopping.", file=sys.stderr)
            return 1

        destination = output_dir / filename
        if existing_size(destination) == expected_bytes:
            skipped += 1
            print(f"[{position}/{len(files)}] Skip {item.get('track')}: already complete")
            continue

        print(f"[{position}/{len(files)}] Download {item.get('track')}: {item.get('title', '')}")
        try:
            download_one(str(item["url"]), destination, expected_bytes)
        except (KeyError, OSError, RuntimeError, URLError) as error:
            print(f"Failed on track {item.get('track')}: {error}", file=sys.stderr)
            print("Run the same command again: complete files will be skipped.", file=sys.stderr)
            return 1
        downloaded += 1

    print(f"Done. Downloaded: {downloaded}. Skipped: {skipped}.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
