import asyncio
import uuid
from datetime import datetime
from typing import Any

import pytz
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message, WebAppInfo
from aiogram.utils.keyboard import InlineKeyboardBuilder
from py3xui import AsyncApi
from sqlalchemy import func, select, update
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession

from bot import bot
from config import (
    ADMIN_PASSWORD,
    ADMIN_USERNAME,
    CONNECT_PHONE_BUTTON,
    PUBLIC_LINK,
    REMNAWAVE_LOGIN,
    REMNAWAVE_PASSWORD,
    SUPPORT_CHAT_URL,
)
from database import (
    add_user,
    check_server_name_by_cluster,
    check_user_exists,
    get_key_details,
    get_trial,
    update_balance,
    update_trial,
)
from database.models import Key, Server, Tariff
from handlers.buttons import (
    BACK,
    CONNECT_DEVICE,
    CONNECT_PHONE,
    MAIN_MENU,
    PC_BUTTON,
    SUPPORT,
    TV_BUTTON,
)
from handlers.keys.key_utils import create_client_on_server
from handlers.texts import SELECT_COUNTRY_MSG, key_message_success
from handlers.utils import (
    edit_or_send_message,
    generate_random_email,
    get_least_loaded_cluster,
    is_full_remnawave_cluster,
)
from logger import logger
from panels.remnawave import RemnawaveAPI
from panels.three_xui import delete_client, get_xui_instance

router = Router()

moscow_tz = pytz.timezone("Europe/Moscow")


async def key_country_mode(
    tg_id: int,
    expiry_time: datetime,
    state: FSMContext,
    session: AsyncSession,
    message_or_query: Message | CallbackQuery | None = None,
    old_key_name: str = None,
    plan: int = None,
):
    target_message = None
    safe_to_edit = False

    if state and plan:
        await state.update_data(tariff_id=plan)

    if isinstance(message_or_query, CallbackQuery) and message_or_query.message:
        target_message = message_or_query.message
        safe_to_edit = True
    elif isinstance(message_or_query, Message):
        target_message = message_or_query
        safe_to_edit = True

    least_loaded_cluster = await get_least_loaded_cluster(session)
    if not least_loaded_cluster:
        logger.error("❌ Не удалось определить наименее загруженный кластер")
        text = "❌ Нет доступных кластеров для создания ключа."
        if safe_to_edit:
            await edit_or_send_message(
                target_message=target_message, text=text, reply_markup=None
            )
        else:
            await bot.send_message(chat_id=tg_id, text=text)
        return

    result = await session.execute(
        select(
            Server.server_name,
            Server.api_url,
            Server.panel_type,
            Server.enabled,
            Server.max_keys,
        ).where(Server.cluster_name == least_loaded_cluster)
    )
    servers = result.mappings().all()

    if not servers:
        logger.error(f"❌ Нет серверов в кластере {least_loaded_cluster}")
        text = "❌ Нет доступных серверов в выбранном кластере."
        if safe_to_edit:
            await edit_or_send_message(
                target_message=target_message, text=text, reply_markup=None
            )
        else:
            await bot.send_message(chat_id=tg_id, text=text)
        return

    available_servers = []
    tasks = [
        asyncio.create_task(check_server_availability(server, session))
        for server in servers
    ]
    results = await asyncio.gather(*tasks, return_exceptions=True)

    for server, result in zip(servers, results, strict=False):
        if result is True:
            available_servers.append(server["server_name"])

    if not available_servers:
        logger.warning(
            f"[Country Selection] Нет доступных серверов в кластере {least_loaded_cluster}"
        )
        text = "❌ Нет доступных серверов в выбранном кластере."
        if safe_to_edit:
            await edit_or_send_message(
                target_message=target_message, text=text, reply_markup=None
            )
        else:
            await bot.send_message(chat_id=tg_id, text=text)
        return

    logger.info(
        f"[Country Selection] Доступные сервера в кластере {least_loaded_cluster}: {available_servers}"
    )

    builder = InlineKeyboardBuilder()
    ts = int(expiry_time.timestamp())
    for server_name in available_servers:
        if old_key_name:
            callback_data = f"select_country|{server_name}|{ts}|{old_key_name}"
        else:
            callback_data = f"select_country|{server_name}|{ts}"
        builder.row(InlineKeyboardButton(text=server_name, callback_data=callback_data))

    builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))

    if safe_to_edit:
        await edit_or_send_message(
            target_message=target_message,
            text=SELECT_COUNTRY_MSG,
            reply_markup=builder.as_markup(),
        )
    else:
        await bot.send_message(
            chat_id=tg_id,
            text=SELECT_COUNTRY_MSG,
            reply_markup=builder.as_markup(),
        )


