# handlers/finance_handlers.py

from __future__ import annotations

import asyncio
import inspect
import logging
import re
from decimal import Decimal, InvalidOperation
from typing import Any, Final, Optional

from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, Message

from core.config import settings
from core.ui_emojis import ce, premiumize_html

from keyboards.admin_kb import (
    get_admin_cancel_keyboard,
    get_admin_pricing_keyboard,
)

from utils.finance import finance_manager


logger = logging.getLogger(__name__)

router = Router(
    name="finance_handlers"
)


# ============================================================
# FSM
# ============================================================

class FinanceStates(StatesGroup):
    """
    Stateهای مخصوص مدیریت نرخ‌ها.

    از State واقعی aiogram استفاده می‌کنیم تا Handler تغییر نرخ
    روی تمام پیام‌های متنی ربات اجرا نشود.
    """

    waiting_for_new_rate = State()


# ============================================================
# Configuration
# ============================================================

CURRENCY_MAP: Final[dict[str, dict[str, str]]] = {
    "set_rate_usdt": {
        "key": "usdt",
        "name": "تتر (USDT)",
        "unit": "تومان",
        "emoji": "money",
    },
    "set_rate_ton": {
        "key": "ton",
        "name": "تون (TON)",
        "unit": "تومان",
        "emoji": "ton",
    },
    "set_rate_stars": {
        "key": "stars",
        "name": "استارز (Stars)",
        "unit": "تومان",
        "emoji": "stars",
    },
    "set_rate_premium": {
        "key": "premium_monthly",
        "name": "پرمیوم ماهانه",
        "unit": "تومان",
        "emoji": "premium",
    },
}

_ALLOWED_RATE_KEYS: Final[frozenset[str]] = frozenset(
    item["key"]
    for item in CURRENCY_MAP.values()
)

_RATE_UPDATE_LOCK = asyncio.Lock()


# ============================================================
# Admin Access
# ============================================================

def get_admin_ids() -> frozenset[int]:
    """
    منبع دسترسی ادمین از config پروژه.
    """
    method = getattr(
        settings,
        "get_admin_list",
        None,
    )

    if callable(method):
        try:
            return frozenset(
                int(user_id)
                for user_id in method()
                if int(user_id) > 0
            )

        except Exception:
            logger.exception(
                "Could not parse ADMIN_IDS "
                "using settings.get_admin_list()."
            )

    raw = str(
        getattr(
            settings,
            "ADMIN_IDS",
            "",
        )
        or ""
    )

    result: set[int] = set()

    for item in raw.split(","):
        item = item.strip()

        if not item:
            continue

        try:
            user_id = int(item)

            if user_id > 0:
                result.add(user_id)

        except ValueError:
            logger.warning(
                "Invalid ADMIN_IDS entry ignored: %r",
                item,
            )

    return frozenset(result)


async def verify_admin_access(
    user_id: int | None,
) -> bool:
    try:
        user_id = int(user_id or 0)

    except (
        TypeError,
        ValueError,
    ):
        return False

    return (
        user_id > 0
        and user_id in get_admin_ids()
    )


async def require_admin_callback(
    callback: CallbackQuery,
) -> bool:
    user = callback.from_user

    if (
        not user
        or not await verify_admin_access(user.id)
    ):
        logger.warning(
            "Unauthorized finance callback | "
            "user_id=%s callback=%s",
            (
                user.id
                if user
                else None
            ),
            callback.data,
        )

        await safe_callback_answer(
            callback,
            "⛔ دسترسی مدیریت ندارید.",
            show_alert=True,
        )

        return False

    return True


async def require_admin_message(
    message: Message,
) -> bool:
    user = message.from_user

    if (
        not user
        or not await verify_admin_access(user.id)
    ):
        logger.warning(
            "Unauthorized finance message | user_id=%s",
            (
                user.id
                if user
                else None
            ),
        )

        return False

    return True


# ============================================================
# Finance Manager Compatibility
# ============================================================

