import asyncio

from datetime import datetime

from sqlalchemy.ext.asyncio import AsyncSession

from config import REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
from database import (
    delete_notification,
    filter_cluster_by_subgroup,
    filter_cluster_by_tariff,
    get_key_details,
    get_servers,
    get_tariff_by_id,
    resolve_device_limit_from_group,
    update_key_expiry,
    update_key_link,
)
from handlers.utils import ALLOWED_GROUP_CODES
from hooks.processors import process_get_cryptolink_after_renewal
from logger import (
    CLOGGER as logger,
    PANEL_REMNA,
    PANEL_XUI,
)
from panels._3xui import extend_client_key, get_xui_instance
from panels.remnawave import RemnawaveAPI

from .aggregated_links import make_aggregated_link
from ...tariffs.subgroup_migration import migrate_between_subgroups


async def resolve_cluster(session: AsyncSession, cluster_id: str):
    """Возвращает список серверов для кластера или конкретного сервера."""
    servers = await get_servers(session)
    cluster = servers.get(cluster_id)
    if cluster:
        return cluster
    found = []
    for _key, server_list in servers.items():
        for s in server_list:
            if s.get("server_name", "").lower() == cluster_id.lower():
                found.append(s)
    if found:
        return found
    raise ValueError(f"Кластер или сервер с ID/именем {cluster_id} не найден.")