@router.callback_query(F.data.startswith("change_location|"))
async def change_location_callback(callback_query: CallbackQuery, session: Any):
    try:
        data = callback_query.data.split("|")
        if len(data) < 2:
            await callback_query.answer("❌ Некорректные данные", show_alert=True)
            return

        old_key_name = data[1]
        record = await get_key_details(session, old_key_name)
        if not record:
            await callback_query.answer("❌ Ключ не найден", show_alert=True)
            return

        expiry_timestamp = record["expiry_time"]
        ts = int(expiry_timestamp / 1000)

        current_server = record["server_id"]

        cluster_info = await check_server_name_by_cluster(session, current_server)
        if not cluster_info:
            await callback_query.answer(
                "❌ Кластер для текущего сервера не найден", show_alert=True
            )
            return

        cluster_name = cluster_info["cluster_name"]

        servers = (
            (
                await session.execute(
                    select(
                        Server.server_name,
                        Server.api_url,
                        Server.panel_type,
                        Server.enabled,
                        Server.max_keys,
                    )
                    .where(Server.cluster_name == cluster_name)
                    .where(Server.server_name != current_server)
                )
            )
            .mappings()
            .all()
        )
        if not servers:
            await callback_query.answer(
                "❌ Доступных серверов в кластере не найдено", show_alert=True
            )
            return

        available_servers = []
        tasks = []

        for server in servers:
            server_info = {
                "server_name": server["server_name"],
                "api_url": server["api_url"],
                "panel_type": server["panel_type"],
                "enabled": server.get("enabled", True),
                "max_keys": server.get("max_keys"),
            }
            task = asyncio.create_task(check_server_availability(server_info, session))
            tasks.append(task)

        results = await asyncio.gather(*tasks, return_exceptions=True)

        for server, result in zip(servers, results, strict=False):
            if result is True:
                available_servers.append(server["server_name"])

        if not available_servers:
            await callback_query.answer(
                "❌ Нет доступных серверов для смены локации", show_alert=True
            )
            return

        logger.info(f"Доступные страны для смены локации: {available_servers}")

        builder = InlineKeyboardBuilder()
        for country in available_servers:
            callback_data = f"select_country|{country}|{ts}|{old_key_name}"
            builder.row(InlineKeyboardButton(text=country, callback_data=callback_data))
        builder.row(
            InlineKeyboardButton(text=BACK, callback_data=f"view_key|{old_key_name}")
        )

        await edit_or_send_message(
            target_message=callback_query.message,
            text="🌍 Пожалуйста, выберите новую локацию для вашей подписки:",
            reply_markup=builder.as_markup(),
            media_path=None,
        )
    except Exception as e:
        logger.error(
            f"Ошибка при смене локации для пользователя {callback_query.from_user.id}: {e}"
        )
        await callback_query.answer(
            "❌ Ошибка смены локации. Попробуйте снова.", show_alert=True
        )


@router.callback_query(F.data.startswith("select_country|"))
async def handle_country_selection(
    callback_query: CallbackQuery, session: Any, state: FSMContext
):
    """
    Обрабатывает выбор страны.
    Формат callback data:
      select_country|{selected_country}|{ts} [|{old_key_name} (опционально)]
    Если передан old_key_name – значит, происходит смена локации.
    """
    data = callback_query.data.split("|")
    if len(data) < 3:
        await callback_query.message.answer("❌ Некорректные данные. Попробуйте снова.")
        return

    selected_country = data[1]
    try:
        ts = int(data[2])
    except ValueError:
        await callback_query.message.answer(
            "❌ Некорректное время истечения. Попробуйте снова."
        )
        return

    expiry_time = datetime.fromtimestamp(ts, tz=moscow_tz)

    old_key_name = data[3] if len(data) > 3 else None

    tg_id = callback_query.from_user.id
    logger.info(f"Пользователь {tg_id} выбрал страну: {selected_country}")
    logger.info(f"Получено время истечения (timestamp): {ts}")

    await finalize_key_creation(
        tg_id,
        expiry_time,
        selected_country,
        state,
        session,
        callback_query,
        old_key_name,
    )