async def _call_manager_method(
    method_name: str,
    *args,
    **kwargs,
) -> Any:
    """
    پشتیبانی همزمان از finance_managerهای sync و async.
    """
    method = getattr(
        finance_manager,
        method_name,
        None,
    )

    if not callable(method):
        raise AttributeError(
            f"finance_manager.{method_name} "
            "is not implemented."
        )

    if inspect.iscoroutinefunction(method):
        return await method(
            *args,
            **kwargs,
        )

    result = await asyncio.to_thread(
        method,
        *args,
        **kwargs,
    )

    # برای wrapperهایی که sync تعریف شده‌اند ولی awaitable برمی‌گردانند.
    if inspect.isawaitable(result):
        return await result

    return result


async def _get_rate(
    currency_key: str,
) -> Optional[Decimal]:
    """
    دریافت نرخ فعلی با Decimal.
    """
    if currency_key not in _ALLOWED_RATE_KEYS:
        return None

    try:
        value = await _call_manager_method(
            "get_rate",
            currency_key,
        )

        if value is None:
            return None

        result = Decimal(
            str(value)
        )

        if (
            not result.is_finite()
            or result <= 0
        ):
            return None

        return result

    except (
        InvalidOperation,
        ValueError,
        TypeError,
    ):
        logger.exception(
            "Invalid finance rate returned | key=%s",
            currency_key,
        )
        return None

    except Exception:
        logger.exception(
            "Could not read finance rate | key=%s",
            currency_key,
        )
        return None


async def _update_rate(
    currency_key: str,
    new_rate: Decimal,
) -> bool:
    """
    ثبت نرخ و در صورت امکان read-back برای اطمینان.
    """
    if currency_key not in _ALLOWED_RATE_KEYS:
        return False

    result = await _call_manager_method(
        "update_rate",
        currency_key,
        new_rate,
    )

    # بعضی پیاده‌سازی‌ها None برمی‌گردانند ولی تغییر را انجام می‌دهند.
    if result is False:
        return False

    current = await _get_rate(
        currency_key
    )

    if current is None:
        # اگر manager امکان read-back نداشته باشد،
        # نتیجه صریح False نبودن را موفق در نظر می‌گیریم.
        return result is not False

    return current == new_rate


# ============================================================
# Formatting
# ============================================================

def _format_decimal(
    value: Decimal | None,
) -> str:
    if value is None:
        return "نامشخص"

    try:
        value = Decimal(
            str(value)
        )

    except (
        InvalidOperation,
        ValueError,
        TypeError,
    ):
        return "نامشخص"

    if value == value.to_integral():
        return f"{int(value):,}"

    text = format(
        value.normalize(),
        "f",
    )

    if "." in text:
        integer,
        fraction = text.split(
            ".",
            1,
        )

        try:
            integer = f"{int(integer):,}"
        except ValueError:
            pass

        fraction = fraction.rstrip("0")

        if fraction:
            return (
                f"{integer}."
                f"{fraction}"
            )

        return integer

    return text


async def _build_pricing_text() -> str:
    """
    متن Premium Emoji منوی مدیریت نرخ‌ها.
    """
    usdt, ton, stars, premium = (
        await asyncio.gather(
            _get_rate("usdt"),
            _get_rate("ton"),
            _get_rate("stars"),
            _get_rate("premium_monthly"),
        )
    )

    return (
        f"{ce('sparkles')} "
        "<b>مدیریت نرخ ارز و قیمت خدمات</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"{ce('money')} <b>USDT</b>\n"
        f"└ نرخ فعلی: "
        f"<code>{_format_decimal(usdt)}</code> تومان\n\n"

        f"{ce('ton')} <b>TON</b>\n"
        f"└ نرخ فعلی: "
        f"<code>{_format_decimal(ton)}</code> تومان\n\n"

        f"{ce('stars')} <b>Telegram Stars</b>\n"
        f"└ نرخ ثبت‌شده: "
        f"<code>{_format_decimal(stars)}</code> تومان\n\n"

        f"{ce('premium')} <b>Telegram Premium</b>\n"
        f"└ نرخ ماهانه ثبت‌شده: "
        f"<code>{_format_decimal(premium)}</code> تومان\n\n"

        "━━━━━━━━━━━━━━━━━━━━\n"
        f"{ce('warning')} "
        "<b>قیمت نهایی Premium و Stars هنگام خرید "
        "دوباره از MarketApp بررسی می‌شود.</b>\n\n"

        f"{ce('target')} "
        "<b>نرخ موردنظر را انتخاب کنید:</b>"
    )


