#!/usr/bin/env python3
"""Загрузчик списков топ-1М (Umbrella и Majestic)."""

from __future__ import annotations

import argparse
import logging
import shutil
import sys
import tempfile
from pathlib import Path
from typing import Iterable
from zipfile import ZipFile

import httpx

from ioc_checker.http_client import build_sync_http_client

LOGGER = logging.getLogger("update-top1m")
UMBRELLA_ZIP_URL = "https://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip"
UMBRELLA_TLD_ZIP_URL = "https://s3-us-west-1.amazonaws.com/umbrella-static/top-1m-TLD.csv.zip"
MAJESTIC_URL = "https://downloads.majestic.com/majestic_million.csv"


def configure_logging(verbose: bool) -> None:
    """Настроить вывод логов для скрипта обновления."""

    level = logging.DEBUG if verbose else logging.INFO
    logging.basicConfig(level=level, format="[%(name)s] %(message)s", force=True)


def parse_args(argv: Iterable[str]) -> argparse.Namespace:
    """Разобрать аргументы командной строки."""

    parser = argparse.ArgumentParser(
        description="Скачать CSV топ-1М (Umbrella + Majestic) с рейтингами доменов и зон.",
    )
    parser.add_argument(
        "--verbose",
        action="store_true",
        help="Включить подробные логи для отладки.",
    )
    parser.add_argument(
        "--zip-url",
        default=UMBRELLA_ZIP_URL,
        help="Переопределить URL архива Umbrella с рейтингом доменов.",
    )
    parser.add_argument(
        "--tld-zip-url",
        default=UMBRELLA_TLD_ZIP_URL,
        help="Переопределить URL архива Umbrella с рейтингом зон (TLD).",
    )
    parser.add_argument(
        "--majestic-url",
        default=MAJESTIC_URL,
        help="Переопределить URL CSV Majestic Million.",
    )
    return parser.parse_args(list(argv))


def repo_paths() -> tuple[Path, Path, Path]:
    """Вернуть пути для выгрузки CSV."""

    script_path = Path(__file__).resolve()
    repo_root = script_path.parent.parent
    target_dir = repo_root / "ioc_checker" / "top1m"
    domain_csv_path = target_dir / "umbrella-top-1m.csv"
    tld_csv_path = target_dir / "umbrella-top-1m-TLD.csv"
    majestic_csv_path = target_dir / "majestic-top-1m.csv"
    return domain_csv_path, tld_csv_path, majestic_csv_path


def download_zip(client: httpx.Client, url: str) -> Path:
    """Скачать архив во временный файл."""

    LOGGER.info("Скачиваем архив топ-1М из %s", url)
    try:
        with client.stream("GET", url) as response:
            response.raise_for_status()
            with tempfile.NamedTemporaryFile(delete=False) as handle:
                for chunk in response.iter_bytes():
                    handle.write(chunk)
                temp_path = Path(handle.name)
    except httpx.HTTPError as exc:
        raise SystemExit(f"Не удалось скачать архив топ-1М: {exc}") from exc

    return temp_path


def download_csv(client: httpx.Client, url: str, destination: Path) -> None:
    """Скачать CSV и сохранить в указанный путь."""

    LOGGER.info("Скачиваем CSV топ-1М из %s", url)
    destination.parent.mkdir(parents=True, exist_ok=True)
    try:
        with client.stream("GET", url) as response:
            response.raise_for_status()
            with destination.open("wb") as handle:
                for chunk in response.iter_bytes():
                    handle.write(chunk)
    except httpx.HTTPError as exc:
        raise SystemExit(f"Не удалось скачать CSV топ-1М: {exc}") from exc


def extract_csv(archive_path: Path, destination: Path) -> None:
    """Извлечь CSV из архива в целевой каталог."""

    LOGGER.info("Распаковываем CSV в %s", destination)
    destination.parent.mkdir(parents=True, exist_ok=True)
    try:
        with ZipFile(archive_path) as archive:
            csv_members = [
                member
                for member in archive.namelist()
                if member.lower().endswith(".csv")
            ]
            if not csv_members:
                raise SystemExit("В загруженном архиве нет CSV-файла")
            member = csv_members[0]
            with archive.open(member) as source, destination.open("wb") as target:
                shutil.copyfileobj(source, target)
    except (OSError, RuntimeError) as exc:
        raise SystemExit(f"Не удалось распаковать CSV из архива: {exc}") from exc


def update_top1m(domain_url: str, tld_url: str, majestic_url: str) -> None:
    """Полностью обновить локальные CSV топ-1М."""

    domain_csv, tld_csv, majestic_csv = repo_paths()
    with build_sync_http_client(timeout=120.0) as client:
        domain_archive = download_zip(client, domain_url)
        tld_archive = download_zip(client, tld_url)
        download_csv(client, majestic_url, majestic_csv)
    try:
        extract_csv(domain_archive, domain_csv)
        extract_csv(tld_archive, tld_csv)
    finally:
        for archive_path in (domain_archive, tld_archive):
            try:
                archive_path.unlink()
            except FileNotFoundError:
                pass

    LOGGER.info("CSV топ-1М успешно обновлены")


def main(argv: Iterable[str]) -> int:
    """Запустить обновление из CLI."""

    args = parse_args(argv)
    configure_logging(args.verbose)
    update_top1m(args.zip_url, args.tld_zip_url, args.majestic_url)
    return 0


if __name__ == "__main__":  # pragma: no cover - точка входа для ручного запуска
    sys.exit(main(sys.argv[1:]))
