from __future__ import annotations

import asyncio
import logging
import sqlite3
from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal, ROUND_HALF_UP
from html import escape
from pathlib import Path
from typing import Any, Optional
from zoneinfo import ZoneInfo

from aiogram import F, Router
from aiogram.enums import ParseMode
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, FSInputFile, InlineKeyboardButton, InlineKeyboardMarkup, Message, User

from core.config import settings
from core.database import DatabaseManager
from keyboards.user_kb import (
    get_crypto_deposit_cancel_keyboard,
    get_crypto_deposit_currency_keyboard,
    get_crypto_deposit_destination_keyboard,
    get_crypto_note_keyboard,
    get_crypto_receipt_keyboard,
    get_wallet_menu_keyboard,
)
from services.crypto_deposit_service import (
    APPROVED,
    PENDING,
    REJECTED,
    SUPPORTED,
    TON,
    TRX,
    USDT,
    AlreadyHandledError,
    CryptoDepositService,
    DepositRequest,
    DepositValidationError,
    DuplicateTxError,
    normalize_currency,
    normalize_tx_hash,
    parse_amount,
    validate_address,
)
from services.fast_crypto_rates import FastRateUnavailable, fast_crypto_rates

logger = logging.getLogger(__name__)
router = Router(name="user_wallet")
db = DatabaseManager()
TEHRAN = ZoneInfo("Asia/Tehran")


class CryptoDepositStates(StatesGroup):
    amount = State()
    from_wallet = State()
    tx_hash = State()
    paid_at = State()
    receipt = State()
    note = State()


class OwnerCryptoStates(StatesGroup):
    wallet_address = State()
    reject_reason = State()


def now_tehran() -> str:
    return datetime.now(TEHRAN).strftime("%Y/%m/%d - %H:%M:%S")


def db_path() -> str:
    for name in ("db_path", "database_path", "path"):
        value = getattr(db, name, None)
        if value:
            return str(value)
    return str(getattr(settings, "DB_PATH", None) or getattr(settings, "DATABASE_PATH", None) or "matrix_bot.db")


def service() -> CryptoDepositService:
    return CryptoDepositService(db_path())


def admin_ids() -> frozenset[int]:
    fn = getattr(settings, "get_admin_list", None)
    if callable(fn):
        try:
            return frozenset(int(x) for x in fn() if int(x) > 0)
        except Exception:
            logger.exception("Could not read admin list")
    result: set[int] = set()
    for item in str(getattr(settings, "ADMIN_IDS", "") or "").split(","):
        try:
            value = int(item.strip())
            if value > 0:
                result.add(value)
        except Exception:
            pass
    return frozenset(result)


def is_admin(user_id: int) -> bool:
    return int(user_id) in admin_ids()


PROFILE_EMOJI = {
    "wallet": "5803143039060808890",
    "wallets": "6008196202384859008",
    "plus": "5318974936310627698",
    "orders": "5985774024968379294",
    "folder": "5244837092042750681",
    "support": "5319164022245832659",
    "ton": "6008196202384859008",
    "trx": "6032713293049633080",
    "users": "5875180111744995604",
    "back": "5447183459602669338",
    "flash": "5449683594425410231",
}


def btn(
    text: str,
    data: str,
    style: str = "primary",
    *,
    icon: Optional[str] = None,
) -> InlineKeyboardButton:
    """
    ساخت دکمه با Custom Emoji پرمیومی در نسخه‌های جدید Bot API.

    اگر aiogram نصب‌شده فیلد icon_custom_emoji_id یا style را
    پشتیبانی نکند، دکمه بدون آن فیلد ساخته می‌شود تا خطا ندهد.
    """

    kwargs: dict[str, Any] = {
        "text": text,
        "callback_data": data,
    }

    model_fields = getattr(
        InlineKeyboardButton,
        "model_fields",
        None,
    )

    if not model_fields:
        model_fields = getattr(
            InlineKeyboardButton,
            "__fields__",
            {},
        )

    supported = set(
        getattr(
            model_fields,
            "keys",
            lambda: [],
        )()
    )

    if (
        style
        and "style" in supported
    ):
        kwargs["style"] = style

    if (
        icon
        and "icon_custom_emoji_id"
        in supported
    ):
        kwargs["icon_custom_emoji_id"] = PROFILE_EMOJI.get(
            icon,
            icon,
        )

    return InlineKeyboardButton(
        **kwargs
    )



async def answer_cb(callback: CallbackQuery, text: Optional[str] = None, alert: bool = False) -> None:
    try:
        await callback.answer(text=text, show_alert=alert)
    except Exception:
        pass


async def edit_cb(callback: CallbackQuery, text: str, keyboard: Optional[InlineKeyboardMarkup] = None) -> None:
    if not callback.message:
        return
    try:
        if callback.message.photo or callback.message.document:
            await callback.message.edit_caption(caption=text, reply_markup=keyboard, parse_mode=ParseMode.HTML)
        else:
            await callback.message.edit_text(text, reply_markup=keyboard, parse_mode=ParseMode.HTML)
        return
    except TelegramBadRequest as exc:
        if "message is not modified" in str(exc).lower():
            return
    except Exception:
        logger.debug("Edit failed", exc_info=True)
    await callback.message.answer(text, reply_markup=keyboard, parse_mode=ParseMode.HTML)


# ------------------------------------------------------------------
# Profile / wallet dashboard
# ------------------------------------------------------------------

BASE_DIR = Path(__file__).resolve().parents[2]
PROFILE_PHOTO_PATH = BASE_DIR / "assets" / "profile_card.jpg"


@dataclass(slots=True)
class UserProfileSnapshot:
    user_id: int

    username: Optional[str] = None
    full_name: Optional[str] = None

    balance_toman: int = 0
    balance_ton: float = 0.0
    balance_usdt: float = 0.0

    user_tier: str = "standard"
    is_admin: bool = False
    is_banned: bool = False

    kyc_status: str = "none"

    total_orders: int = 0
    completed_orders: int = 0
    pending_orders: int = 0

    referral_count: int = 0
    joined_date: str = "نامشخص"

    deposit_total: int = 0
    deposit_pending: int = 0
    deposit_approved: int = 0
    deposit_rejected: int = 0
    deposit_credited_toman: int = 0


PROFILE_TEXT_EMOJI = {
    "panel": ("5987803826512466184", "⚡"),
    "user": ("5431577498364158238", "👤"),
    "chat": ("5987803826512466184", "💬"),
    "level": ("5316809461044623413", "🎖"),
    "shield": ("6030430277413638534", "🛡"),
    "history": ("5985774024968379294", "📊"),
    "money": ("5951861774947979620", "💰"),
    "wallet": ("5803143039060808890", "👝"),
    "ton": ("6008196202384859008", "💎"),
    "trx": ("6032713293049633080", "🪙"),
    "pin": ("5985774024968379294", "📌"),
    "warning": ("6030430277413638534", "⚠️"),
    "success": ("5951861774947979620", "✅"),
    "loading": ("5987803826512466184", "⏳"),
    "error": ("6030430277413638534", "❌"),
    "orders": ("5990147899403539264", "📦"),
    "calendar": ("5985774024968379294", "🗓"),
    "referral": ("5875180111744995604", "👥"),
    "crypto": ("6008196202384859008", "💱"),
}


def ce(
    name: str,
    fallback: Optional[str] = None,
) -> str:
    """
    Premium Custom Emoji برای متن HTML.
    """

    emoji_id, default_fallback = PROFILE_TEXT_EMOJI[name]

    return (
        f'<tg-emoji emoji-id="{emoji_id}">'
        f"{fallback or default_fallback}"
        "</tg-emoji>"
    )


def _table_exists(
    conn: sqlite3.Connection,
    table: str,
) -> bool:
    row = conn.execute(
        """
        SELECT 1
        FROM sqlite_master
        WHERE type = 'table'
          AND name = ?
        LIMIT 1
        """,
        (table,),
    ).fetchone()

    return row is not None


def _columns(
    conn: sqlite3.Connection,
    table: str,
) -> set[str]:
    if not _table_exists(
        conn,
        table,
    ):
        return set()

    return {
        str(row["name"])
        for row in conn.execute(
            f"PRAGMA table_info({table})"
        ).fetchall()
    }


def _safe_int(
    value: Any,
    default: int = 0,
) -> int:
    try:
        return int(
            value
            if value is not None
            else default
        )
    except (
        TypeError,
        ValueError,
    ):
        return int(default)


def _safe_float(
    value: Any,
    default: float = 0.0,
) -> float:
    try:
        return float(
            value
            if value is not None
            else default
        )
    except (
        TypeError,
        ValueError,
    ):
        return float(default)