# ============================================================
# Telegram Helpers
# ============================================================

async def safe_callback_answer(
    callback: CallbackQuery,
    text: str | None = None,
    *,
    show_alert: bool = False,
) -> bool:
    try:
        await callback.answer(
            text=text,
            show_alert=show_alert,
        )
        return True

    except TelegramBadRequest:
        return False

    except Exception:
        logger.exception(
            "Could not answer finance callback."
        )
        return False


async def _safe_edit_message(
    callback: CallbackQuery,
    text: str,
    reply_markup=None,
) -> bool:
    """
    Edit امن + Premium Emoji.
    """
    if not callback.message:
        return False

    try:
        await callback.message.edit_text(
            text=premiumize_html(
                text
            ),
            reply_markup=reply_markup,
            parse_mode="HTML",
        )
        return True

    except TelegramBadRequest as exc:
        error_text = str(
            exc
        ).lower()

        if (
            "message is not modified"
            in error_text
        ):
            return True

        if (
            "message to edit not found"
            in error_text
            or "message can't be edited"
            in error_text
        ):
            try:
                await callback.message.answer(
                    premiumize_html(
                        text
                    ),
                    reply_markup=reply_markup,
                    parse_mode="HTML",
                )
                return True

            except Exception:
                logger.exception(
                    "Could not send replacement finance message."
                )
                return False

        logger.warning(
            "TelegramBadRequest while editing "
            "finance message: %s",
            exc,
        )
        return False

    except Exception:
        logger.exception(
            "Unexpected error while editing finance message."
        )
        return False


async def _send_html(
    message: Message,
    text: str,
    *,
    reply_markup=None,
) -> None:
    await message.answer(
        premiumize_html(
            text
        ),
        reply_markup=reply_markup,
        parse_mode="HTML",
    )


# ============================================================
# Input Parsing
# ============================================================

_NUMBER_TRANSLATION = str.maketrans(
    "۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩",
    "01234567890123456789",
)


def _parse_decimal_input(
    raw_text: str,
) -> Optional[Decimal]:
    """
    نمونه‌های معتبر:
        62000
        62,000
        ۶۲٬۰۰۰
        62000.5
        62000 تومان

    ورودی scientific notation عمداً رد می‌شود.
    """
    if not raw_text:
        return None

    cleaned = (
        raw_text
        .strip()
        .translate(
            _NUMBER_TRANSLATION
        )
    )

    suffixes = (
        "تومان",
        "تومن",
        "ریال",
        "IRT",
        "IRR",
    )

    # واحد فقط از انتهای متن حذف می‌شود.
    for suffix in suffixes:
        if cleaned.lower().endswith(
            suffix.lower()
        ):
            cleaned = cleaned[
                :-len(suffix)
            ].strip()
            break

    cleaned = (
        cleaned
        .replace(",", "")
        .replace("،", "")
        .replace("٬", "")
        .replace("_", "")
        .replace(" ", "")
        .replace("٫", ".")
    )

    if not cleaned:
        return None

    # فقط عدد معمولی؛ e/E و کاراکترهای اضافی مجاز نیست.
    if not re.fullmatch(
        r"\+?\d+(?:\.\d+)?",
        cleaned,
    ):
        return None

    try:
        value = Decimal(
            cleaned.lstrip("+")
        )

    except (
        InvalidOperation,
        ValueError,
        TypeError,
    ):
        return None

    if (
        not value.is_finite()
        or value <= 0
    ):
        return None

    return value


def _validate_rate(
    currency_key: str,
    value: Decimal,
) -> tuple[bool, Optional[str]]:
    if currency_key not in _ALLOWED_RATE_KEYS:
        return (
            False,
            "نوع نرخ انتخاب‌شده معتبر نیست.",
        )

    if (
        not value.is_finite()
        or value <= 0
    ):
        return (
            False,
            "مقدار باید بیشتر از صفر باشد.",
        )

    if value > Decimal(
        "999999999999999999"
    ):
        return (
            False,
            "مقدار واردشده بیش از حد بزرگ است.",
        )

    # حداکثر 4 رقم اعشار برای نرخ‌های پنل.
    if value.as_tuple().exponent < -4:
        return (
            False,
            "حداکثر ۴ رقم اعشار مجاز است.",
        )

    return (
        True,
        None,
    )


