# Приложение обеспечивает выгрузку файлов с портала наблюдения stat.vybory.gov.ru
#
# для запуска необходимо установить python3
# зависимости: requests (pip install requests)
#              fake_useragent (опционально, pip install fake_useragent)
#
# АВТОРИЗАЦИЯ (обновлено, сентябрь 2026):
# Портал больше не использует заголовок "Authorization: Bearer <token>".
# Теперь авторизация выполняется куки-токеном stat_acc_t (JWT, выдаётся ЕСИА
# при входе наблюдателя). Достаточно ОДНОЙ этой куки — session-cookie и
# refr_t для чтения API не требуются (проверено вызовами к защищённым
# эндпоинтам без них).
#
# Как получить stat_acc_t:
#   Chrome DevTools -> Application -> Cookies -> https://stat.vybory.gov.ru
#   -> найти строку stat_acc_t -> скопировать Value
# (Кука httpOnly, поэтому её не видно в document.cookie и в
# Local Storage — только в разделе Cookies.)
#
# Токен - это JWT со сроком жизни ~3 часа (поля iat/exp внутри токена).
# После истечения срока запросы к защищённым эндпоинтам возвращают:
#   HTTP 401  {"error":{"code":"AUTH_ERROR","description":"Запрос не авторизован"}}
# В этом случае нужно заново скопировать куку из браузера (авто-обновление
# по refr_t на стороне клиента в этом скрипте не реализовано).
#
# Значение куки нужно положить в файл stat_acc_t.txt (одна строка, без кавычек).
#
# Скрипт запускается командой: python3 file_downloader_cookie.py
#
# Структура сайта/API описана в API_REFERENCE.md рядом с этим файлом.
#
# Исходная версия (bearer-токен): file-downloader.py, Alexander Isavnin.
# Переписано на куки-авторизацию (stat_acc_t) — сентябрь 2026.

import json
import os
import time
from datetime import datetime

import requests
from urllib3.exceptions import InsecureRequestWarning

try:
    from fake_useragent import UserAgent
    USER_AGENT = UserAgent().random
except Exception:
    USER_AGENT = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")

BASE_URL = "https://stat.vybory.gov.ru/api/"
TOKEN_FILE = "stat_acc_t.txt"

# Пауза между запросами (сек). Увеличьте, если сервер начнёт отвечать 429/503.
REQUEST_DELAY = 0.0

# Если True — уже скачанные файлы (есть на диске) не перекачиваются повторно.
SKIP_EXISTING = True

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)


def get_str_time_now(fmt_kind="YMDHMS"):
    """Замена внешней зависимости utils.getStrTimeNow — метка времени запуска."""
    return datetime.now().strftime("%Y%m%d_%H%M%S")


def load_token():
    with open(TOKEN_FILE, "r", encoding="utf-8") as f:
        return f.read().rstrip().strip()


def make_session(token):
    sess = requests.Session()
    sess.headers.update({"User-Agent": USER_AGENT})
    # Новая авторизация: кука stat_acc_t вместо заголовка Authorization.
    sess.cookies.set("stat_acc_t", token, domain="stat.vybory.gov.ru")
    print (":"+token+":")
    print (sess.cookies)
    return sess


def check_token(sess):
    """Проверяем, что кука действительна, дёргая защищённый эндпоинт."""
    print(BASE_URL + "idm/user/me")
    resp = sess.get(BASE_URL + "idm/user/me", verify=False, timeout=20)
#    resp = sess.get("https://stat.vybory.gov.ru/api/voting/regions", verify=False, timeout=20)
    print (resp.text)

    try:
        data = resp.json()
    except ValueError:
        print("Не удалось разобрать ответ idm/user/me:", resp.status_code, resp.text[:300])
        return False

    if resp.status_code == 401 or "error" in data:
        print("Ошибка авторизации:", data)
        print("Похоже, кука stat_acc_t недействительна или истекла.")
        print("Скопируйте новое значение из DevTools -> Application -> Cookies "
              "-> stat_acc_t и обновите файл", TOKEN_FILE)
        return False

    user = data.get("data", {})
    print("Авторизован как:", user.get("lastName"), user.get("firstName"), user.get("middleName"))
    return True


