#!/usr/bin/env python3
"""Загрузка рейтинга автономных систем из REST API ASRank."""

from __future__ import annotations

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

import httpx

from ioc_checker.http_client import build_sync_http_client


LOGGER = logging.getLogger("update-asrank")
DEFAULT_URL = "https://api.asrank.caida.org/v2/restful/asns"
DEFAULT_PAGE_SIZE = 500
OUTPUT_FILE = Path(__file__).resolve().parent.parent / "lists" / "asns.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=__doc__)
    parser.add_argument("--url", default=DEFAULT_URL, help="REST-эндпоинт ASRank")
    parser.add_argument(
        "--page-size",
        type=int,
        default=DEFAULT_PAGE_SIZE,
        help="Количество записей в одной странице (по умолчанию 500)",
    )
    parser.add_argument(
        "--destination",
        type=Path,
        default=OUTPUT_FILE,
        help="Путь для сохранения файла asns.csv",
    )
    parser.add_argument(
        "--verbose", "-v", action="store_true", help="Подробный вывод логов"
    )
    return parser.parse_args(list(argv))


def fetch_page(
    client: httpx.Client, url: str, first: int, offset: int
) -> Tuple[list[dict], bool, int]:
    """Запросить страницу с автономными системами и вернуть данные."""

    LOGGER.debug("Запрашиваем %s записей, offset=%s", first, offset)
    try:
        response = client.get(
            url,
            params={"first": first, "offset": offset},
            headers={"accept": "application/json"},
        )
        response.raise_for_status()
    except httpx.HTTPError as exc:
        raise SystemExit(f"Ошибка при запросе данных ASRank: {exc}") from exc

    try:
        payload = response.json()
        asns = payload["data"]["asns"]
        edges = asns.get("edges", [])
        has_next = asns.get("pageInfo", {}).get("hasNextPage", False)
        total = int(asns.get("totalCount", 0))
    except (KeyError, TypeError, ValueError) as exc:
        raise SystemExit(
            f"Не удалось разобрать ответ ASRank: {exc}; тело: {response.text[:500]}"
        ) from exc

    LOGGER.debug(
        "Получено %s записей, has_next=%s, total=%s", len(edges), has_next, total
    )
    return edges, has_next, total


def write_header(destination: Path) -> None:
    """Создать CSV и записать заголовок."""

    destination.parent.mkdir(parents=True, exist_ok=True)
    with destination.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.writer(handle)
        writer.writerow(["asn", "rank"])


def append_edges(destination: Path, edges: list[dict]) -> None:
    """Добавить строки в CSV из списка записей REST-ответа."""

    with destination.open("a", newline="", encoding="utf-8") as handle:
        writer = csv.writer(handle)
        for edge in edges:
            node = edge.get("node", {})
            writer.writerow([node.get("asn"), node.get("rank")])


def update_asrank(url: str, destination: Path, page_size: int) -> None:
    """Выгрузить рейтинг ASRank и сохранить в CSV."""

    write_header(destination)
    offset = 0
    total = None

    with build_sync_http_client(timeout=60.0, follow_redirects=True) as client:
        while True:
            edges, has_next, total_count = fetch_page(client, url, page_size, offset)
            if total is None:
                total = total_count
                LOGGER.info("Всего записей: %s", total)

            if not edges:
                LOGGER.warning(
                    "Получена пустая страница: offset=%s, завершаем загрузку", offset
                )
                break

            append_edges(destination, edges)
            offset += len(edges)
            LOGGER.info("Сохранено %s / %s", offset, total or "?")

            if not has_next:
                break

    LOGGER.info("Файл с рейтингом ASRank сохранён в %s", destination)


def main(argv: Iterable[str]) -> int:
    """Точка входа для скрипта обновления ASRank."""

    args = parse_args(argv)
    configure_logging(args.verbose)
    update_asrank(args.url, args.destination, args.page_size)
    return 0


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