# ============================================================
# Admin Pricing Menu
# ============================================================

@router.callback_query(
    F.data == "admin_pricing"
)
async def cmd_admin_pricing(
    callback: CallbackQuery,
    state: FSMContext,
):
    if not await require_admin_callback(
        callback
    ):
        return

    try:
        # بعد از احراز دسترسی State پاک می‌شود.
        await state.clear()

        text = await _build_pricing_text()

        await _safe_edit_message(
            callback=callback,
            text=text,
            reply_markup=(
                get_admin_pricing_keyboard()
            ),
        )

        await safe_callback_answer(
            callback
        )

    except Exception:
        logger.exception(
            "Error in admin pricing menu handler."
        )

        await safe_callback_answer(
            callback,
            "❌ خطایی در بارگذاری منوی قیمت‌ها رخ داد.",
            show_alert=True,
        )


# ============================================================
# Set Rate - Prompt
# ============================================================

@router.callback_query(
    F.data.in_(
        set(CURRENCY_MAP.keys())
    )
)
async def prompt_set_rate(
    callback: CallbackQuery,
    state: FSMContext,
):
    if not await require_admin_callback(
        callback
    ):
        return

    try:
        config = CURRENCY_MAP.get(
            str(callback.data)
        )

        if not config:
            await safe_callback_answer(
                callback,
                "❌ گزینه انتخاب‌شده معتبر نیست.",
                show_alert=True,
            )
            return

        currency_key = config[
            "key"
        ]
        currency_name = config[
            "name"
        ]
        currency_emoji = config[
            "emoji"
        ]
        unit = config[
            "unit"
        ]

        current_value = await _get_rate(
            currency_key
        )

        await state.set_state(
            FinanceStates.waiting_for_new_rate
        )

        await state.update_data(
            target_currency=currency_key,
            target_name=currency_name,
            target_emoji=currency_emoji,
            target_unit=unit,
        )

        prompt_text = (
            f"{ce(currency_emoji)} "
            f"<b>تغییر نرخ {currency_name}</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"{ce('pin')} <b>نرخ فعلی:</b>\n"
            f"<code>{_format_decimal(current_value)}</code> "
            f"{unit}\n\n"

            f"{ce('chat')} "
            "<b>نرخ جدید را ارسال کنید.</b>\n\n"

            "نمونه:\n"
            "<code>62000</code>\n"
            "<code>62,000</code>\n"
            "<code>۶۲٬۰۰۰</code>\n"
            "<code>62000.50</code>\n\n"

            f"{ce('warning')} "
            "مقدار باید بیشتر از صفر باشد."
        )

        await _safe_edit_message(
            callback=callback,
            text=prompt_text,
            reply_markup=(
                get_admin_cancel_keyboard(
                    "admin_pricing"
                )
            ),
        )

        await safe_callback_answer(
            callback
        )

    except Exception:
        logger.exception(
            "Error in prompt_set_rate."
        )

        await state.clear()

        await safe_callback_answer(
            callback,
            "❌ خطایی در باز کردن فرم تغییر نرخ رخ داد.",
            show_alert=True,
        )


# ============================================================
# Legacy Cancel Compatibility
# ============================================================

@router.callback_query(
    F.data == "admin_confirm_yes_cancel_rate_update"
)
async def cancel_rate_update(
    callback: CallbackQuery,
    state: FSMContext,
):
    """
    سازگاری با دکمه‌های نسخه قدیمی.
    دکمه جدید مستقیماً callback_data=admin_pricing دارد.
    """
    if not await require_admin_callback(
        callback
    ):
        return

    try:
        await state.clear()

        text = await _build_pricing_text()

        await _safe_edit_message(
            callback=callback,
            text=text,
            reply_markup=(
                get_admin_pricing_keyboard()
            ),
        )

        await safe_callback_answer(
            callback,
            "عملیات تغییر نرخ لغو شد.",
        )

    except Exception:
        logger.exception(
            "Error cancelling rate update."
        )

        await safe_callback_answer(
            callback,
            "❌ خطا در لغو عملیات.",
            show_alert=True,
        )


