import sys
import time
from typing import Optional, Dict, List

import requests
import pandas as pd

DEXSCREENER_URL_TEMPLATE_MULTI = "https://api.dexscreener.com/latest/dex/tokens/{mints}"


def log(msg: str) -> None:
    print(msg, flush=True)


def choose_best_pair(pairs: List[dict]) -> dict:
    """
    Выбор "лучшего" пула:
      - сначала solana / raydium / SOL
      - если не нашли, берём первый.
    """
    if not pairs:
        return {}

    for pair in pairs:
        try:
            if (
                pair.get("chainId") == "solana"
                and pair.get("dexId") == "raydium"
                and pair.get("quoteToken", {}).get("symbol") == "SOL"
            ):
                return pair
        except AttributeError:
            continue

    return pairs[0]


def get_dexscreener_market_caps_batch(mints: List[str]) -> Dict[str, Optional[int]]:
    """
    Запрос к dexscreener по пачке токенов (до 20):
      - https://api.dexscreener.com/latest/dex/tokens/{mint1},{mint2},...
      - для каждого mint выбираем лучший пул и берём 'marketCap'
      - возвращаем словарь {mint: marketCap_int или None}
    """
    result: Dict[str, Optional[int]] = {mint: None for mint in mints if mint}

    mints_clean = [m for m in mints if m]
    if not mints_clean:
        return result

    mints_str = ",".join(mints_clean)
    url = DEXSCREENER_URL_TEMPLATE_MULTI.format(mints=mints_str)
    log(f"  [dexscreener batch] GET {url}")
    try:
        resp = requests.get(url, timeout=15)
    except Exception as e:
        log(f"  [dexscreener batch] Ошибка запроса: {e}")
        return result

    if resp.status_code != 200:
        log(f"  [dexscreener batch] Неверный статус ответа: {resp.status_code}")
        return result

    try:
        data = resp.json()
    except Exception as e:
        log(f"  [dexscreener batch] Ошибка парсинга JSON: {e}")
        return result

    pairs = data.get("pairs") or []
    if not isinstance(pairs, list) or not pairs:
        log("  [dexscreener batch] 'pairs' пустой или отсутствует")
        return result

    # группируем пары по baseToken.address
    pairs_by_mint: Dict[str, List[dict]] = {}
    for pair in pairs:
        base_addr = (pair.get("baseToken") or {}).get("address")
        if not base_addr:
            continue
        pairs_by_mint.setdefault(base_addr, []).append(pair)

    # для каждого mint выбираем лучший пул и берём marketCap
    for mint in mints_clean:
        mint_pairs = pairs_by_mint.get(mint, [])
        if not mint_pairs:
            log(f"    [dexscreener batch] Для токена {mint} не найдено ни одного пула")
            result[mint] = None
            continue

        chosen_pair = choose_best_pair(mint_pairs)
        mc = chosen_pair.get("marketCap")
        if mc is None:
            log(f"    [dexscreener batch] Для токена {mint} 'marketCap' отсутствует или = null")
            result[mint] = None
            continue

        try:
            value = int(mc)
            result[mint] = value
        except (TypeError, ValueError) as e:
            log(f"    [dexscreener batch] Ошибка преобразования marketCap для {mint}: {e}")
            result[mint] = None

    return result


def detect_token_column(df: pd.DataFrame) -> str:
    """
    Пытаемся угадать колонку с токенами:
      - ищем по названию: 'token', 'ca', 'address' и т.п.
      - если не нашли — берём первый столбец.
    """
    candidates = ["token", "tokens", "ca", "address", "mint", "contract"]
    for col in df.columns:
        name = str(col).lower()
        if any(c in name for c in candidates):
            log(f"Найдена колонка токенов по имени: {col!r}")
            return col

    first_col = df.columns[0]
    log(f"Колонка токенов не найдена по имени, используем первый столбец: {first_col!r}")
    return first_col


def process_excel(
    input_path: str = "арс.xlsx",
    output_path: str = "арс_with_mc.xlsx",
    delay: float = 0.5,
    batch_size: int = 20,
) -> None:
    """
    1) Читает арс.xlsx (первая строка — заголовки).
    2) Берёт токены из колонки с адресами (пытается угадать; если нет — первая колонка).
    3) Делает батчевые запросы к dexscreener по 20 токенов.
    4) Из "начала пула" (solana/raydium/SOL, иначе первый пул) берёт marketCap.
    5) Записывает marketCap в Excel-колонку H (8-й столбец).
    """

    log(f"Читаю файл: {input_path}")
    df = pd.read_excel(input_path, engine="openpyxl")  # первая строка — заголовки

    token_col = detect_token_column(df)
    tokens = df[token_col].astype(str)
    total = len(tokens)

    market_caps: List[Optional[int]] = [None] * total

    # идём по токенам пачками по batch_size
    log("\n=== Сбор marketCap из Dexscreener батчами ===")
    idx_start = 0
    batch_num = 1
    while idx_start < total:
        idx_end = min(idx_start + batch_size, total)
        batch_tokens = [t.strip() for t in tokens.iloc[idx_start:idx_end].tolist()]

        log(f"\n[batch {batch_num}] Обрабатываю строки {idx_start + 1}–{idx_end}")

        # убираем пустые, но сохраняем глобальные индексы
        non_empty_tokens = []
        index_map = []  # local -> global index
        for local_idx, mint in enumerate(batch_tokens):
            if mint:
                non_empty_tokens.append(mint)
                index_map.append(idx_start + local_idx)

        if not non_empty_tokens:
            log("  В этой пачке нет непустых токенов, пропускаю.")
            idx_start = idx_end
            batch_num += 1
            continue

        batch_caps = get_dexscreener_market_caps_batch(non_empty_tokens)
        time.sleep(delay)

        # записываем значения в массив по глобальным индексам
        for mint, global_idx in zip(non_empty_tokens, index_map):
            market_caps[global_idx] = batch_caps.get(mint)

        idx_start = idx_end
        batch_num += 1

    # --- запись в колонку H (8-й столбец) ---
    # если колонок меньше 8 — добавим нужное количество до 8-й
    if df.shape[1] < 8:
        log("В файле меньше 8 столбцов, добавляю пустые до колонки H...")
        while df.shape[1] < 7:
            df[f"extra_{df.shape[1] + 1}"] = None
        # вставляем marketCap именно на позицию 7 (индекс 7 -> колонка H)
        df.insert(7, "marketCap", market_caps)
        log("marketCap записан в новый столбец 'marketCap' (Excel колонка H).")
    else:
        col_name_H = df.columns[7]
        log(f"marketCap записываю в существующий столбец {col_name_H!r} (Excel колонка H).")
        df.iloc[:, 7] = market_caps
        # если хочешь переименовать заголовок в 'marketCap', раскомментируй строку ниже:
        # df.rename(columns={col_name_H: "marketCap"}, inplace=True)

    df.to_excel(output_path, engine="openpyxl", index=False)
    log(f"\nГотово! Результат записан в файл: {output_path}")


if __name__ == "__main__":
    try:
        log("Запуск скрипта...")
        log("Версия Python: " + sys.version.replace("\n", " "))
        process_excel()
    except Exception as e:
        log(f"\n[ОШИБКА] Скрипт упал: {e!r}")
        raise