def _normalize_kyc_status(
    value: Any,
) -> str:
    raw = str(
        value
        or ""
    ).strip().lower()

    if raw in {
        "approved",
        "verified",
        "verify",
        "done",
        "1",
        "true",
    }:
        return "approved"

    if raw in {
        "pending",
        "review",
        "waiting",
        "under_review",
    }:
        return "pending"

    if raw in {
        "rejected",
        "reject",
        "failed",
    }:
        return "rejected"

    return "none"


def _profile_sync(
    path: str,
    user_id: int,
) -> UserProfileSnapshot:
    """
    پروفایل کامل و مقاوم در برابر تفاوت Schema نسخه‌های مختلف پروژه.
    """

    profile = UserProfileSnapshot(
        user_id=int(user_id),
        is_admin=is_admin(
            int(user_id)
        ),
    )

    conn = sqlite3.connect(
        path,
        timeout=20,
        check_same_thread=False,
    )
    conn.row_factory = sqlite3.Row

    conn.execute(
        "PRAGMA busy_timeout = 20000"
    )

    try:
        # ====================================================
        # USERS
        # ====================================================

        user_cols = _columns(
            conn,
            "users",
        )

        user_key = (
            "user_id"
            if "user_id" in user_cols
            else "id"
            if "id" in user_cols
            else None
        )

        if user_key:
            selectable = [
                column
                for column in (
                    "username",
                    "full_name",
                    "first_name",

                    "balance_toman",
                    "balance",
                    "balance_ton",
                    "balance_usdt",

                    "user_tier",
                    "tier",
                    "role",

                    "is_admin",
                    "is_banned",
                    "banned",

                    "kyc_status",
                    "is_verified",
                    "verified",

                    "created_at",
                    "joined_at",
                    "registration_date",

                    "referrer_id",
                )
                if column in user_cols
            ]

            if selectable:
                row = conn.execute(
                    f"""
                    SELECT
                        {", ".join(selectable)}
                    FROM users
                    WHERE {user_key} = ?
                    LIMIT 1
                    """,
                    (
                        int(user_id),
                    ),
                ).fetchone()

                if row:
                    if "username" in user_cols:
                        profile.username = (
                            str(row["username"])
                            if row["username"]
                            else None
                        )

                    if "full_name" in user_cols:
                        profile.full_name = (
                            str(row["full_name"])
                            if row["full_name"]
                            else None
                        )
                    elif "first_name" in user_cols:
                        profile.full_name = (
                            str(row["first_name"])
                            if row["first_name"]
                            else None
                        )

                    if "balance_toman" in user_cols:
                        profile.balance_toman = _safe_int(
                            row["balance_toman"]
                        )
                    elif "balance" in user_cols:
                        profile.balance_toman = _safe_int(
                            row["balance"]
                        )

                    if "balance_ton" in user_cols:
                        profile.balance_ton = _safe_float(
                            row["balance_ton"]
                        )

                    if "balance_usdt" in user_cols:
                        profile.balance_usdt = _safe_float(
                            row["balance_usdt"]
                        )

                    for tier_col in (
                        "user_tier",
                        "tier",
                        "role",
                    ):
                        if tier_col in user_cols and row[tier_col]:
                            profile.user_tier = str(
                                row[tier_col]
                            )
                            break

                    if "is_admin" in user_cols:
                        profile.is_admin = (
                            profile.is_admin
                            or bool(
                                _safe_int(
                                    row["is_admin"]
                                )
                            )
                        )

                    banned_value = None

                    if "is_banned" in user_cols:
                        banned_value = row["is_banned"]
                    elif "banned" in user_cols:
                        banned_value = row["banned"]

                    if banned_value is not None:
                        profile.is_banned = bool(
                            _safe_int(
                                banned_value
                            )
                        )

                    if "kyc_status" in user_cols:
                        profile.kyc_status = (
                            _normalize_kyc_status(
                                row["kyc_status"]
                            )
                        )

                    elif "is_verified" in user_cols:
                        profile.kyc_status = (
                            "approved"
                            if bool(
                                _safe_int(
                                    row["is_verified"]
                                )
                            )
                            else "none"
                        )

                    elif "verified" in user_cols:
                        profile.kyc_status = (
                            "approved"
                            if bool(
                                _safe_int(
                                    row["verified"]
                                )
                            )
                            else "none"
                        )

                    for date_col in (
                        "created_at",
                        "joined_at",
                        "registration_date",
                    ):
                        if date_col in user_cols and row[date_col]:
                            profile.joined_date = str(
                                row[date_col]
                            )
                            break

        # ====================================================
        # KYC TABLES
        # ====================================================

        for table_name in (
            "kyc_requests",
            "kyc_verifications",
            "user_kyc",
        ):
            cols = _columns(
                conn,
                table_name,
            )

            if not cols:
                continue

            key = (
                "user_id"
                if "user_id" in cols
                else "telegram_id"
                if "telegram_id" in cols
                else None
            )

            status_col = (
                "status"
                if "status" in cols
                else "kyc_status"
                if "kyc_status" in cols
                else None
            )

            if not key or not status_col:
                continue

            order_col = (
                "id"
                if "id" in cols
                else "created_at"
                if "created_at" in cols
                else None
            )

            order_sql = (
                f"ORDER BY {order_col} DESC"
                if order_col
                else ""
            )

            row = conn.execute(
                f"""
                SELECT {status_col} AS status
                FROM {table_name}
                WHERE {key} = ?
                {order_sql}
                LIMIT 1
                """,
                (
                    int(user_id),
                ),
            ).fetchone()

            if row and row["status"] is not None:
                profile.kyc_status = (
                    _normalize_kyc_status(
                        row["status"]
                    )
                )
                break

        # ====================================================
        # ORDERS
        # ====================================================

        order_cols = _columns(
            conn,
            "orders",
        )

        if "user_id" in order_cols:
            status_expr = (
                "status"
                if "status" in order_cols
                else "''"
            )

            row = conn.execute(
                f"""
                SELECT
                    COUNT(*) AS total,

                    SUM(
                        CASE
                            WHEN LOWER(COALESCE({status_expr}, ''))
                                IN (
                                    'completed',
                                    'complete',
                                    'delivered',
                                    'success',
                                    'approved'
                                )
                            THEN 1
                            ELSE 0
                        END
                    ) AS completed,

                    SUM(
                        CASE
                            WHEN LOWER(COALESCE({status_expr}, ''))
                                IN (
                                    'pending',
                                    'pending_owner_approval',
                                    'approved_pending_delivery',
                                    'processing'
                                )
                            THEN 1
                            ELSE 0
                        END
                    ) AS pending

                FROM orders
                WHERE user_id = ?
                """,
                (
                    int(user_id),
                ),
            ).fetchone()

            if row:
                profile.total_orders = _safe_int(
                    row["total"]
                )
                profile.completed_orders = _safe_int(
                    row["completed"]
                )
                profile.pending_orders = _safe_int(
                    row["pending"]
                )

        # ====================================================
        # REFERRALS
        # ====================================================

        if (
            user_key
            and "referrer_id" in user_cols
        ):
            row = conn.execute(
                """
                SELECT COUNT(*) AS count
                FROM users
                WHERE referrer_id = ?
                """,
                (
                    int(user_id),
                ),
            ).fetchone()

            if row:
                profile.referral_count = _safe_int(
                    row["count"]
                )

        else:
            for table_name in (
                "referrals",
                "user_referrals",
            ):
                cols = _columns(
                    conn,
                    table_name,
                )

                if not cols:
                    continue

                owner_col = (
                    "referrer_id"
                    if "referrer_id" in cols
                    else "inviter_id"
                    if "inviter_id" in cols
                    else None
                )

                if not owner_col:
                    continue

                row = conn.execute(
                    f"""
                    SELECT COUNT(*) AS count
                    FROM {table_name}
                    WHERE {owner_col} = ?
                    """,
                    (
                        int(user_id),
                    ),
                ).fetchone()

                if row:
                    profile.referral_count = _safe_int(
                        row["count"]
                    )
                break

        # ====================================================
        # CRYPTO DEPOSITS
        # ====================================================

        deposit_cols = _columns(
            conn,
            "crypto_deposit_requests",
        )

        if {
            "user_id",
            "status",
        }.issubset(
            deposit_cols
        ):
            credited_expr = (
                """
                COALESCE(
                    SUM(
                        CASE
                            WHEN status = 'approved'
                            THEN COALESCE(credited_toman, 0)
                            ELSE 0
                        END
                    ),
                    0
                )
                """
                if "credited_toman" in deposit_cols
                else "0"
            )

            row = conn.execute(
                f"""
                SELECT
                    COUNT(*) AS total,

                    SUM(
                        CASE
                            WHEN status = 'pending_owner_review'
                            THEN 1
                            ELSE 0
                        END
                    ) AS pending_count,

                    SUM(
                        CASE
                            WHEN status = 'approved'
                            THEN 1
                            ELSE 0
                        END
                    ) AS approved_count,

                    SUM(
                        CASE
                            WHEN status = 'rejected'
                            THEN 1
                            ELSE 0
                        END
                    ) AS rejected_count,

                    {credited_expr} AS credited_total

                FROM crypto_deposit_requests
                WHERE user_id = ?
                """,
                (
                    int(user_id),
                ),
            ).fetchone()

            if row:
                profile.deposit_total = _safe_int(
                    row["total"]
                )
                profile.deposit_pending = _safe_int(
                    row["pending_count"]
                )
                profile.deposit_approved = _safe_int(
                    row["approved_count"]
                )
                profile.deposit_rejected = _safe_int(
                    row["rejected_count"]
                )
                profile.deposit_credited_toman = _safe_int(
                    row["credited_total"]
                )

        return profile

    finally:
        conn.close()