# ============================================================
# Process New Rate
# ============================================================

@router.message(
    FinanceStates.waiting_for_new_rate,
    F.text,
)
async def process_new_rate(
    message: Message,
    state: FSMContext,
):
    """
    فقط زمانی اجرا می‌شود که ادمین داخل State تغییر نرخ باشد.
    """
    if not await require_admin_message(
        message
    ):
        await state.clear()
        return

    try:
        data = await state.get_data()

        currency_key = str(
            data.get(
                "target_currency"
            )
            or ""
        )

        currency_name = str(
            data.get(
                "target_name"
            )
            or ""
        )

        currency_emoji = str(
            data.get(
                "target_emoji"
            )
            or "money"
        )

        currency_unit = str(
            data.get(
                "target_unit"
            )
            or "تومان"
        )

        if (
            currency_key
            not in _ALLOWED_RATE_KEYS
            or not currency_name
        ):
            await state.clear()

            await _send_html(
                message,
                (
                    f"{ce('warning')} "
                    "<b>نشست تغییر نرخ منقضی شده است.</b>\n\n"
                    "دوباره از پنل مدیریت وارد بخش قیمت‌ها شوید."
                ),
                reply_markup=(
                    get_admin_pricing_keyboard()
                ),
            )
            return

        new_rate = _parse_decimal_input(
            message.text or ""
        )

        if new_rate is None:
            await _send_html(
                message,
                (
                    f"{ce('error')} "
                    "<b>مقدار واردشده معتبر نیست.</b>\n\n"
                    "فقط عدد بزرگ‌تر از صفر ارسال کنید.\n\n"
                    "مثال:\n"
                    "<code>62000</code>\n"
                    "<code>62,000</code>\n"
                    "<code>۶۲٬۰۰۰</code>"
                ),
                reply_markup=(
                    get_admin_cancel_keyboard(
                        "admin_pricing"
                    )
                ),
            )
            return

        valid, error_message = (
            _validate_rate(
                currency_key,
                new_rate,
            )
        )

        if not valid:
            await _send_html(
                message,
                (
                    f"{ce('error')} "
                    "<b>مقدار نامعتبر است.</b>\n\n"
                    f"{error_message or 'خطای اعتبارسنجی.'}"
                ),
                reply_markup=(
                    get_admin_cancel_keyboard(
                        "admin_pricing"
                    )
                ),
            )
            return

        async with _RATE_UPDATE_LOCK:
            old_rate = await _get_rate(
                currency_key
            )

            success = await _update_rate(
                currency_key,
                new_rate,
            )

            if not success:
                await _send_html(
                    message,
                    (
                        f"{ce('error')} "
                        "<b>ثبت نرخ انجام نشد.</b>\n\n"
                        "finance_manager مقدار جدید را "
                        "تایید نکرد."
                    ),
                    reply_markup=(
                        get_admin_cancel_keyboard(
                            "admin_pricing"
                        )
                    ),
                )
                return

            # Read-back نهایی برای لاگ و UI.
            stored_rate = await _get_rate(
                currency_key
            )

        await state.clear()

        final_rate = (
            stored_rate
            if stored_rate is not None
            else new_rate
        )

        success_text = (
            f"{ce('success')} "
            "<b>نرخ با موفقیت تغییر کرد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"{ce(currency_emoji)} "
            f"<b>{currency_name}</b>\n\n"

            f"{ce('pin')} نرخ قبلی:\n"
            f"<code>{_format_decimal(old_rate)}</code> "
            f"{currency_unit}\n\n"

            f"{ce('bolt')} نرخ جدید:\n"
            f"<code>{_format_decimal(final_rate)}</code> "
            f"{currency_unit}\n\n"

            "━━━━━━━━━━━━━━━━━━━━\n"
            f"{ce('success')} "
            "تغییر در finance_manager ثبت شد."
        )

        await _send_html(
            message,
            success_text,
            reply_markup=(
                get_admin_pricing_keyboard()
            ),
        )

        logger.info(
            "Finance rate updated | "
            "admin=%s key=%s old=%s new=%s",
            (
                message.from_user.id
                if message.from_user
                else None
            ),
            currency_key,
            old_rate,
            final_rate,
        )

    except Exception:
        logger.exception(
            "Unexpected error processing new finance rate."
        )

        await state.clear()

        await _send_html(
            message,
            (
                f"{ce('error')} "
                "<b>خطای غیرمنتظره‌ای رخ داد.</b>\n\n"
                "عملیات تغییر نرخ انجام نشد."
            ),
            reply_markup=(
                get_admin_pricing_keyboard()
            ),
        )