def write_json(folder_path, obj):
    with open(os.path.join(folder_path, "info.txt"), "w", encoding="utf-8") as f:
        f.write(json.dumps(obj, ensure_ascii=False, indent=2))


def safe_get(sess, path, params=None):
    resp = sess.get(BASE_URL + path, params=params, verify=False, timeout=30)
    resp.raise_for_status()
    return resp.json()


def download_file(sess, filename, contract_id, dest_path):
    if SKIP_EXISTING and os.path.exists(dest_path) and os.path.getsize(dest_path) > 0:
        return True
    download_url = f"{BASE_URL}files/{filename}/contract/{contract_id}/download"
    resp = sess.post(download_url, allow_redirects=True, verify=False, timeout=60)
    if resp.status_code != 200:
        print("-----Failed download:", filename, resp.status_code, resp.text[:200])
        return False
    with open(dest_path, "wb") as f:
        f.write(resp.content)
    return True


def main():
    token = load_token()
    sess = make_session(token)

    if not check_token(sess):
        return

    start_time = time.time()
    current_path = get_str_time_now()
    os.makedirs(current_path, exist_ok=True)

    regions_resp = safe_get(sess, "voting/regions")
    regions = regions_resp.get("data", {}).get("regions", [])
    print(f"Регионов найдено: {len(regions)}")

    for region in regions:
        region_code = region["code"]
        print("=== Регион", region_code, region.get("description"))
        region_path = os.path.join(current_path, str(region_code))
        os.makedirs(region_path, exist_ok=True)

        try:
            elections_resp = safe_get(sess, f"elections/region/{region_code}/elections")
        except Exception as e:
            print("-----Failed elections for region", region_code, e)
            continue

        election_levels = elections_resp.get("data", {}).get("elections", [])
        for election_level in election_levels:
            for election in election_level.get("elections", []):
                election_id = election["electionId"]
                print("  --- Выборы:", election.get("electionName"))
                election_path = os.path.join(region_path, election_id)
                os.makedirs(election_path, exist_ok=True)
                write_json(election_path, election)

                try:
                    districts_resp = safe_get(
                        sess,
                        "elections/districts",
                        params={"regionCode": region_code, "electionId": election_id},
                    )
                    districts = districts_resp.get("data", [])
                except Exception as e:
                    print("-----Failed districts:", region_code, election_id, e)
                    continue

                for district in districts:
                    district_id = district["id"]
                    print("    --- Округ:", district.get("number"), district.get("name"))
                    district_path = os.path.join(election_path, district_id)
                    os.makedirs(district_path, exist_ok=True)
                    write_json(district_path, district)

                    try:
                        voting_resp = safe_get(
                            sess,
                            "statistics/voting",
                            params={"districtId": district_id, "electionId": election_id},
                        )
                        votings = voting_resp.get("data", {}).get("votings", [])
                    except Exception as e:
                        print("-----Failed statistics/voting:", district_id, e)
                        continue

                    for voting in votings:
                        contract_id = voting["counters"]["contractId"]
                        voting_path = os.path.join(district_path, contract_id)
                        os.makedirs(voting_path, exist_ok=True)
                        write_json(voting_path, voting)

                        try:
                            files_resp = safe_get(sess, f"files/{contract_id}")
                            filenames = files_resp.get("data", [])
                        except Exception as e:
                            print("-----Failed files list:", contract_id, e)
                            continue

                        for filename in filenames:
                            dest_path = os.path.join(voting_path, filename)
                            ok = download_file(sess, filename, contract_id, dest_path)
                            if ok:
                                print("      OK:", filename)
                            if REQUEST_DELAY:
                                time.sleep(REQUEST_DELAY)

    elapsed = time.time() - start_time
    print("Время выполнения:", elapsed, "сек")


if __name__ == "__main__":
    main()