async def renew_on_remnawave(
    cluster: list,
    client_id: str,
    email: str,
    tg_id: int,
    new_expiry_time: int,
    total_gb: int,
    hwid_device_limit: int,
    session: AsyncSession,
    reset_traffic: bool,
    target_server_name: str | None = None,
    external_squad_uuid: str | None = None,
    old_device_limit: int | None = None,
) -> bool:
    remnawave_nodes = [
        s for s in cluster if str(s.get("panel_type", "3x-ui")).lower() == "remnawave" and s.get("inbound_id")
    ]
    if not remnawave_nodes:
        return False
    if target_server_name:
        remnawave_nodes = [s for s in remnawave_nodes if s.get("server_name") == target_server_name] or remnawave_nodes[
            :1
        ]

    remna = RemnawaveAPI(remnawave_nodes[0]["api_url"])
    if not await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD):
        logger.error(f"{PANEL_REMNA} Не удалось войти в Remnawave API")
        return False
    hwid_device_limit = int(hwid_device_limit or 0)

    if old_device_limit is not None and hwid_device_limit < old_device_limit:
        try:
            await remna.clear_all_hwid_devices(client_id)
            logger.info(
                f"{PANEL_REMNA} HWID устройства сброшены для {client_id} (лимит {old_device_limit} → {hwid_device_limit})"
            )
        except Exception as e:
            logger.warning(f"{PANEL_REMNA} Ошибка сброса HWID: {e}")

    expire_iso = datetime.utcfromtimestamp(new_expiry_time // 1000).isoformat() + "Z"
    traffic_limit_bytes = total_gb * 1024 * 1024 * 1024 if total_gb else 0
    active_inbounds = [s["inbound_id"] for s in remnawave_nodes]

    update_kwargs = {
        "uuid": client_id,
        "expire_at": expire_iso,
        "active_user_inbounds": active_inbounds,
        "traffic_limit_bytes": traffic_limit_bytes,
        "hwid_device_limit": hwid_device_limit,
        "external_squad_uuid": external_squad_uuid,
    }

    updated = await remna.update_user(**update_kwargs)
    if updated:
        if reset_traffic:
            try:
                await remna.reset_user_traffic(client_id)
            except Exception as e:
                logger.warning(f"{PANEL_REMNA} reset_user_traffic: {e}")
        logger.info(f"{PANEL_REMNA} Подписка {client_id} успешно продлена")
        return True
    logger.debug(f"{PANEL_REMNA} Не удалось продлить {client_id}. Автосоздание отключено.")
    return False


async def renew_on_3xui(
    cluster: list,
    email: str,
    client_id: str,
    new_expiry_time: int,
    total_gb: int,
    hwid_device_limit: int,
    tg_id: int,
    update_links: bool = False,
    target_server_name: str | None = None,
):
    """Продлевает подписку на 3x-ui серверах кластера."""
    tasks = []
    for server_info in cluster:
        if target_server_name and server_info.get("server_name") != target_server_name:
            continue
        if str(server_info.get("panel_type", "3x-ui")).lower() != "3x-ui":
            continue
        inbound_id = server_info.get("inbound_id")
        server_name = server_info.get("server_name", "unknown")
        if not inbound_id:
            logger.warning(f"{PANEL_XUI} INBOUND_ID отсутствует для сервера {server_name}. Пропуск.")
            continue
        if SUPERNODE:
            unique_email = f"{email}_{server_name.lower()}"
            sub_id_val = email
        else:
            unique_email = email
            sub_id_val = unique_email
        traffic_bytes = total_gb * 1024 * 1024 * 1024 if total_gb else 0

        async def process_server(si, inbound, uniq, sub, name):
            try:
                xui = await get_xui_instance(si["api_url"])
            except Exception as e:
                logger.warning(f"{PANEL_XUI} [{name}] API недоступен: {e}")
                return name, False, f"api_unavailable: {e}"
            try:
                updated = await extend_client_key(
                    xui=xui,
                    inbound_id=int(inbound),
                    email=uniq,
                    new_expiry_time=new_expiry_time,
                    client_id=client_id,
                    total_gb=traffic_bytes,
                    sub_id=sub,
                    tg_id=tg_id,
                    limit_ip=hwid_device_limit,
                )
            except Exception as e:
                logger.warning(f"{PANEL_XUI} [{name}] ошибка продления: {e}")
                updated = False
            if updated:
                return name, True, None
            logger.debug(f"{PANEL_XUI} [{name}] не удалось обновить {uniq}. Автосоздание отключено.")
            return name, False, "no_autocreate"

        tasks.append(process_server(server_info, inbound_id, unique_email, sub_id_val, server_name))

    results = await asyncio.gather(*tasks, return_exceptions=True)
    failed = []
    succeeded = []
    for r in results:
        if isinstance(r, Exception):
            failed.append(("unknown", f"task_exception: {r}"))
            continue
        name, ok, err = r
        if ok:
            succeeded.append(name)
        else:
            failed.append((name, err or "unknown_error"))
    if succeeded:
        logger.info(f"{PANEL_XUI} продлено на: {', '.join(succeeded)}")
    if failed:
        logger.debug(f"{PANEL_XUI} не продлено на: " + ", ".join([f"{n} ({e})" for n, e in failed]))
    return succeeded, failed


async def renew_key_in_cluster(
    cluster_id: str,
    email: str,
    client_id: str,
    new_expiry_time: int,
    total_gb: int,
    session: AsyncSession,
    hwid_device_limit: int = 0,
    reset_traffic: bool = True,
    target_subgroup: str | None = None,
    old_subgroup: str | None = None,
    plan=None,
):
    """Продлевает ключ в кластере с учётом подгрупп и Remnawave/3x-ui."""
    try:
        servers_map = await get_servers(session)

        kd = await get_key_details(session, email)
        if not kd or kd.get("client_id") != client_id:
            logger.error(f"Не найден ключ по email={email} и client_id={client_id}")
            return False

        tg_id = int(kd["tg_id"])
        server_id = kd["server_id"]

        old_device_limit = kd.get("current_device_limit") or kd.get("selected_device_limit")
        if old_device_limit is None and kd.get("tariff_id"):
            old_tariff = await get_tariff_by_id(session, kd["tariff_id"])
            if old_tariff:
                old_device_limit = old_tariff.get("device_limit")

        single_server = None
        if servers_map.get(server_id):
            cluster = servers_map[server_id]
        else:
            for _k, sl in servers_map.items():
                for s in sl:
                    if s.get("server_name") == server_id:
                        single_server = s
                        break
                if single_server:
                    break
            cluster = (
                [single_server]
                if single_server
                else servers_map.get(cluster_id) or await resolve_cluster(session, cluster_id)
            )

        external_squad_uuid = None
        if plan is not None:
            tariff = await get_tariff_by_id(session, plan)
            if tariff:
                raw_external_squad = tariff.get("external_squad")
                if raw_external_squad:
                    external_squad_uuid = raw_external_squad
                else:
                    external_squad_uuid = ""
                is_configurable = tariff.get("configurable", False)
                if not is_configurable:
                    tariff_device_limit = tariff.get("device_limit")
                    if tariff_device_limit is not None:
                        hwid_device_limit = int(tariff_device_limit)
        else:
            dl = await resolve_device_limit_from_group(session, server_id)
            if dl is not None:
                hwid_device_limit = dl

        if (target_subgroup or "") != (old_subgroup or "") and not single_server:
            new_client_id, remna_link = await migrate_between_subgroups(
                session=session,
                cluster_all=cluster,
                cluster_id=cluster_id,
                email=email,
                client_id=client_id,
                tg_id=tg_id,
                new_expiry_time=new_expiry_time,
                total_gb=total_gb,
                hwid_device_limit=hwid_device_limit,
                reset_traffic=reset_traffic,
                old_subgroup=old_subgroup,
                target_subgroup=target_subgroup,
                external_squad_uuid=external_squad_uuid,
                tariff_id=plan,
            )

            await update_key_expiry(session, new_client_id or client_id, new_expiry_time)
            for prefix in ["key_24h", "key_10h", "key_expired", "renew"]:
                await delete_notification(session, tg_id, f"{email}_{prefix}")

            try:
                key_link = await make_aggregated_link(
                    session=session,
                    cluster_all=cluster,
                    cluster_id=cluster_id,
                    email=email,
                    client_id=new_client_id or client_id,
                    tg_id=tg_id,
                    subgroup_code=target_subgroup,
                    remna_link_override=remna_link,
                    plan=plan,
                )
                if key_link:
                    await update_key_link(session, email, key_link)
            except Exception as le:
                logger.warning(f"[Link] ошибка генерации/сохранения после миграции: {le}")

            return True

        if single_server:
            cluster_scope = [single_server]
        else:
            if plan is not None:
                filtered = await filter_cluster_by_tariff(session, cluster, plan, cluster_id)
                if filtered is not cluster:
                    cluster_scope = filtered
                elif target_subgroup:
                    cluster_scope = await filter_cluster_by_subgroup(
                        session, cluster, target_subgroup, cluster_id, tariff_id=plan
                    )
                else:
                    cluster_scope = cluster
            elif target_subgroup:
                target = await filter_cluster_by_subgroup(session, cluster, target_subgroup, cluster_id, tariff_id=plan)
                cluster_scope = target if target else cluster
            else:
                cluster_scope = cluster

            if plan is not None:
                tariff_for_filter = await get_tariff_by_id(session, plan)
                if tariff_for_filter:
                    gc = (tariff_for_filter.get("group_code") or "").lower()
                    if gc in ALLOWED_GROUP_CODES:
                        bound_servers = [s for s in cluster_scope if gc in (s.get("special_groups") or [])]
                        if bound_servers:
                            cluster_scope = bound_servers
                        else:
                            logger.info(f"[Renewal] Нет серверов со спецгруппой '{gc}' в {cluster_id}")

            if not cluster_scope:
                logger.warning(f"[Renewal] Нет серверов после фильтрации в кластере {cluster_id}")
                return False

        remna_ok = await renew_on_remnawave(
            cluster=cluster_scope,
            client_id=client_id,
            email=email,
            tg_id=tg_id,
            new_expiry_time=new_expiry_time,
            total_gb=total_gb,
            hwid_device_limit=hwid_device_limit,
            session=session,
            reset_traffic=reset_traffic,
            target_server_name=server_id if single_server else None,
            external_squad_uuid=external_squad_uuid,
            old_device_limit=old_device_limit,
        )

        succeeded, _ = await renew_on_3xui(
            cluster=cluster_scope,
            email=email,
            client_id=client_id,
            new_expiry_time=new_expiry_time,
            total_gb=total_gb,
            hwid_device_limit=hwid_device_limit,
            tg_id=tg_id,
            update_links=False,
            target_server_name=server_id if single_server else None,
        )

        if remna_ok or succeeded:
            await update_key_expiry(session, client_id, new_expiry_time)
            for prefix in ["key_24h", "key_10h", "key_expired", "renew"]:
                await delete_notification(session, tg_id, f"{email}_{prefix}")

            try:
                remna_link_override = None
                if remna_ok and cluster_scope:
                    remnawave_nodes = [
                        s
                        for s in cluster_scope
                        if str(s.get("panel_type", "3x-ui")).lower() == "remnawave" and s.get("inbound_id")
                    ]
                    if remnawave_nodes:
                        remna_link_override = await process_get_cryptolink_after_renewal(
                            email=email,
                            cluster_id=cluster_id,
                            plan=plan,
                            session=session,
                            tg_id=tg_id,
                            remnawave_nodes=remnawave_nodes,
                        )

                key_link = await make_aggregated_link(
                    session=session,
                    cluster_all=cluster_scope,
                    cluster_id=cluster_id,
                    email=email,
                    client_id=client_id,
                    tg_id=tg_id,
                    subgroup_code=target_subgroup,
                    remna_link_override=remna_link_override,
                    plan=plan,
                )
                if key_link:
                    await update_key_link(session, email, key_link)
            except Exception as le:
                logger.warning(f"[Link] ошибка генерации/сохранения после продления: {le}")

            return True

        return False
    except Exception as e:
        logger.error(f"Не удалось продлить ключ {client_id} в кластере/на сервере {cluster_id}: {e}")
        raise