# ============================================================
# Automatic Rate Synchronization
# ============================================================

@router.callback_query(
    F.data == "auto_sync_rates"
)
async def process_auto_sync_rates(
    callback: CallbackQuery,
    state: FSMContext,
):
    if not await require_admin_callback(
        callback
    ):
        return

    try:
        await state.clear()

        await safe_callback_answer(
            callback,
            "در حال بررسی نرخ‌ها..."
        )

        sync_method = getattr(
            finance_manager,
            "sync_rates",
            None,
        )

        sync_succeeded: Optional[
            bool
        ] = None

        sync_message = ""

        if callable(sync_method):
            try:
                result = await _call_manager_method(
                    "sync_rates"
                )

                sync_succeeded = (
                    result is not False
                )

                if sync_succeeded:
                    sync_message = (
                        f"{ce('success')} "
                        "همگام‌سازی خارجی با موفقیت انجام شد."
                    )
                else:
                    sync_message = (
                        f"{ce('warning')} "
                        "سرویس همگام‌سازی نتیجه موفق برنگرداند."
                    )

            except Exception:
                sync_succeeded = False

                logger.exception(
                    "External finance rate synchronization failed."
                )

                sync_message = (
                    f"{ce('warning')} "
                    "همگام‌سازی خارجی انجام نشد؛ "
                    "نرخ‌های فعلی حفظ شدند."
                )

        else:
            sync_message = (
                f"{ce('warning')} "
                "finance_manager متد sync_rates ندارد؛ "
                "فقط نرخ‌های فعلی نمایش داده می‌شوند."
            )

        text = await _build_pricing_text()

        text += (
            "\n\n━━━━━━━━━━━━━━━━━━━━\n"
            f"{ce('history')} "
            "<b>وضعیت بروزرسانی:</b>\n"
            f"{sync_message}"
        )

        await _safe_edit_message(
            callback=callback,
            text=text,
            reply_markup=(
                get_admin_pricing_keyboard()
            ),
        )

        if sync_succeeded is True:
            await safe_callback_answer(
                callback,
                "✅ نرخ‌ها بروزرسانی شدند.",
            )

    except Exception:
        logger.exception(
            "Error in auto sync rates handler."
        )

        await safe_callback_answer(
            callback,
            "❌ خطا در همگام‌سازی نرخ‌ها.",
            show_alert=True,
        )


# ============================================================
# Refresh
# ============================================================

@router.callback_query(
    F.data == "admin_pricing_refresh"
)
async def refresh_admin_pricing(
    callback: CallbackQuery,
    state: FSMContext,
):
    if not await require_admin_callback(
        callback
    ):
        return

    await cmd_admin_pricing(
        callback,
        state,
    )


# ============================================================
# Invalid set_rate Callback Protection
# ============================================================

@router.callback_query(
    F.data.startswith(
        "set_rate_"
    )
)
async def invalid_rate_callback(
    callback: CallbackQuery,
):
    if not await require_admin_callback(
        callback
    ):
        return

    if callback.data in CURRENCY_MAP:
        return

    logger.warning(
        "Unknown finance rate callback | "
        "admin=%s callback=%s",
        (
            callback.from_user.id
            if callback.from_user
            else None
        ),
        callback.data,
    )

    await safe_callback_answer(
        callback,
        "❌ گزینه نرخ معتبر نیست.",
        show_alert=True,
    )


# ============================================================
# Export
# ============================================================

__all__ = [
    "router",
    "FinanceStates",
    "cmd_admin_pricing",
    "prompt_set_rate",
    "cancel_rate_update",
    "process_new_rate",
    "process_auto_sync_rates",
    "refresh_admin_pricing",
]