async def finalize_key_creation(
    tg_id: int,
    expiry_time: datetime,
    selected_country: str,
    state: FSMContext | None,
    session: AsyncSession,
    callback_query: CallbackQuery,
    old_key_name: str = None,
    tariff_id: int | None = None,
):
    from_user = callback_query.from_user

    if not await check_user_exists(session, tg_id):
        await add_user(
            session=session,
            tg_id=from_user.id,
            username=from_user.username,
            first_name=from_user.first_name,
            last_name=from_user.last_name,
            language_code=from_user.language_code,
            is_bot=from_user.is_bot,
        )
        logger.info(f"[User] Новый пользователь {tg_id} добавлен")

    expiry_time = expiry_time.astimezone(moscow_tz)

    if old_key_name:
        old_key_details = await get_key_details(session, old_key_name)
        if not old_key_details:
            await callback_query.message.answer("❌ Ключ не найден. Попробуйте снова.")
            return

        key_name = old_key_name
        client_id = old_key_details["client_id"]
        email = old_key_details["email"]
        expiry_timestamp = old_key_details["expiry_time"]

    else:
        while True:
            key_name = generate_random_email()
            existing_key = await get_key_details(session, key_name)
            if not existing_key:
                break
        client_id = str(uuid.uuid4())
        email = key_name.lower()
        expiry_timestamp = int(expiry_time.timestamp() * 1000)

    public_link = None
    remnawave_link = None
    created_at = int(datetime.now(moscow_tz).timestamp() * 1000)

    try:
        result = await session.execute(
            select(Server).where(Server.server_name == selected_country)
        )
        server_info = result.scalar_one_or_none()
        if not server_info:
            raise ValueError(f"Сервер {selected_country} не найден")

        panel_type = server_info.panel_type.lower()
        cluster_info = await check_server_name_by_cluster(
            session, server_info.server_name
        )
        if not cluster_info:
            raise ValueError(f"Кластер для сервера {server_info.server_name} не найден")

        is_full_remnawave = await is_full_remnawave_cluster(
            cluster_info["cluster_name"], session
        )

        if old_key_name:
            old_server_id = old_key_details["server_id"]
            if old_server_id:
                result = await session.execute(
                    select(Server).where(Server.server_name == old_server_id)
                )
                old_server_info = result.scalar_one_or_none()
                if old_server_info:
                    try:
                        if old_server_info.panel_type.lower() == "3x-ui":
                            xui = await get_xui_instance(old_server_info.api_url)
                            await delete_client(
                                xui, old_server_info.inbound_id, email, client_id
                            )

                            await session.execute(
                                update(Key)
                                .where(Key.tg_id == tg_id, Key.email == email)
                                .values(key=None)
                            )
                        elif old_server_info.panel_type.lower() == "remnawave":
                            remna = RemnawaveAPI(old_server_info.api_url)
                            if await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD):
                                await remna.delete_user(client_id)
                                await session.execute(
                                    update(Key)
                                    .where(Key.tg_id == tg_id, Key.email == email)
                                    .values(remnawave_link=None)
                                )
                    except Exception as e:
                        logger.warning(f"[Delete] Ошибка при удалении клиента: {e}")

        if panel_type == "remnawave" or is_full_remnawave:
            remna = RemnawaveAPI(server_info.api_url)
            if not await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD):
                raise ValueError(
                    f"❌ Не удалось авторизоваться в Remnawave ({server_info.server_name})"
                )

            expire_at = (
                datetime.utcfromtimestamp(expiry_timestamp / 1000).isoformat() + "Z"
            )
            user_data = {
                "username": email,
                "trafficLimitStrategy": "NO_RESET",
                "expireAt": expire_at,
                "telegramId": tg_id,
                "activeUserInbounds": [server_info.inbound_id],
                "hwidDeviceLimit": 0,
            }
            result = await remna.create_user(user_data)
            if not result:
                raise ValueError("❌ Ошибка при создании пользователя в Remnawave")

            client_id = result.get("uuid")
            remnawave_link = result.get("subscriptionUrl")

            if old_key_name:
                await session.execute(
                    update(Key)
                    .where(Key.tg_id == tg_id, Key.email == email)
                    .values(client_id=client_id)
                )

        if panel_type == "3x-ui":
            semaphore = asyncio.Semaphore(2)
            await create_client_on_server(
                server_info={
                    "api_url": server_info.api_url,
                    "inbound_id": server_info.inbound_id,
                    "server_name": server_info.server_name,
                    "panel_type": server_info.panel_type,
                },
                tg_id=tg_id,
                client_id=client_id,
                email=email,
                expiry_timestamp=expiry_timestamp,
                semaphore=semaphore,
            )
            public_link = f"{PUBLIC_LINK}{email}/{tg_id}"

        logger.info(
            f"[Key Creation] Подписка создана для пользователя {tg_id} на сервере {selected_country}"
        )

        if old_key_name:
            update_data = {"server_id": selected_country}
            if panel_type == "3x-ui":
                update_data["key"] = public_link
            elif panel_type == "remnawave":
                update_data["remnawave_link"] = remnawave_link

            await session.execute(
                update(Key)
                .where(Key.tg_id == tg_id, Key.email == email)
                .values(**update_data)
            )
        else:
            data = {}
            if state:
                data = await state.get_data()

            tariff_id = data.get("tariff_id") or tariff_id

            new_key = Key(
                tg_id=tg_id,
                client_id=client_id,
                email=email,
                created_at=created_at,
                expiry_time=expiry_timestamp,
                key=public_link,
                remnawave_link=remnawave_link,
                server_id=selected_country,
                tariff_id=data.get("tariff_id"),
            )
            session.add(new_key)

            data = await state.get_data()
            if data.get("is_trial"):
                trial_status = await get_trial(session, tg_id)
                if trial_status in [0, -1]:
                    await update_trial(session, tg_id, 1)
            if data.get("tariff_id"):
                result = await session.execute(
                    select(Tariff.price_rub).where(Tariff.id == data["tariff_id"])
                )
                row = result.scalar_one_or_none()
                if row:
                    await update_balance(session, tg_id, -row)

        await session.commit()

    except Exception as e:
        logger.error(
            f"[Key Finalize] Ошибка при создании ключа для пользователя {tg_id}: {e}"
        )
        await callback_query.message.answer(
            "❌ Произошла ошибка при создании подписки. Попробуйте снова."
        )
        return

    builder = InlineKeyboardBuilder()
    is_full_remnawave = await is_full_remnawave_cluster(
        cluster_info["cluster_name"], session
    )
    if (panel_type == "remnawave" or is_full_remnawave) and (
        public_link or remnawave_link
    ):
        builder.row(
            InlineKeyboardButton(
                text=CONNECT_DEVICE,
                web_app=WebAppInfo(url=public_link or remnawave_link),
            )
        )
    elif CONNECT_PHONE_BUTTON:
        builder.row(
            InlineKeyboardButton(
                text=CONNECT_PHONE, callback_data=f"connect_phone|{key_name}"
            )
        )
        builder.row(
            InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"),
            InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"),
        )
    else:
        builder.row(
            InlineKeyboardButton(
                text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}"
            )
        )
    builder.row(
        InlineKeyboardButton(
            text="🔐 Моя подписка", callback_data=f"view_key|{key_name}"
        )
    )
    builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL))
    builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))

    remaining_time = expiry_time - datetime.now(moscow_tz)
    days = remaining_time.days
    link_to_show = public_link or remnawave_link or "Ссылка не найдена"
    key_message_text = key_message_success(link_to_show, f"⏳ Осталось дней: {days} 📅")

    await edit_or_send_message(
        target_message=callback_query.message,
        text=key_message_text,
        reply_markup=builder.as_markup(),
        media_path="img/pic.jpg",
    )

    if state:
        await state.clear()


