#!/usr/bin/env python3
"""Загрузка фронтовых зависимостей (Bootstrap, Bootswatch, Gridstack, Bootstrap Icons)."""

from __future__ import annotations

import argparse
import logging
import sys
from pathlib import Path
from typing import Iterable, List, Tuple

from ioc_checker.http_client import build_sync_http_client

LOGGER = logging.getLogger("vendor-downloader")

BOOTSWATCH_VERSION = "5"
BOOTSTRAP_VERSION = "5.3.3"
BOOTSTRAP_ICONS_VERSION = "1.11.3"
GRIDSTACK_VERSION = "9.2.0"

BOOTSWATCH_THEMES = [
    "brite",
    "cerulean",
    "cosmo",
    "cyborg",
    "darkly",
    "flatly",
    "journal",
    "litera",
    "lumen",
    "lux",
    "materia",
    "minty",
    "morph",
    "pulse",
    "quartz",
    "sandstone",
    "simplex",
    "sketchy",
    "slate",
    "solar",
    "spacelab",
    "superhero",
    "united",
    "vapor",
    "yeti",
    "zephyr",
]

Asset = Tuple[str, Path]


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

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


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

    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--vendor-dir",
        type=Path,
        default=(
            Path(__file__).resolve().parent.parent / "ui" / "SOC" / "static" / "verificator" / "vendor"
        ),
        help="Каталог для сохранения скачанных файлов",
    )
    parser.add_argument("--verbose", "-v", action="store_true", help="Подробный вывод")
    return parser.parse_args(list(argv))


def build_assets(vendor_dir: Path) -> List[Asset]:
    """Сформировать список артефактов для загрузки."""

    assets: List[Asset] = [
        (
            f"https://cdn.jsdelivr.net/npm/bootstrap@{BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js",
            vendor_dir / "bootstrap" / "js" / "bootstrap.bundle.min.js",
        ),
        (
            f"https://cdn.jsdelivr.net/npm/bootstrap@{BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css",
            vendor_dir / "bootstrap" / "css" / "bootstrap.min.css",
        ),
        (
            f"https://cdn.jsdelivr.net/npm/bootstrap-icons@{BOOTSTRAP_ICONS_VERSION}/font/bootstrap-icons.css",
            vendor_dir / "bootstrap-icons" / "font" / "bootstrap-icons.css",
        ),
        (
            f"https://cdn.jsdelivr.net/npm/bootstrap-icons@{BOOTSTRAP_ICONS_VERSION}/font/fonts/bootstrap-icons.woff2",
            vendor_dir / "bootstrap-icons" / "font" / "fonts" / "bootstrap-icons.woff2",
        ),
        (
            f"https://cdn.jsdelivr.net/npm/bootstrap-icons@{BOOTSTRAP_ICONS_VERSION}/font/fonts/bootstrap-icons.woff",
            vendor_dir / "bootstrap-icons" / "font" / "fonts" / "bootstrap-icons.woff",
        ),
        (
            f"https://cdn.jsdelivr.net/npm/gridstack@{GRIDSTACK_VERSION}/dist/gridstack-all.js",
            vendor_dir / "gridstack" / "gridstack-all.js",
        ),
        (
            f"https://cdn.jsdelivr.net/npm/gridstack@{GRIDSTACK_VERSION}/dist/gridstack.min.css",
            vendor_dir / "gridstack" / "gridstack.min.css",
        ),
        (
            "https://bazaar.abuse.ch/favicon.ico",
            vendor_dir.parent / "icons" / "malwarebazaar.ico",
        ),
    ]

    for theme in BOOTSWATCH_THEMES:
        assets.append(
            (
                f"https://bootswatch.com/{BOOTSWATCH_VERSION}/{theme}/bootstrap.min.css",
                vendor_dir / "bootswatch" / theme / "bootstrap.min.css",
            )
        )

    return assets


def download_asset(client, url: str, destination: Path) -> None:
    """Скачать один файл и сохранить его в ``destination``."""

    LOGGER.info("Скачиваем %s", url)
    response = client.get(url)
    try:
        response.raise_for_status()
    except Exception as exc:  # noqa: BLE001
        LOGGER.error("Не удалось скачать %s: %s", url, exc)
        raise

    destination.parent.mkdir(parents=True, exist_ok=True)
    destination.write_bytes(response.content)
    LOGGER.debug("Сохранено %d байт в %s", len(response.content), destination)


def localize_icon_css(css_path: Path) -> None:
    """Переписать ссылки на шрифты Bootstrap Icons на локальные."""

    if not css_path.exists():
        LOGGER.warning("Файл %s не найден для локализации шрифтов", css_path)
        return

    css_content = css_path.read_text(encoding="utf-8")
    remote_prefix = (
        f"https://cdn.jsdelivr.net/npm/bootstrap-icons@{BOOTSTRAP_ICONS_VERSION}/font/fonts/"
    )
    local_prefix = "fonts/"
    if remote_prefix not in css_content:
        LOGGER.debug("Пути шрифтов в %s уже локальные", css_path)
        return

    css_path.write_text(css_content.replace(remote_prefix, local_prefix), encoding="utf-8")
    LOGGER.info("Локализованы пути к шрифтам в %s", css_path)


def download_vendor_assets(vendor_dir: Path) -> None:
    """Загрузить все внешние ассеты для UI в ``vendor_dir``."""

    assets = build_assets(vendor_dir)
    with build_sync_http_client(timeout=60.0) as client:
        for url, destination in assets:
            download_asset(client, url, destination)

    localize_icon_css(vendor_dir / "bootstrap-icons" / "font" / "bootstrap-icons.css")
    LOGGER.info("Готово. Файлы сохранены в %s", vendor_dir)


def main(argv: Iterable[str]) -> int:
    """Запустить загрузку ассетов."""

    args = parse_args(argv)
    configure_logging(args.verbose)
    LOGGER.debug("Параметры запуска: %s", args)
    try:
        download_vendor_assets(args.vendor_dir)
    except Exception as exc:  # noqa: BLE001
        LOGGER.error("Загрузка завершилась ошибкой: %s", exc)
        return 1

    return 0


if __name__ == "__main__":  # pragma: no cover - ручной запуск
    sys.exit(main(sys.argv[1:]))