async def profile_data(
    user_id: int,
) -> UserProfileSnapshot:
    return await asyncio.to_thread(
        _profile_sync,
        db_path(),
        int(user_id),
    )


def get_user_level(
    profile: UserProfileSnapshot,
) -> str:
    if profile.is_admin:
        return "مالک / مدیر"

    tier = str(
        profile.user_tier
        or "standard"
    ).strip().lower()

    if tier in {
        "vip",
        "premium",
        "verified",
        "gold",
    }:
        return "VIP"

    return "عضو عادی"


def get_kyc_badge(
    status: str,
) -> str:
    status = _normalize_kyc_status(
        status
    )

    if status == "approved":
        return (
            f"{ce('success')} "
            "تأیید شده"
        )

    if status == "pending":
        return (
            f"{ce('loading')} "
            "در حال بررسی"
        )

    if status == "rejected":
        return (
            f"{ce('error')} "
            "رد شده"
        )

    return (
        f"{ce('warning')} "
        "انجام نشده"
    )


def profile_keyboard(
    *,
    owner: bool,
    kyc_status: str,
) -> InlineKeyboardMarkup:
    """
    منوی کامل و مرتب حساب کاربری.

    واریز ارزی فقط اینجاست و در منوی اصلی نمایش داده نمی‌شود.
    """

    rows: list[
        list[
            InlineKeyboardButton
        ]
    ] = []

    if owner:
        rows.append(
            [
                btn(
                    "ولت‌های دریافت",
                    "admin_crypto_wallets",
                    "success",
                    icon="wallets",
                ),
                btn(
                    "واریزهای منتظر",
                    "admin_crypto_deposits",
                    "primary",
                    icon="flash",
                ),
            ]
        )

    if (
        _normalize_kyc_status(
            kyc_status
        )
        != "approved"
    ):
        rows.append(
            [
                btn(
                    "احراز هویت KYC",
                    "user_kyc_start",
                    "primary",
                    icon="users",
                )
            ]
        )

    rows.extend(
        [
            [
                btn(
                    "افزایش موجودی",
                    "charge_fiat",
                    "success",
                    icon="plus",
                ),
                btn(
                    "واریز ارزی",
                    "crypto_deposit_menu",
                    "success",
                    icon="wallets",
                ),
            ],
            [
                btn(
                    "واریزهای من",
                    "my_crypto_deposits",
                    "primary",
                    icon="ton",
                ),
                btn(
                    "تاریخچه مالی",
                    "wallet_tx_history",
                    "primary",
                    icon="orders",
                ),
            ],
            [
                btn(
                    "سفارش‌های من",
                    "my_orders",
                    "primary",
                    icon="folder",
                ),
                btn(
                    "پشتیبانی",
                    "support",
                    "primary",
                    icon="support",
                ),
            ],
            [
                btn(
                    "بازگشت به منوی اصلی",
                    "main_menu",
                    "danger",
                    icon="back",
                )
            ],
        ]
    )

    return InlineKeyboardMarkup(
        inline_keyboard=rows
    )


def build_profile_text(
    *,
    profile: UserProfileSnapshot,
    telegram_full_name: str,
    telegram_username: Optional[str],
) -> str:
    full_name = escape(
        telegram_full_name
        or profile.full_name
        or "کاربر"
    )

    username = (
        f"@{telegram_username}"
        if telegram_username
        else (
            f"@{profile.username}"
            if profile.username
            else "ثبت نشده"
        )
    )

    account_status = (
        "مسدود"
        if profile.is_banned
        else "فعال"
    )

    account_type = get_user_level(
        profile
    )

    return (
        f"{ce('panel')} "
        "<b>حساب کاربری و کیف پول</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"{ce('user')} "
        "<b>مشخصات حساب</b>\n"

        f"نام: <b>{full_name}</b>\n"

        f"{ce('chat')} "
        "نام کاربری: "
        f"<code>{escape(username)}</code>\n"

        f"{ce('pin')} "
        "شناسه تلگرام: "
        f"<code>{profile.user_id}</code>\n"

        f"{ce('level')} "
        "سطح حساب: "
        f"<b>{escape(account_type)}</b>\n"

        f"{ce('shield')} "
        "وضعیت حساب: "
        f"<b>{account_status}</b>\n"

        f"{ce('shield')} "
        "وضعیت KYC: "
        f"<b>{get_kyc_badge(profile.kyc_status)}</b>\n"

        f"{ce('calendar')} "
        "تاریخ عضویت: "
        f"<code>{escape(profile.joined_date)}</code>\n\n"

        f"{ce('wallet')} "
        "<b>موجودی کیف پول</b>\n"

        f"{ce('money')} "
        "تومان: "
        f"<code>{profile.balance_toman:,}</code> تومان\n"

        f"{ce('ton')} "
        "TON داخلی: "
        f"<code>{profile.balance_ton:,.4f}</code>\n"

        f"{ce('money')} "
        "USDT داخلی: "
        f"<code>{profile.balance_usdt:,.4f}</code>\n\n"

        f"{ce('history')} "
        "<b>آمار فعالیت</b>\n"

        f"{ce('orders')} "
        "کل سفارش‌ها: "
        f"<code>{profile.total_orders:,}</code>\n"

        f"{ce('success')} "
        "سفارش‌های موفق: "
        f"<code>{profile.completed_orders:,}</code>\n"

        f"{ce('loading')} "
        "سفارش‌های در جریان: "
        f"<code>{profile.pending_orders:,}</code>\n"

        f"{ce('referral')} "
        "زیرمجموعه مستقیم: "
        f"<code>{profile.referral_count:,}</code> نفر\n\n"

        f"{ce('crypto')} "
        "<b>واریزهای ارزی</b>\n"

        "کل درخواست‌ها: "
        f"<code>{profile.deposit_total:,}</code>\n"

        "در انتظار بررسی: "
        f"<code>{profile.deposit_pending:,}</code>\n"

        "تأییدشده: "
        f"<code>{profile.deposit_approved:,}</code>\n"

        "ردشده: "
        f"<code>{profile.deposit_rejected:,}</code>\n"

        "مجموع شارژ تومانی از واریز ارزی: "
        f"<code>{profile.deposit_credited_toman:,}</code> تومان\n\n"

        f"{ce('warning')} "
        "<i>برای واریز TON / USDT / TRX از دکمه "
        "«واریز ارزی» همین صفحه استفاده کنید.</i>"
    )


async def _send_profile_card(
    callback: CallbackQuery,
    text: str,
    keyboard: InlineKeyboardMarkup,
) -> None:
    """
    اگر assets/profile_card.jpg وجود داشته باشد، حساب کاربری به‌صورت
    کارت تصویری ارسال می‌شود؛ در غیر این صورت متن معمولی نمایش داده می‌شود.
    """

    if not callback.message:
        return

    chat_id = callback.message.chat.id

    if PROFILE_PHOTO_PATH.is_file():
        try:
            await callback.bot.send_photo(
                chat_id=chat_id,
                photo=FSInputFile(
                    str(
                        PROFILE_PHOTO_PATH
                    )
                ),
                caption=text,
                reply_markup=keyboard,
                parse_mode=ParseMode.HTML,
            )

            try:
                await callback.message.delete()
            except Exception:
                pass

            return

        except TelegramForbiddenError:
            raise

        except Exception:
            logger.warning(
                "Could not send profile photo card; falling back to text.",
                exc_info=True,
            )

    await edit_cb(
        callback,
        text,
        keyboard,
    )