async def check_server_availability(server_info: dict, session: AsyncSession) -> bool:
    server_name = server_info.get("server_name", "unknown")
    panel_type = server_info.get("panel_type", "3x-ui").lower()
    enabled = server_info.get("enabled", True)
    max_keys = server_info.get("max_keys")

    if not enabled:
        logger.info(f"[Ping] Сервер {server_name} выключен (enabled = FALSE).")
        return False

    try:
        if max_keys is not None:
            result = await session.execute(
                select(func.count())
                .select_from(Key)
                .where(Key.server_id == server_name)
            )
            key_count = result.scalar()

            if key_count >= max_keys:
                logger.info(
                    f"[Ping] Сервер {server_name} достиг лимита ключей: {key_count}/{max_keys}."
                )
                return False

    except SQLAlchemyError as e:
        logger.warning(
            f"[Ping] Ошибка при проверке лимита ключей на сервере {server_name}: {e}"
        )
        return False

    try:
        if panel_type == "remnawave":
            remna = RemnawaveAPI(server_info["api_url"])
            await asyncio.wait_for(
                remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD), timeout=5.0
            )
            logger.info(f"[Ping] Remnawave сервер {server_name} доступен.")
            return True

        else:
            xui = AsyncApi(
                server_info["api_url"],
                username=ADMIN_USERNAME,
                password=ADMIN_PASSWORD,
                logger=logger,
            )
            await asyncio.wait_for(xui.login(), timeout=5.0)
            logger.info(f"[Ping] 3x-ui сервер {server_name} доступен.")
            return True

    except TimeoutError:
        logger.warning(f"[Ping] Сервер {server_name} не ответил вовремя.")
        return False
    except Exception as e:
        logger.warning(f"[Ping] Ошибка при проверке сервера {server_name}: {e}")
        return False