@router.callback_query(
    F.data == "user_profile"
)
async def user_profile(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    await state.clear()

    uid = int(
        callback.from_user.id
    )

    try:
        # جدول‌های واریز ارزی قبل از گرفتن آمار وجود داشته باشند.
        await service().ensure_schema()

    except Exception:
        logger.debug(
            "Could not ensure crypto deposit schema before profile.",
            exc_info=True,
        )

    try:
        profile = await profile_data(
            uid
        )

        # اطلاعات تازه تلگرام برای UI اولویت دارد.
        profile.username = (
            callback.from_user.username
            or profile.username
        )

        profile.full_name = (
            callback.from_user.full_name
            or profile.full_name
        )

        text = build_profile_text(
            profile=profile,
            telegram_full_name=(
                callback.from_user.full_name
                or "کاربر"
            ),
            telegram_username=(
                callback.from_user.username
            ),
        )

        keyboard = profile_keyboard(
            owner=is_admin(
                uid
            ),
            kyc_status=(
                profile.kyc_status
            ),
        )

        await _send_profile_card(
            callback,
            text,
            keyboard,
        )

        await answer_cb(
            callback
        )

        logger.info(
            (
                "User profile displayed | "
                "user=%s orders=%s "
                "crypto_deposits=%s kyc=%s"
            ),
            uid,
            profile.total_orders,
            profile.deposit_total,
            profile.kyc_status,
        )

    except Exception:
        logger.exception(
            "Profile panel error | user=%s",
            uid,
        )

        await answer_cb(
            callback,
            "❌ خطا در نمایش حساب کاربری.",
            True,
        )


@router.message(
    Command("profile")
)
async def profile_command(
    message: Message,
    state: FSMContext,
) -> None:
    """
    نسخه Command حساب کاربری برای تست/دسترسی مستقیم.
    """

    await state.clear()

    if not message.from_user:
        return

    uid = int(
        message.from_user.id
    )

    try:
        await service().ensure_schema()
    except Exception:
        pass

    profile = await profile_data(
        uid
    )

    text = build_profile_text(
        profile=profile,
        telegram_full_name=(
            message.from_user.full_name
            or "کاربر"
        ),
        telegram_username=(
            message.from_user.username
        ),
    )

    keyboard = profile_keyboard(
        owner=is_admin(
            uid
        ),
        kyc_status=(
            profile.kyc_status
        ),
    )

    if PROFILE_PHOTO_PATH.is_file():
        try:
            await message.answer_photo(
                photo=FSInputFile(
                    str(
                        PROFILE_PHOTO_PATH
                    )
                ),
                caption=text,
                reply_markup=keyboard,
                parse_mode=ParseMode.HTML,
            )
            return
        except Exception:
            logger.debug(
                "Profile command photo send failed.",
                exc_info=True,
            )

    await message.answer(
        text,
        reply_markup=keyboard,
        parse_mode=ParseMode.HTML,
    )


# ------------------------------------------------------------------
# Wallet transaction history
# ------------------------------------------------------------------

def _wallet_history_sync(
    path: str,
    user_id: int,
    limit: int = 15,
) -> list[dict[str, Any]]:
    conn = sqlite3.connect(
        path,
        timeout=20,
        check_same_thread=False,
    )
    conn.row_factory = sqlite3.Row

    try:
        cols = _columns(
            conn,
            "wallet_transactions",
        )

        if (
            "user_id" not in cols
            or not cols
        ):
            return []

        select_cols = [
            column
            for column in (
                "id",
                "type",
                "amount",
                "balance_before",
                "balance_after",
                "description",
                "reference_id",
                "created_at",
            )
            if column in cols
        ]

        order_col = (
            "id"
            if "id" in cols
            else "created_at"
            if "created_at" in cols
            else None
        )

        order_sql = (
            f"ORDER BY {order_col} DESC"
            if order_col
            else ""
        )

        rows = conn.execute(
            f"""
            SELECT
                {", ".join(select_cols)}
            FROM wallet_transactions
            WHERE user_id = ?
            {order_sql}
            LIMIT ?
            """,
            (
                int(user_id),
                max(
                    1,
                    min(
                        int(limit),
                        50,
                    ),
                ),
            ),
        ).fetchall()

        return [
            dict(row)
            for row in rows
        ]

    finally:
        conn.close()


@router.callback_query(
    F.data == "wallet_tx_history"
)
async def wallet_tx_history(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    await state.clear()

    items = await asyncio.to_thread(
        _wallet_history_sync,
        db_path(),
        callback.from_user.id,
        15,
    )

    lines = [
        f"{ce('history')} <b>تاریخچه مالی</b>",
        "━━━━━━━━━━━━━━━━━━━━",
        "",
    ]

    if not items:
        lines.append(
            "هنوز تراکنش مالی ثبت‌شده‌ای برای شما وجود ندارد."
        )

    for item in items:
        tx_type = escape(
            str(
                item.get("type")
                or "transaction"
            )
        )

        amount = _safe_int(
            item.get("amount")
        )

        created_at = escape(
            str(
                item.get("created_at")
                or "نامشخص"
            )
        )

        description = (
            escape(
                str(
                    item.get("description")
                )
            )
            if item.get("description")
            else ""
        )

        sign = (
            "+"
            if amount >= 0
            else ""
        )

        lines.extend(
            [
                f"• <b>{tx_type}</b>",
                f"مبلغ: <code>{sign}{amount:,}</code> تومان",
                f"زمان: <code>{created_at}</code>",
            ]
        )

        if description:
            lines.append(
                f"توضیح: {description}"
            )

        lines.append("")

    keyboard = InlineKeyboardMarkup(
        inline_keyboard=[
            [
                btn(
                    "واریز ارزی",
                    "crypto_deposit_menu",
                    "success",
                    icon="wallets",
                ),
                btn(
                    "حساب کاربری",
                    "user_profile",
                    "primary",
                    icon="wallet",
                ),
            ],
            [
                btn(
                    "بازگشت به منوی اصلی",
                    "main_menu",
                    "danger",
                    icon="back",
                )
            ],
        ]
    )

    await edit_cb(
        callback,
        "\n".join(
            lines
        ),
        keyboard,
    )

    await answer_cb(
        callback
    )


@router.message(
    Command("balance")
)
async def balance_command(
    message: Message,
) -> None:
    if not message.from_user:
        return

    profile = await profile_data(
        message.from_user.id
    )

    await message.answer(
        (
            f"{ce('wallet')} <b>موجودی حساب</b>\n\n"
            f"{ce('money')} تومان: "
            f"<code>{profile.balance_toman:,}</code> تومان\n"
            f"{ce('ton')} TON: "
            f"<code>{profile.balance_ton:,.4f}</code>\n"
            f"{ce('money')} USDT: "
            f"<code>{profile.balance_usdt:,.4f}</code>"
        ),
        parse_mode=ParseMode.HTML,
    )



@router.message(
    Command("wallet")
)
async def wallet_command(
    message: Message,
    state: FSMContext,
) -> None:
    """
    Alias مستقیم برای نمایش حساب/کیف پول.
    """

    await profile_command(
        message,
        state,
    )


# ------------------------------------------------------------------
# Owner receiving wallets
# ------------------------------------------------------------------

def owner_wallet_keyboard(configured: set[str]) -> InlineKeyboardMarkup:
    rows = []
    for cur, label in ((TON, "TON"), (USDT, "USDT (TRC20)"), (TRX, "TRX (TRON)")):
        verb = "ویرایش" if cur in configured else "ثبت"
        rows.append([btn(f"{verb} ولت {label}", f"admin_set_crypto_wallet_{cur}", icon=("ton" if cur == TON else "trx" if cur == TRX else "wallet"))])
    rows.append([btn("درخواست‌های واریز در انتظار", "admin_crypto_deposits", "success", icon="flash")])
    rows.append([btn("بازگشت به حساب", "user_profile", "danger", icon="back")])
    return InlineKeyboardMarkup(inline_keyboard=rows)


def owner_wallet_text(wallets: dict) -> str:
    lines = ["👛 <b>ولت‌های دریافت ارزی مالک</b>", "━━━━━━━━━━━━━━━━━━━━", ""]
    for cur in (TON, USDT, TRX):
        item = wallets.get(cur)
        lines.append(f"<b>{cur}</b> — {escape(str(SUPPORTED[cur]['network']))}")
        lines.append(f"<code>{escape(item.address)}</code>" if item else "<i>ثبت نشده</i>")
        lines.append("")
    lines += ["این آدرس‌ها برای کاربران نمایش داده می‌شوند.", "⚠️ فقط آدرس عمومی وارد کنید؛ Seed/Private Key وارد نکنید."]
    return "\n".join(lines)


async def show_owner_wallets(target: CallbackQuery | Message) -> None:
    if not is_admin(target.from_user.id):
        if isinstance(target, CallbackQuery):
            await answer_cb(target, "دسترسی ندارید.", True)
        else:
            await target.answer("⛔ دسترسی ندارید.")
        return
    wallets = await service().list_owner_wallets()
    text = owner_wallet_text(wallets)
    kb = owner_wallet_keyboard(set(wallets))
    if isinstance(target, CallbackQuery):
        await edit_cb(target, text, kb); await answer_cb(target)
    else:
        await target.answer(text, reply_markup=kb, parse_mode=ParseMode.HTML)


@router.message(Command("crypto_wallets"))
async def owner_wallet_command(message: Message, state: FSMContext) -> None:
    await state.clear(); await show_owner_wallets(message)


@router.callback_query(F.data == "admin_crypto_wallets")
async def owner_wallet_callback(callback: CallbackQuery, state: FSMContext) -> None:
    await state.clear(); await show_owner_wallets(callback)


@router.callback_query(F.data.startswith("admin_set_crypto_wallet_"))
async def owner_wallet_start(callback: CallbackQuery, state: FSMContext) -> None:
    if not is_admin(callback.from_user.id):
        await answer_cb(callback, "دسترسی ندارید.", True); return
    try:
        cur = normalize_currency(str(callback.data).removeprefix("admin_set_crypto_wallet_"))
    except Exception:
        await answer_cb(callback, "ارز نامعتبر است.", True); return
    await state.clear(); await state.update_data(owner_currency=cur); await state.set_state(OwnerCryptoStates.wallet_address)
    await edit_cb(callback, f"👛 <b>ثبت ولت دریافت {cur}</b>\n\nشبکه: <b>{escape(str(SUPPORTED[cur]['network']))}</b>\n\nآدرس عمومی مقصد را ارسال کنید.\n\n⚠️ Seed/Private Key ارسال نکنید.", InlineKeyboardMarkup(inline_keyboard=[[btn("انصراف", "admin_crypto_wallets", "danger")]]))
    await answer_cb(callback)


@router.message(OwnerCryptoStates.wallet_address, F.text)
async def owner_wallet_save(message: Message, state: FSMContext) -> None:
    if not is_admin(message.from_user.id):
        await state.clear(); return
    data = await state.get_data(); cur = str(data.get("owner_currency", ""))
    try:
        address = validate_address(cur, message.text or "")
        await service().set_owner_wallet(cur, address, message.from_user.id)
    except DepositValidationError as exc:
        await message.answer(f"❌ {escape(str(exc))}", parse_mode=ParseMode.HTML); return
    await state.clear(); wallets = await service().list_owner_wallets()
    await message.answer(f"✅ <b>ولت {cur} ثبت شد.</b>\n\n<code>{escape(address)}</code>", reply_markup=owner_wallet_keyboard(set(wallets)), parse_mode=ParseMode.HTML)


# ------------------------------------------------------------------
# User deposit flow
# ------------------------------------------------------------------

@router.callback_query(F.data == "crypto_deposit_menu")
async def deposit_menu(callback: CallbackQuery, state: FSMContext) -> None:
    await state.clear(); await fast_crypto_rates.ensure_started()
    wallets = await service().list_owner_wallets(); available = set(wallets)
    if not available:
        await edit_cb(callback, "💱 <b>واریز ارزی</b>\n\nدر حال حاضر هیچ ولت دریافت ارزی فعال نیست.", InlineKeyboardMarkup(inline_keyboard=[[btn("بازگشت", "user_profile", "danger")]]))
    else:
        await edit_cb(callback, "💱 <b>واریز ارزی و شارژ تومانی</b>\n━━━━━━━━━━━━━━━━━━━━\n\nارز مورد نظر را انتخاب کنید. بعد از واریز و ثبت TX Hash، درخواست برای مالک می‌رود. با تأیید مالک، معادل تومانی با نرخ لحظه تأیید شارژ می‌شود.", get_crypto_deposit_currency_keyboard(available))
    await answer_cb(callback)


@router.callback_query(F.data.in_({"crypto_deposit_TON", "crypto_deposit_USDT", "crypto_deposit_TRX"}))
async def deposit_currency(callback: CallbackQuery, state: FSMContext) -> None:
    cur = normalize_currency(str(callback.data).removeprefix("crypto_deposit_"))
    wallet = await service().get_owner_wallet(cur)
    if not wallet:
        await answer_cb(callback, "ولت این ارز فعال نیست.", True); return
    await state.clear()
    await edit_cb(callback, f"💱 <b>واریز {cur}</b>\n━━━━━━━━━━━━━━━━━━━━\n\n🌐 شبکه: <b>{escape(wallet.network)}</b>\n\n📥 آدرس مقصد:\n<code>{escape(wallet.address)}</code>\n\n⚠️ فقط روی همین شبکه واریز کنید. سپس «واریز کردم» را بزنید.", get_crypto_deposit_destination_keyboard(cur))
    await answer_cb(callback)


@router.callback_query(F.data.startswith("crypto_deposit_paid_"))
async def deposit_start(callback: CallbackQuery, state: FSMContext) -> None:
    cur = normalize_currency(str(callback.data).removeprefix("crypto_deposit_paid_"))
    wallet = await service().get_owner_wallet(cur)
    if not wallet:
        await answer_cb(callback, "ولت مقصد فعال نیست.", True); return
    await state.clear(); await state.update_data(currency=cur); await state.set_state(CryptoDepositStates.amount)
    await edit_cb(callback, f"💰 <b>مقدار واریز {cur}</b>\n\nمقدار دقیق واریزشده را ارسال کنید.\nمثال: <code>12.5</code>", get_crypto_deposit_cancel_keyboard())
    await answer_cb(callback)


@router.message(CryptoDepositStates.amount, F.text)
async def deposit_amount(message: Message, state: FSMContext) -> None:
    cur = str((await state.get_data()).get("currency", ""))
    try:
        amount = parse_amount(message.text, cur)
    except DepositValidationError as exc:
        await message.answer(f"❌ {escape(str(exc))}", reply_markup=get_crypto_deposit_cancel_keyboard(), parse_mode=ParseMode.HTML); return
    await state.update_data(amount=format(amount, "f")); await state.set_state(CryptoDepositStates.from_wallet)
    await message.answer("📤 <b>آدرس ولت فرستنده</b>\n\nآدرس عمومی ولتی که از آن انتقال داده‌اید را ارسال کنید.", reply_markup=get_crypto_deposit_cancel_keyboard(), parse_mode=ParseMode.HTML)


@router.message(CryptoDepositStates.from_wallet, F.text)
async def deposit_sender(message: Message, state: FSMContext) -> None:
    cur = str((await state.get_data()).get("currency", ""))
    try:
        address = validate_address(cur, message.text or "")
    except DepositValidationError as exc:
        await message.answer(f"❌ {escape(str(exc))}", reply_markup=get_crypto_deposit_cancel_keyboard(), parse_mode=ParseMode.HTML); return
    await state.update_data(from_wallet=address); await state.set_state(CryptoDepositStates.tx_hash)
    await message.answer("🔎 <b>TX Hash / Transaction ID</b>\n\nشناسه دقیق تراکنش را ارسال کنید. هر TX Hash فقط یک بار قابل ثبت است.", reply_markup=get_crypto_deposit_cancel_keyboard(), parse_mode=ParseMode.HTML)


@router.message(CryptoDepositStates.tx_hash, F.text)
async def deposit_hash(message: Message, state: FSMContext) -> None:
    try:
        tx = normalize_tx_hash(message.text or "")
    except DepositValidationError as exc:
        await message.answer(f"❌ {escape(str(exc))}", reply_markup=get_crypto_deposit_cancel_keyboard(), parse_mode=ParseMode.HTML); return
    await state.update_data(tx_hash=tx); await state.set_state(CryptoDepositStates.paid_at)
    await message.answer("🕒 <b>تاریخ و ساعت واریز</b>\n\nمثال: <code>2026/08/18 - 11:40</code>\nاگر همین الان واریز کرده‌اید بنویسید: <code>الان</code>", reply_markup=get_crypto_deposit_cancel_keyboard(), parse_mode=ParseMode.HTML)


@router.message(CryptoDepositStates.paid_at, F.text)
async def deposit_time(message: Message, state: FSMContext) -> None:
    raw = str(message.text or "").strip()
    if not raw or len(raw) > 100:
        await message.answer("❌ زمان واردشده معتبر نیست."); return
    claimed = now_tehran() if raw in {"الان", "همین الان", "now"} else raw
    await state.update_data(paid_at=claimed); await state.set_state(CryptoDepositStates.receipt)
    await message.answer("📎 <b>رسید واریز</b>\n\nاسکرین‌شات یا فایل رسید را ارسال کنید؛ یا «رسید ندارم» را بزنید.", reply_markup=get_crypto_receipt_keyboard(), parse_mode=ParseMode.HTML)


async def ask_note(message: Message, state: FSMContext) -> None:
    await state.set_state(CryptoDepositStates.note)
    await message.answer("📝 <b>توضیحات اختیاری</b>\n\nاگر توضیحی برای مالک دارید ارسال کنید؛ یا «بدون توضیحات» را بزنید.", reply_markup=get_crypto_note_keyboard(), parse_mode=ParseMode.HTML)


@router.message(CryptoDepositStates.receipt, F.photo)
async def receipt_photo(message: Message, state: FSMContext) -> None:
    await state.update_data(receipt_file_id=message.photo[-1].file_id, receipt_type="photo"); await ask_note(message, state)


@router.message(CryptoDepositStates.receipt, F.document)
async def receipt_doc(message: Message, state: FSMContext) -> None:
    await state.update_data(receipt_file_id=message.document.file_id, receipt_type="document"); await ask_note(message, state)


@router.callback_query(F.data == "crypto_dep_skip_receipt")
async def receipt_skip(callback: CallbackQuery, state: FSMContext) -> None:
    current = await state.get_state()
    if current != CryptoDepositStates.receipt.state:
        await answer_cb(callback); return
    await state.update_data(receipt_file_id=None, receipt_type=None); await state.set_state(CryptoDepositStates.note)
    await edit_cb(callback, "📝 <b>توضیحات اختیاری</b>\n\nاگر توضیحی برای مالک دارید ارسال کنید؛ یا «بدون توضیحات» را بزنید.", get_crypto_note_keyboard()); await answer_cb(callback)


# ------------------------------------------------------------------
# Deposit display / estimate helpers
# ------------------------------------------------------------------

def db_time_to_tehran(
    value: Any,
) -> str:
    """
    SQLite CURRENT_TIMESTAMP به‌صورت UTC ذخیره می‌شود.
    این تابع آن را برای نمایش به وقت تهران تبدیل می‌کند.
    اگر فرمت ناشناخته باشد، همان مقدار اصلی نمایش داده می‌شود.
    """

    raw = str(
        value
        or ""
    ).strip()

    if not raw:
        return "نامشخص"

    try:
        normalized = raw.replace(
            "Z",
            "+00:00",
        )

        dt = datetime.fromisoformat(
            normalized
        )

        if dt.tzinfo is None:
            dt = dt.replace(
                tzinfo=ZoneInfo("UTC")
            )

        return dt.astimezone(
            TEHRAN
        ).strftime(
            "%Y/%m/%d - %H:%M:%S"
        )

    except Exception:
        return raw


async def get_request_estimate(
    r: DepositRequest,
) -> tuple[
    Optional[Decimal],
    Optional[int],
    Optional[str],
]:
    """
    ارزش تقریبی درخواست در لحظه ثبت.

    مهم:
        این عدد فقط برای نمایش است.
        مبلغ نهایی هنگام تأیید مالک با نرخ همان لحظه محاسبه می‌شود.
    """

    try:
        quote = await fast_crypto_rates.get_rate(
            r.currency
        )

        rate = Decimal(
            str(
                quote.rate_toman
            )
        )

        estimated_toman = int(
            (
                r.amount
                * rate
            ).quantize(
                Decimal("1"),
                rounding=ROUND_HALF_UP,
            )
        )

        if (
            rate <= 0
            or estimated_toman <= 0
        ):
            return (
                None,
                None,
                None,
            )

        return (
            rate,
            estimated_toman,
            str(
                quote.source
                or "نامشخص"
            ),
        )

    except FastRateUnavailable:
        logger.warning(
            (
                "Initial crypto deposit estimate "
                "is unavailable | request=%s currency=%s"
            ),
            r.id,
            r.currency,
        )

    except Exception:
        logger.exception(
            (
                "Could not calculate initial "
                "crypto deposit estimate | "
                "request=%s currency=%s"
            ),
            r.id,
            r.currency,
        )

    return (
        None,
        None,
        None,
    )


def build_user_deposit_registered_text(
    r: DepositRequest,
    *,
    estimate_rate: Optional[Decimal],
    estimate_toman: Optional[int],
    estimate_source: Optional[str],
) -> str:
    """
    متن کامل رسید ثبت درخواست برای کاربر.
    """

    registered_at = db_time_to_tehran(
        r.created_at
    )

    receipt_status = (
        "ارسال شده"
        if r.receipt_file_id
        else "بدون رسید"
    )

    note_text = escape(
        r.note
        or "ندارد"
    )

    rate_text = (
        f"<code>{estimate_rate:,.0f}</code> تومان / {r.currency}"
        if estimate_rate is not None
        else "<b>فعلاً در دسترس نیست</b>"
    )

    toman_text = (
        f"<code>{int(estimate_toman):,}</code> تومان"
        if estimate_toman is not None
        else "<b>فعلاً قابل محاسبه نیست</b>"
    )

    source_text = (
        f"<code>{escape(estimate_source)}</code>"
        if estimate_source
        else "<code>نامشخص</code>"
    )

    return (
        f"{ce('success')} "
        "<b>درخواست واریز ارزی با موفقیت ثبت شد</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"{ce('history')} "
        "<b>اطلاعات درخواست</b>\n"

        f"🧾 شماره درخواست: <code>#{r.id}</code>\n"

        "📌 وضعیت: "
        f"<b>{status_label(r.status)}</b>\n"

        "🗓 تاریخ ثبت در ربات: "
        f"<code>{escape(registered_at)}</code>\n"

        "🕒 زمان واریز اعلام‌شده: "
        f"<code>{escape(r.claimed_paid_at)}</code>\n\n"

        f"{ce('crypto')} "
        "<b>جزئیات مبلغ و نرخ</b>\n"

        f"💎 ارز: <b>{r.currency}</b>\n"
        f"🌐 شبکه: <b>{r.network}</b>\n"

        "💰 مقدار واریزی: "
        f"<code>{r.amount} {r.currency}</code>\n"

        "📈 نرخ تقریبی لحظه ثبت: "
        f"{rate_text}\n"

        "💵 ارزش تقریبی لحظه ثبت: "
        f"{toman_text}\n"

        "🔌 منبع نرخ: "
        f"{source_text}\n\n"

        "🏦 <b>اطلاعات انتقال</b>\n"

        "📤 ولت فرستنده:\n"
        f"<code>{escape(r.from_wallet)}</code>\n\n"

        "📥 ولت مقصد:\n"
        f"<code>{escape(r.destination_wallet)}</code>\n\n"

        "🔎 TX Hash:\n"
        f"<code>{escape(r.tx_hash)}</code>\n\n"

        f"📎 رسید پرداخت: <b>{receipt_status}</b>\n"
        f"📝 توضیحات: {note_text}\n\n"

        "━━━━━━━━━━━━━━━━━━━━\n"
        f"{ce('loading')} "
        "<b>در انتظار بررسی مالک</b>\n\n"

        "درخواست شما برای مالک ارسال شده است. "
        "پس از بررسی تراکنش و تأیید مالک، مبلغ نهایی تومانی "
        "به موجودی حساب شما اضافه می‌شود.\n\n"

        f"{ce('warning')} "
        "<b>توجه:</b> مبلغ تومانی بالا فقط برآورد لحظه ثبت است؛ "
        "<b>مبلغ نهایی بر اساس نرخ لحظه تأیید مالک</b> محاسبه می‌شود."
    )


# ------------------------------------------------------------------
# Admin notification helpers
# ------------------------------------------------------------------

def status_label(status: str) -> str:
    return {PENDING: "⏳ در انتظار بررسی", APPROVED: "✅ تأیید شده", REJECTED: "❌ رد شده"}.get(status, status)


def review_keyboard(request_id: int) -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup(inline_keyboard=[
        [btn("تأیید و شارژ تومانی", f"crypto_dep_approve_{request_id}", "success")],
        [btn("رد درخواست", f"crypto_dep_reject_{request_id}", "danger"), btn("بروزرسانی جزئیات", f"crypto_dep_view_{request_id}")],
    ])


def admin_text(
    r: DepositRequest,
    *,
    estimate_rate: Optional[Decimal] = None,
    estimate_toman: Optional[int] = None,
    estimate_source: Optional[str] = None,
) -> str:
    user = (
        f"@{r.username}"
        if r.username
        else "ندارد"
    )

    lines = [
        "💰 <b>درخواست واریز ارزی</b>",
        "━━━━━━━━━━━━━━━━━━━━",
        "",

        f"🧾 شماره: <code>#{r.id}</code>",

        f"👤 کاربر: "
        f"<b>{escape(r.full_name or 'کاربر')}</b>",

        f"🆔 User ID: "
        f"<code>{r.user_id}</code>",

        f"🔗 Username: "
        f"<code>{escape(user)}</code>",

        "",

        f"💎 ارز: <b>{r.currency}</b>",

        f"🌐 شبکه: "
        f"<b>{r.network}</b>",

        f"💰 مبلغ: "
        f"<code>{r.amount} {r.currency}</code>",
    ]

    if (
        estimate_rate is not None
        and estimate_toman is not None
    ):
        lines += [
            "",
            "📊 <b>برآورد لحظه ثبت</b>",

            "📈 نرخ تقریبی: "
            f"<code>{estimate_rate:,.0f}</code> "
            f"تومان / {r.currency}",

            "💵 ارزش تقریبی: "
            f"<code>{estimate_toman:,}</code> تومان",

            "🔌 منبع نرخ: "
            f"<code>{escape(estimate_source or 'نامشخص')}</code>",
        ]

    lines += [
        "",
        "📤 ولت فرستنده:",
        f"<code>{escape(r.from_wallet)}</code>",
        "",

        "📥 ولت مقصد مالک:",
        f"<code>{escape(r.destination_wallet)}</code>",
        "",

        "🔎 TX Hash:",
        f"<code>{escape(r.tx_hash)}</code>",
        "",

        "🕒 زمان اعلامی واریز: "
        f"<code>{escape(r.claimed_paid_at)}</code>",

        "🗓 زمان ثبت درخواست: "
        f"<code>{escape(db_time_to_tehran(r.created_at))}</code>",

        f"📎 رسید: "
        f"<b>{'ارسال شده' if r.receipt_file_id else 'بدون رسید'}</b>",

        f"📝 توضیحات: "
        f"{escape(r.note or 'ندارد')}",

        "",
        f"📌 وضعیت: "
        f"<b>{status_label(r.status)}</b>",
    ]

    if r.status == APPROVED:
        lines += [
            "",
            "━━━━━━━━━━━━━━━━━━━━",

            "📊 نرخ نهایی تأیید: "
            f"<code>{r.rate_toman or 0:,.0f}</code> تومان",

            "💵 شارژ نهایی: "
            f"<code>{int(r.credited_toman or 0):,}</code> تومان",

            "👛 موجودی بعد: "
            f"<code>{int(r.balance_after or 0):,}</code> تومان",
        ]

    elif r.status == REJECTED:
        lines += [
            "",
            "━━━━━━━━━━━━━━━━━━━━",

            "📝 دلیل رد: "
            f"{escape(r.rejection_reason or 'ثبت نشده')}",
        ]

    return "\n".join(
        lines
    )


async def notify_admin(
    bot,
    admin_id: int,
    r: DepositRequest,
    *,
    estimate_rate: Optional[Decimal] = None,
    estimate_toman: Optional[int] = None,
    estimate_source: Optional[str] = None,
) -> None:
    text = admin_text(
        r,
        estimate_rate=estimate_rate,
        estimate_toman=estimate_toman,
        estimate_source=estimate_source,
    )

    kb = review_keyboard(
        r.id
    )

    try:
        if (
            r.receipt_file_id
            and r.receipt_type == "photo"
        ):
            await bot.send_photo(
                admin_id,
                r.receipt_file_id,
                caption=text,
                reply_markup=kb,
                parse_mode=ParseMode.HTML,
            )

        elif (
            r.receipt_file_id
            and r.receipt_type == "document"
        ):
            await bot.send_document(
                admin_id,
                r.receipt_file_id,
                caption=text,
                reply_markup=kb,
                parse_mode=ParseMode.HTML,
            )

        else:
            await bot.send_message(
                admin_id,
                text,
                reply_markup=kb,
                parse_mode=ParseMode.HTML,
            )

    except Exception:
        logger.exception(
            (
                "Could not notify admin %s "
                "for crypto deposit #%s"
            ),
            admin_id,
            r.id,
        )


async def finalize(
    bot,
    chat_id: int,
    user: User,
    state: FSMContext,
    note: Optional[str],
) -> None:
    data = await state.get_data()

    try:
        r = await service().create_request(
            user_id=user.id,
            username=user.username or "",
            full_name=user.full_name or "",

            currency=str(
                data.get(
                    "currency",
                    "",
                )
            ),

            crypto_amount=str(
                data.get(
                    "amount",
                    "",
                )
            ),

            from_wallet=str(
                data.get(
                    "from_wallet",
                    "",
                )
            ),

            tx_hash=str(
                data.get(
                    "tx_hash",
                    "",
                )
            ),

            claimed_paid_at=str(
                data.get(
                    "paid_at",
                    "",
                )
            ),

            receipt_file_id=(
                data.get(
                    "receipt_file_id"
                )
            ),

            receipt_type=(
                data.get(
                    "receipt_type"
                )
            ),

            note=note,
        )

    except DuplicateTxError:
        await bot.send_message(
            chat_id,
            (
                "⛔ این TX Hash قبلاً "
                "در سیستم ثبت شده است."
            ),
        )
        return

    except DepositValidationError as exc:
        await bot.send_message(
            chat_id,
            (
                "❌ ثبت درخواست انجام نشد:\n"
                f"{escape(str(exc))}"
            ),
            parse_mode=ParseMode.HTML,
        )
        return

    except Exception:
        logger.exception(
            "Deposit request create failed"
        )

        await bot.send_message(
            chat_id,
            (
                "❌ ثبت درخواست با خطا "
                "روبه‌رو شد."
            ),
        )
        return

    # --------------------------------------------------------
    # Current estimate - display only
    # --------------------------------------------------------

    (
        estimate_rate,
        estimate_toman,
        estimate_source,
    ) = await get_request_estimate(
        r
    )

    await state.clear()

    # --------------------------------------------------------
    # Owner notification
    # --------------------------------------------------------

    for aid in admin_ids():
        await notify_admin(
            bot,
            aid,
            r,
            estimate_rate=estimate_rate,
            estimate_toman=estimate_toman,
            estimate_source=estimate_source,
        )

    # --------------------------------------------------------
    # Detailed user receipt
    # --------------------------------------------------------

    text = (
        build_user_deposit_registered_text(
            r,
            estimate_rate=estimate_rate,
            estimate_toman=estimate_toman,
            estimate_source=estimate_source,
        )
    )

    keyboard = InlineKeyboardMarkup(
        inline_keyboard=[
            [
                btn(
                    "درخواست‌های واریز من",
                    "my_crypto_deposits",
                    "primary",
                    icon="orders",
                )
            ],
            [
                btn(
                    "واریز جدید",
                    "crypto_deposit_menu",
                    "success",
                    icon="wallets",
                ),
                btn(
                    "حساب کاربری",
                    "user_profile",
                    "primary",
                    icon="wallet",
                ),
            ],
        ]
    )

    await bot.send_message(
        chat_id,
        text,
        reply_markup=keyboard,
        parse_mode=ParseMode.HTML,
    )


@router.message(CryptoDepositStates.note, F.text)
async def note_text(message: Message, state: FSMContext) -> None:
    note = str(message.text or "").strip()
    if len(note) > 1000:
        await message.answer("❌ توضیحات حداکثر ۱۰۰۰ کاراکتر باشد."); return
    await finalize(message.bot, message.chat.id, message.from_user, state, note)


@router.callback_query(F.data == "crypto_dep_skip_note")
async def note_skip(callback: CallbackQuery, state: FSMContext) -> None:
    current = await state.get_state()
    if current != CryptoDepositStates.note.state or not callback.message:
        await answer_cb(callback); return
    await answer_cb(callback)
    await finalize(callback.bot, callback.message.chat.id, callback.from_user, state, None)


# ------------------------------------------------------------------
# User deposit history
# ------------------------------------------------------------------

@router.callback_query(F.data == "my_crypto_deposits")
async def my_deposits(callback: CallbackQuery, state: FSMContext) -> None:
    await state.clear(); items = await service().list_user(callback.from_user.id, 10)
    lines = ["💱 <b>درخواست‌های واریز ارزی من</b>", "━━━━━━━━━━━━━━━━━━━━", ""]
    if not items:
        lines.append("هنوز درخواستی ثبت نشده است.")
    for r in items:
        lines += [
            f"🧾 <code>#{r.id}</code> — <b>{r.currency}</b>",
            f"🌐 شبکه: <b>{r.network}</b>",
            f"💰 مقدار: <code>{r.amount}</code> {r.currency}",
            f"🗓 ثبت: <code>{escape(db_time_to_tehran(r.created_at))}</code>",
            f"📌 {status_label(r.status)}",
        ]

        if r.status == APPROVED:
            lines += [
                (
                    "📈 نرخ نهایی: "
                    f"<code>{r.rate_toman or 0:,.0f}</code> تومان"
                ),
                (
                    "💵 شارژ نهایی: "
                    f"<code>{int(r.credited_toman or 0):,}</code> تومان"
                ),
            ]

        if r.status == REJECTED:
            lines.append(
                f"📝 دلیل: {escape(r.rejection_reason or 'ثبت نشده')}"
            )

        lines.append("")
    kb = InlineKeyboardMarkup(inline_keyboard=[[btn("واریز جدید", "crypto_deposit_menu", "success")], [btn("بازگشت", "user_profile", "danger")]])
    await edit_cb(callback, "\n".join(lines), kb); await answer_cb(callback)


# ------------------------------------------------------------------
# Admin pending list / review
# ------------------------------------------------------------------

def pending_keyboard(items: list[DepositRequest]) -> InlineKeyboardMarkup:
    rows = [[btn(f"#{r.id} | {r.amount} {r.currency}", f"crypto_dep_view_{r.id}")] for r in items[:15]]
    rows += [[btn("بروزرسانی", "admin_crypto_deposits", "success")], [btn("ولت‌های دریافت", "admin_crypto_wallets")], [btn("بازگشت", "user_profile", "danger")]]
    return InlineKeyboardMarkup(inline_keyboard=rows)


async def show_pending(target: CallbackQuery | Message) -> None:
    if not is_admin(target.from_user.id):
        if isinstance(target, CallbackQuery): await answer_cb(target, "دسترسی ندارید.", True)
        else: await target.answer("⛔ دسترسی ندارید.")
        return
    items = await service().list_pending(15)
    text = f"📥 <b>واریزهای ارزی در انتظار</b>\n━━━━━━━━━━━━━━━━━━━━\n\nتعداد: <code>{len(items)}</code>\n\nبرای جزئیات روی درخواست بزنید."
    if isinstance(target, CallbackQuery):
        await edit_cb(target, text, pending_keyboard(items)); await answer_cb(target)
    else:
        await target.answer(text, reply_markup=pending_keyboard(items), parse_mode=ParseMode.HTML)


@router.message(Command("crypto_deposits"))
async def pending_command(message: Message, state: FSMContext) -> None:
    await state.clear(); await show_pending(message)


@router.callback_query(F.data == "admin_crypto_deposits")
async def pending_callback(callback: CallbackQuery, state: FSMContext) -> None:
    await state.clear(); await show_pending(callback)


@router.callback_query(F.data.startswith("crypto_dep_view_"))
async def review_view(callback: CallbackQuery) -> None:
    if not is_admin(callback.from_user.id): await answer_cb(callback, "دسترسی ندارید.", True); return
    try: rid = int(str(callback.data).removeprefix("crypto_dep_view_"))
    except ValueError: return
    r = await service().get_request(rid)
    if not r: await answer_cb(callback, "درخواست پیدا نشد.", True); return
    await edit_cb(callback, admin_text(r), review_keyboard(r.id) if r.status == PENDING else None); await answer_cb(callback)


@router.callback_query(F.data.startswith("crypto_dep_approve_"))
async def review_approve(callback: CallbackQuery) -> None:
    if not is_admin(callback.from_user.id): await answer_cb(callback, "دسترسی ندارید.", True); return
    try: rid = int(str(callback.data).removeprefix("crypto_dep_approve_"))
    except ValueError: return
    r = await service().get_request(rid)
    if not r or r.status != PENDING:
        await answer_cb(callback, "این درخواست قبلاً بررسی شده یا موجود نیست.", True); return
    try:
        quote = await fast_crypto_rates.get_rate(r.currency)
    except FastRateUnavailable:
        await answer_cb(callback, "نرخ معتبر در دسترس نیست؛ موجودی تغییر نکرد. دوباره تلاش کنید.", True); return
    try:
        result = await service().approve(rid, admin_id=callback.from_user.id, rate_toman=quote.rate_toman, rate_source=f"{quote.source}|age={quote.age_seconds:.1f}s")
    except AlreadyHandledError:
        await answer_cb(callback, "این درخواست هم‌زمان یا قبلاً بررسی شده است.", True); return
    except Exception:
        logger.exception("Deposit approval failed #%s", rid); await answer_cb(callback, "تأیید انجام نشد و موجودی تغییر نکرد.", True); return
    await edit_cb(callback, admin_text(result.request), None)
    await answer_cb(callback, f"✅ {result.credited_toman:,} تومان شارژ شد.", True)
    try:
        await callback.bot.send_message(result.request.user_id, f"✅ <b>واریز شما تأیید شد</b>\n━━━━━━━━━━━━━━━━━━━━\n\n🧾 درخواست: <code>#{rid}</code>\n💎 ارز: <b>{result.request.currency}</b>\n💰 مقدار: <code>{result.request.amount} {result.request.currency}</code>\n\n📊 نرخ: <code>{result.rate_toman:,.0f}</code> تومان\n💵 شارژ: <code>{result.credited_toman:,}</code> تومان\n👛 موجودی جدید: <code>{result.balance_after:,}</code> تومان\n🕒 تأیید: <code>{now_tehran()}</code>", parse_mode=ParseMode.HTML)
    except Exception:
        logger.exception("Could not notify approved user #%s", rid)


@router.callback_query(F.data.startswith("crypto_dep_reject_"))
async def review_reject_start(callback: CallbackQuery, state: FSMContext) -> None:
    if not is_admin(callback.from_user.id): await answer_cb(callback, "دسترسی ندارید.", True); return
    try: rid = int(str(callback.data).removeprefix("crypto_dep_reject_"))
    except ValueError: return
    r = await service().get_request(rid)
    if not r or r.status != PENDING:
        await answer_cb(callback, "این درخواست دیگر قابل رد نیست.", True); return
    await state.clear(); await state.update_data(reject_id=rid, review_chat=callback.message.chat.id if callback.message else None, review_msg=callback.message.message_id if callback.message else None); await state.set_state(OwnerCryptoStates.reject_reason)
    await callback.message.answer(f"❌ <b>رد درخواست #{rid}</b>\n\nدلیل رد را ارسال کنید؛ برای کاربر هم ارسال می‌شود.", parse_mode=ParseMode.HTML); await answer_cb(callback)


@router.message(OwnerCryptoStates.reject_reason, F.text)
async def review_reject_reason(message: Message, state: FSMContext) -> None:
    if not is_admin(message.from_user.id): await state.clear(); return
    data = await state.get_data()
    try: rid = int(data.get("reject_id"))
    except Exception: await state.clear(); return
    reason = str(message.text or "").strip()
    if not reason: await message.answer("دلیل رد را وارد کنید."); return
    try:
        r = await service().reject(rid, admin_id=message.from_user.id, reason=reason)
    except AlreadyHandledError:
        await state.clear(); await message.answer("این درخواست قبلاً بررسی شده است."); return
    except Exception:
        logger.exception("Deposit rejection failed #%s", rid); await message.answer("❌ رد درخواست انجام نشد."); return
    await state.clear()
    try:
        if data.get("review_chat") and data.get("review_msg"):
            await message.bot.edit_message_reply_markup(chat_id=int(data["review_chat"]), message_id=int(data["review_msg"]), reply_markup=None)
    except Exception: pass
    await message.answer(f"✅ درخواست <code>#{rid}</code> رد شد.\nدلیل: {escape(reason)}", parse_mode=ParseMode.HTML)
    try:
        await message.bot.send_message(r.user_id, f"❌ <b>درخواست واریز شما رد شد</b>\n━━━━━━━━━━━━━━━━━━━━\n\n🧾 درخواست: <code>#{rid}</code>\n💎 ارز: <b>{r.currency}</b>\n💰 مقدار: <code>{r.amount} {r.currency}</code>\n\n📝 دلیل رد:\n{escape(reason)}", parse_mode=ParseMode.HTML)
    except Exception:
        logger.exception("Could not notify rejected user #%s", rid)


__all__ = [
    "router",

    "CryptoDepositStates",
    "OwnerCryptoStates",

    "UserProfileSnapshot",

    "profile_data",
    "build_profile_text",
    "profile_keyboard",

    "user_profile",
    "profile_command",
    "wallet_command",
    "balance_command",
    "wallet_tx_history",

    "service",
    "admin_ids",
    "is_admin",
]