# handlers/user/referral.py

from __future__ import annotations

import asyncio
import logging
import sqlite3
from dataclasses import dataclass
from html import escape
from pathlib import Path
from typing import Any, Final, Optional
from urllib.parse import quote

from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.types import (
    CallbackQuery,
    InlineKeyboardButton,
    InlineKeyboardMarkup,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder

from core.config import settings
from core.ui_emojis import (
    ce,
    emoji_id as ui_emoji_id,
    premiumize_html,
)


logger = logging.getLogger(__name__)

router = Router(
    name="user_referral"
)


# ============================================================
# Configuration
# ============================================================

REFERRAL_TIER_1_PERCENT: Final[float] = float(
    getattr(
        settings,
        "REFERRAL_TIER_1_PERCENT",
        5,
    )
    or 5
)

REFERRAL_TIER_2_PERCENT: Final[float] = float(
    getattr(
        settings,
        "REFERRAL_TIER_2_PERCENT",
        2,
    )
    or 2
)

MIN_REFERRAL_WITHDRAW_TOMAN: Final[int] = int(
    getattr(
        settings,
        "MIN_REFERRAL_WITHDRAW_TOMAN",
        50_000,
    )
    or 50_000
)

REFERRAL_LIST_PAGE_SIZE: Final[int] = max(
    1,
    min(
        int(
            getattr(
                settings,
                "REFERRAL_LIST_PAGE_SIZE",
                8,
            )
            or 8
        ),
        20,
    ),
)


def get_database_path() -> str:
    """
    مسیر دیتابیس اصلی پروژه.

    DB_PATH اولویت دارد و DATABASE_PATH برای سازگاری
    با نسخه‌های قبلی نگه داشته شده است.
    """
    value = (
        getattr(
            settings,
            "DB_PATH",
            None,
        )
        or getattr(
            settings,
            "DATABASE_PATH",
            None,
        )
        or "matrix_bot.db"
    )

    return str(value)


DB_PATH = get_database_path()


# ============================================================
# Models
# ============================================================

@dataclass(slots=True)
class ReferralStats:
    tier_1_count: int = 0
    tier_2_count: int = 0

    # مجموع reward_earned ثبت‌شده در جدول referrals.
    registered_reward_toman: int = 0

    # مجموع مبالغی که قبلاً از پاداش رفرال به کیف پول منتقل شده.
    withdrawn_reward_toman: int = 0

    # پاداش قابل انتقال فعلی.
    available_reward_toman: int = 0

    direct_reward_toman: int = 0


@dataclass(slots=True)
class ReferralMember:
    user_id: int
    level: int
    username: Optional[str] = None
    full_name: Optional[str] = None
    reward_earned: int = 0
    created_at: Optional[str] = None


@dataclass(slots=True)
class ReferralWithdrawalResult:
    success: bool
    reason: str
    amount: int = 0
    balance_before: int = 0
    balance_after: int = 0
    withdrawal_id: Optional[int] = None


# ============================================================
# Database
# ============================================================

def _connect() -> sqlite3.Connection:
    conn = sqlite3.connect(
        DB_PATH,
        timeout=30,
        check_same_thread=False,
    )

    conn.row_factory = sqlite3.Row

    conn.execute(
        "PRAGMA busy_timeout = 30000"
    )
    conn.execute(
        "PRAGMA foreign_keys = ON"
    )

    return conn


def _table_exists(
    conn: sqlite3.Connection,
    table_name: str,
) -> bool:
    row = conn.execute(
        """
        SELECT 1
        FROM sqlite_master
        WHERE type = 'table'
          AND name = ?
        LIMIT 1
        """,
        (
            table_name,
        ),
    ).fetchone()

    return row is not None


def _column_names(
    conn: sqlite3.Connection,
    table_name: str,
) -> set[str]:
    if not _table_exists(
        conn,
        table_name,
    ):
        return set()

    return {
        str(
            row["name"]
        )
        for row
        in conn.execute(
            f"PRAGMA table_info({table_name})"
        ).fetchall()
    }


def _wallet_balance_column(
    conn: sqlite3.Connection,
) -> Optional[str]:
    """
    نسخه فعلی پروژه balance_toman دارد.
    balance برای DBهای قدیمی‌تر پشتیبانی می‌شود.
    """
    columns = _column_names(
        conn,
        "users",
    )

    if "balance_toman" in columns:
        return "balance_toman"

    if "balance" in columns:
        return "balance"

    return None


async def ensure_referral_schema() -> None:
    await asyncio.to_thread(
        _ensure_referral_schema_sync
    )


def _ensure_referral_schema_sync() -> None:
    """
    Migration غیرمخرب.

    referrals:
        رابطه دعوت و reward_earned موجود پروژه.

    referral_withdrawals:
        Ledger برداشت پاداش، برای جلوگیری از برداشت دوباره.

    هیچ داده قدیمی حذف یا reset نمی‌شود.
    """
    conn = _connect()

    try:
        conn.execute(
            "BEGIN IMMEDIATE"
        )

        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS referrals (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                referrer_id INTEGER,
                referred_id INTEGER,
                reward_earned INTEGER DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            """
        )

        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS referral_withdrawals (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                amount_toman INTEGER NOT NULL,
                balance_before INTEGER NOT NULL DEFAULT 0,
                balance_after INTEGER NOT NULL DEFAULT 0,
                status TEXT NOT NULL DEFAULT 'completed',
                created_at TIMESTAMP NOT NULL
                    DEFAULT CURRENT_TIMESTAMP
            )
            """
        )

        conn.execute(
            """
            CREATE INDEX IF NOT EXISTS
            idx_referrals_referrer
            ON referrals(referrer_id)
            """
        )

        conn.execute(
            """
            CREATE INDEX IF NOT EXISTS
            idx_referrals_referred
            ON referrals(referred_id)
            """
        )

        conn.execute(
            """
            CREATE INDEX IF NOT EXISTS
            idx_referral_withdrawals_user
            ON referral_withdrawals(user_id)
            """
        )

        conn.execute(
            """
            CREATE INDEX IF NOT EXISTS
            idx_referral_withdrawals_created
            ON referral_withdrawals(created_at)
            """
        )

        conn.commit()

    except Exception:
        conn.rollback()

        logger.exception(
            "Could not ensure referral schema."
        )

        raise

    finally:
        conn.close()


# ============================================================
# Referral Queries
# ============================================================

async def get_referral_stats(
    user_id: int,
) -> ReferralStats:
    await ensure_referral_schema()

    return await asyncio.to_thread(
        _get_referral_stats_sync,
        int(user_id),
    )


def _get_referral_stats_sync(
    user_id: int,
) -> ReferralStats:
    conn = _connect()

    try:
        direct_row = conn.execute(
            """
            SELECT
                COUNT(DISTINCT referred_id) AS cnt,
                COALESCE(
                    SUM(
                        CASE
                            WHEN reward_earned > 0
                            THEN reward_earned
                            ELSE 0
                        END
                    ),
                    0
                ) AS rewards
            FROM referrals
            WHERE referrer_id = ?
            """,
            (
                user_id,
            ),
        ).fetchone()

        tier_1_count = int(
            direct_row["cnt"]
            or 0
        )

        direct_reward = int(
            direct_row["rewards"]
            or 0
        )

        tier_2_row = conn.execute(
            """
            SELECT
                COUNT(
                    DISTINCT r2.referred_id
                ) AS cnt
            FROM referrals AS r1
            INNER JOIN referrals AS r2
                ON r2.referrer_id = r1.referred_id
            WHERE r1.referrer_id = ?
              AND r2.referred_id != ?
            """,
            (
                user_id,
                user_id,
            ),
        ).fetchone()

        tier_2_count = int(
            tier_2_row["cnt"]
            or 0
        )

        # در schema فعلی reward_earned روی ردیف referrer ذخیره می‌شود.
        registered_reward = max(
            0,
            direct_reward,
        )

        withdrawn_row = conn.execute(
            """
            SELECT COALESCE(
                SUM(amount_toman),
                0
            ) AS total
            FROM referral_withdrawals
            WHERE user_id = ?
              AND status = 'completed'
            """,
            (
                user_id,
            ),
        ).fetchone()

        withdrawn = int(
            withdrawn_row["total"]
            or 0
        )

        available = max(
            0,
            registered_reward
            - withdrawn,
        )

        return ReferralStats(
            tier_1_count=tier_1_count,
            tier_2_count=tier_2_count,
            registered_reward_toman=registered_reward,
            withdrawn_reward_toman=withdrawn,
            available_reward_toman=available,
            direct_reward_toman=direct_reward,
        )

    finally:
        conn.close()


async def get_referral_members(
    user_id: int,
    *,
    page: int = 1,
    per_page: int = REFERRAL_LIST_PAGE_SIZE,
) -> tuple[
    list[ReferralMember],
    int,
]:
    await ensure_referral_schema()

    page = max(
        1,
        int(page),
    )

    per_page = max(
        1,
        min(
            int(per_page),
            20,
        ),
    )

    return await asyncio.to_thread(
        _get_referral_members_sync,
        int(user_id),
        page,
        per_page,
    )


def _get_referral_members_sync(
    user_id: int,
    page: int,
    per_page: int,
) -> tuple[
    list[ReferralMember],
    int,
]:
    conn = _connect()

    try:
        users_exists = _table_exists(
            conn,
            "users",
        )

        users_columns = _column_names(
            conn,
            "users",
        )

        username_select = (
            "u.username"
            if (
                users_exists
                and "username"
                in users_columns
            )
            else "NULL"
        )

        full_name_select = (
            "u.full_name"
            if (
                users_exists
                and "full_name"
                in users_columns
            )
            else "NULL"
        )

        join_users = (
            "LEFT JOIN users AS u "
            "ON u.user_id = network.user_id"
            if (
                users_exists
                and "user_id"
                in users_columns
            )
            else ""
        )

        network_cte = """
            WITH network AS (
                SELECT
                    r1.referred_id AS user_id,
                    1 AS level,
                    COALESCE(
                        r1.reward_earned,
                        0
                    ) AS reward_earned,
                    r1.created_at AS created_at
                FROM referrals AS r1
                WHERE r1.referrer_id = ?

                UNION ALL

                SELECT
                    r2.referred_id AS user_id,
                    2 AS level,
                    0 AS reward_earned,
                    r2.created_at AS created_at
                FROM referrals AS r1
                INNER JOIN referrals AS r2
                    ON r2.referrer_id = r1.referred_id
                WHERE r1.referrer_id = ?
                  AND r2.referred_id != ?
            )
        """

        count_row = conn.execute(
            network_cte
            + """
            SELECT COUNT(*)
            FROM network
            """,
            (
                user_id,
                user_id,
                user_id,
            ),
        ).fetchone()

        total = int(
            count_row[0]
            or 0
        )

        offset = (
            page - 1
        ) * per_page

        query = (
            network_cte
            + f"""
            SELECT
                network.user_id,
                network.level,
                network.reward_earned,
                network.created_at,
                {username_select} AS username,
                {full_name_select} AS full_name
            FROM network
            {join_users}
            ORDER BY
                network.level ASC,
                network.created_at ASC,
                network.user_id ASC
            LIMIT ? OFFSET ?
            """
        )

        rows = conn.execute(
            query,
            (
                user_id,
                user_id,
                user_id,
                per_page,
                offset,
            ),
        ).fetchall()

        members = [
            ReferralMember(
                user_id=int(
                    row["user_id"]
                ),
                level=int(
                    row["level"]
                ),
                username=(
                    str(
                        row["username"]
                    )
                    if row["username"]
                    else None
                ),
                full_name=(
                    str(
                        row["full_name"]
                    )
                    if row["full_name"]
                    else None
                ),
                reward_earned=int(
                    row["reward_earned"]
                    or 0
                ),
                created_at=(
                    str(
                        row["created_at"]
                    )
                    if row["created_at"]
                    else None
                ),
            )
            for row in rows
        ]

        return (
            members,
            total,
        )

    finally:
        conn.close()


# ============================================================
# Withdrawal
# ============================================================

_withdraw_locks: dict[
    int,
    asyncio.Lock,
] = {}

_withdraw_locks_guard = (
    asyncio.Lock()
)


async def _get_withdraw_lock(
    user_id: int,
) -> asyncio.Lock:
    async with _withdraw_locks_guard:
        lock = _withdraw_locks.get(
            user_id
        )

        if lock is None:
            lock = asyncio.Lock()

            _withdraw_locks[
                user_id
            ] = lock

        return lock


async def withdraw_referral_to_wallet(
    user_id: int,
) -> ReferralWithdrawalResult:
    """
    انتقال کل پاداش قابل برداشت به کیف پول تومانی.

    امنیت:
        - BEGIN IMMEDIATE
        - محاسبه مجدد available داخل Transaction
        - ثبت Ledger برداشت
        - افزایش Wallet در همان Transaction

    بنابراین Double-click در همان یا Process دیگر باعث
    برداشت دوباره نمی‌شود.
    """
    await ensure_referral_schema()

    user_id = int(
        user_id
    )

    lock = await _get_withdraw_lock(
        user_id
    )

    async with lock:
        return await asyncio.to_thread(
            _withdraw_referral_to_wallet_sync,
            user_id,
        )


def _withdraw_referral_to_wallet_sync(
    user_id: int,
) -> ReferralWithdrawalResult:
    conn = _connect()

    try:
        conn.execute(
            "BEGIN IMMEDIATE"
        )

        if not _table_exists(
            conn,
            "users",
        ):
            conn.rollback()

            return ReferralWithdrawalResult(
                success=False,
                reason="users_table_missing",
            )

        balance_column = (
            _wallet_balance_column(
                conn
            )
        )

        if not balance_column:
            conn.rollback()

            return ReferralWithdrawalResult(
                success=False,
                reason="wallet_balance_column_missing",
            )

        user_row = conn.execute(
            f"""
            SELECT
                {balance_column} AS balance
            FROM users
            WHERE user_id = ?
            LIMIT 1
            """,
            (
                user_id,
            ),
        ).fetchone()

        if not user_row:
            conn.rollback()

            return ReferralWithdrawalResult(
                success=False,
                reason="user_not_found",
            )

        earned_row = conn.execute(
            """
            SELECT COALESCE(
                SUM(
                    CASE
                        WHEN reward_earned > 0
                        THEN reward_earned
                        ELSE 0
                    END
                ),
                0
            ) AS total
            FROM referrals
            WHERE referrer_id = ?
            """,
            (
                user_id,
            ),
        ).fetchone()

        earned = int(
            earned_row["total"]
            or 0
        )

        withdrawn_row = conn.execute(
            """
            SELECT COALESCE(
                SUM(amount_toman),
                0
            ) AS total
            FROM referral_withdrawals
            WHERE user_id = ?
              AND status = 'completed'
            """,
            (
                user_id,
            ),
        ).fetchone()

        withdrawn = int(
            withdrawn_row["total"]
            or 0
        )

        available = max(
            0,
            earned - withdrawn,
        )

        if (
            available
            < MIN_REFERRAL_WITHDRAW_TOMAN
        ):
            conn.rollback()

            return ReferralWithdrawalResult(
                success=False,
                reason="below_minimum",
                amount=available,
            )

        balance_before = int(
            user_row["balance"]
            or 0
        )

        balance_after = (
            balance_before
            + available
        )

        conn.execute(
            f"""
            UPDATE users
            SET {balance_column} = ?,
                updated_at = CURRENT_TIMESTAMP
            WHERE user_id = ?
            """,
            (
                balance_after,
                user_id,
            ),
        )

        cursor = conn.execute(
            """
            INSERT INTO referral_withdrawals (
                user_id,
                amount_toman,
                balance_before,
                balance_after,
                status
            )
            VALUES (?, ?, ?, ?, 'completed')
            """,
            (
                user_id,
                available,
                balance_before,
                balance_after,
            ),
        )

        withdrawal_id = int(
            cursor.lastrowid
        )

        # اگر wallet_transactions موجود است،
        # تراکنش Wallet هم ثبت می‌شود.
        if _table_exists(
            conn,
            "wallet_transactions",
        ):
            columns = _column_names(
                conn,
                "wallet_transactions",
            )

            required = {
                "user_id",
                "type",
                "amount",
                "balance_before",
                "balance_after",
            }

            if required.issubset(
                columns
            ):
                insert_columns = [
                    "user_id",
                    "type",
                    "amount",
                    "balance_before",
                    "balance_after",
                ]

                values: list[
                    Any
                ] = [
                    user_id,
                    "referral_reward",
                    available,
                    balance_before,
                    balance_after,
                ]

                if (
                    "description"
                    in columns
                ):
                    insert_columns.append(
                        "description"
                    )

                    values.append(
                        "انتقال پاداش زیرمجموعه‌گیری به کیف پول"
                    )

                if (
                    "reference_id"
                    in columns
                ):
                    insert_columns.append(
                        "reference_id"
                    )

                    values.append(
                        f"referral_withdrawal:{withdrawal_id}"
                    )

                placeholders = ", ".join(
                    "?"
                    for _
                    in insert_columns
                )

                conn.execute(
                    (
                        "INSERT INTO wallet_transactions ("
                        + ", ".join(
                            insert_columns
                        )
                        + ") VALUES ("
                        + placeholders
                        + ")"
                    ),
                    tuple(
                        values
                    ),
                )

        # Audit best-effort داخل همان Transaction.
        if _table_exists(
            conn,
            "audit_logs",
        ):
            audit_columns = _column_names(
                conn,
                "audit_logs",
            )

            if {
                "user_id",
                "action",
                "details",
            }.issubset(
                audit_columns
            ):
                conn.execute(
                    """
                    INSERT INTO audit_logs (
                        user_id,
                        action,
                        details
                    )
                    VALUES (?, ?, ?)
                    """,
                    (
                        user_id,
                        "referral_withdrawal",
                        (
                            f"withdrawal_id={withdrawal_id}; "
                            f"amount={available}; "
                            f"balance_before={balance_before}; "
                            f"balance_after={balance_after}"
                        ),
                    ),
                )

        conn.commit()

        logger.info(
            "Referral reward transferred | "
            "user=%s amount=%s withdrawal=%s",
            user_id,
            available,
            withdrawal_id,
        )

        return ReferralWithdrawalResult(
            success=True,
            reason="completed",
            amount=available,
            balance_before=balance_before,
            balance_after=balance_after,
            withdrawal_id=withdrawal_id,
        )

    except Exception:
        conn.rollback()

        logger.exception(
            "Referral withdrawal failed | user=%s",
            user_id,
        )

        return ReferralWithdrawalResult(
            success=False,
            reason="database_error",
        )

    finally:
        conn.close()


# ============================================================
# Buttons
# ============================================================

_ALLOWED_STYLES: Final[
    frozenset[str]
] = frozenset(
    {
        "primary",
        "success",
        "danger",
    }
)


def make_button(
    text: str,
    callback_data: str,
    style: str = "primary",
    emoji_id: str | None = None,
) -> InlineKeyboardButton:
    """
    Factory سازگار با نسخه قبلی.

    تمام دکمه‌های Callback:
        - style دارند
        - Custom Premium Emoji دارند
        - callback_data <= 64 bytes
    """
    style = str(
        style
    ).strip().lower()

    if style not in _ALLOWED_STYLES:
        raise ValueError(
            f"Invalid button style: {style}"
        )

    callback_data = str(
        callback_data
    ).strip()

    if not callback_data:
        raise ValueError(
            "callback_data cannot be empty."
        )

    if len(
        callback_data.encode(
            "utf-8"
        )
    ) > 64:
        raise ValueError(
            "callback_data exceeds 64 bytes."
        )

    icon = emoji_id

    if not icon:
        if style == "success":
            icon = ui_emoji_id(
                "success"
            )

        elif style == "danger":
            icon = ui_emoji_id(
                "error"
            )

        else:
            icon = ui_emoji_id(
                "sparkles"
            )

    return InlineKeyboardButton(
        text=str(
            text
        ),
        callback_data=callback_data,
        style=style,
        icon_custom_emoji_id=icon,
    )


def make_url_button(
    text: str,
    url: str,
    *,
    style: str = "success",
    icon_key: str = "sparkles",
) -> InlineKeyboardButton:
    style = str(
        style
    ).strip().lower()

    if style not in _ALLOWED_STYLES:
        raise ValueError(
            "Invalid URL button style."
        )

    url = str(
        url
    ).strip()

    if not url.startswith(
        (
            "https://",
            "http://",
        )
    ):
        raise ValueError(
            "Invalid URL."
        )

    return InlineKeyboardButton(
        text=text,
        url=url,
        style=style,
        icon_custom_emoji_id=(
            ui_emoji_id(
                icon_key
            )
        ),
    )


# ============================================================
# Keyboards
# ============================================================

def get_referral_keyboard(
    *,
    can_withdraw: bool = False,
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        make_button(
            text="لیست زیرمجموعه‌ها",
            callback_data="referral_list",
            style="primary",
            emoji_id=ui_emoji_id(
                "users"
            ),
        ),
        make_button(
            text="راهنمای کمیسیون",
            callback_data="referral_guide",
            style="primary",
            emoji_id=ui_emoji_id(
                "list"
            ),
        ),
    )

    builder.row(
        make_button(
            text="آمار تفکیکی سطح‌ها",
            callback_data="referral_tiers",
            style="primary",
            emoji_id=ui_emoji_id(
                "history"
            ),
        ),
        make_button(
            text=(
                "برداشت پاداش"
                if can_withdraw
                else "وضعیت پاداش"
            ),
            callback_data="withdraw_referral",
            style=(
                "success"
                if can_withdraw
                else "primary"
            ),
            emoji_id=ui_emoji_id(
                "money"
            ),
        ),
    )

    builder.row(
        make_button(
            text="اشتراک‌گذاری لینک دعوت",
            callback_data="referral_share",
            style="success",
            emoji_id=ui_emoji_id(
                "sparkles"
            ),
        )
    )

    builder.row(
        make_button(
            text="بازگشت به منوی اصلی",
            callback_data="main_menu",
            style="danger",
            emoji_id=ui_emoji_id(
                "back"
            ),
        )
    )

    return builder.as_markup()


def get_referral_back_keyboard() -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        make_button(
            text="بازگشت به بخش رفرال",
            callback_data="referral_menu",
            style="primary",
            emoji_id=ui_emoji_id(
                "back"
            ),
        )
    )

    builder.row(
        make_button(
            text="منوی اصلی",
            callback_data="main_menu",
            style="danger",
            emoji_id=ui_emoji_id(
                "error"
            ),
        )
    )

    return builder.as_markup()


def get_referral_list_keyboard(
    *,
    page: int,
    total_pages: int,
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    navigation: list[
        InlineKeyboardButton
    ] = []

    if page > 1:
        navigation.append(
            make_button(
                text="قبلی",
                callback_data=(
                    "referral_list_page_"
                    f"{page - 1}"
                ),
                style="primary",
                emoji_id=ui_emoji_id(
                    "back"
                ),
            )
        )

    navigation.append(
        make_button(
            text=(
                f"{page} / "
                f"{total_pages}"
            ),
            callback_data="noop",
            style="primary",
            emoji_id=ui_emoji_id(
                "list"
            ),
        )
    )

    if page < total_pages:
        navigation.append(
            make_button(
                text="بعدی",
                callback_data=(
                    "referral_list_page_"
                    f"{page + 1}"
                ),
                style="primary",
                emoji_id=ui_emoji_id(
                    "bolt"
                ),
            )
        )

    builder.row(
        *navigation
    )

    builder.row(
        make_button(
            text="بازگشت به رفرال",
            callback_data="referral_menu",
            style="primary",
            emoji_id=ui_emoji_id(
                "back"
            ),
        )
    )

    return builder.as_markup()


def get_referral_withdraw_keyboard(
    *,
    can_withdraw: bool,
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    if can_withdraw:
        builder.row(
            make_button(
                text="انتقال پاداش به کیف پول",
                callback_data="referral_withdraw_confirm",
                style="success",
                emoji_id=ui_emoji_id(
                    "money"
                ),
            )
        )

    builder.row(
        make_button(
            text="بازگشت به رفرال",
            callback_data="referral_menu",
            style="primary",
            emoji_id=ui_emoji_id(
                "back"
            ),
        ),
        make_button(
            text="منوی اصلی",
            callback_data="main_menu",
            style="danger",
            emoji_id=ui_emoji_id(
                "error"
            ),
        ),
    )

    return builder.as_markup()


def get_share_keyboard(
    referral_link: str,
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    share_url = (
        "https://t.me/share/url"
        f"?url={quote(referral_link, safe='')}"
        "&text="
        + quote(
            "با لینک من وارد ربات شو 👇",
            safe="",
        )
    )

    builder.row(
        make_url_button(
            text="اشتراک‌گذاری در تلگرام",
            url=share_url,
            style="success",
            icon_key="sparkles",
        )
    )

    builder.row(
        make_button(
            text="بازگشت به رفرال",
            callback_data="referral_menu",
            style="primary",
            emoji_id=ui_emoji_id(
                "back"
            ),
        )
    )

    return builder.as_markup()


# ============================================================
# Telegram Helpers
# ============================================================

async def safe_answer(
    callback: CallbackQuery,
    text: str | None = None,
    show_alert: bool = False,
) -> None:
    """
    Callback popup از HTML/tg-emoji پشتیبانی نمی‌کند؛
    این متن عمداً ساده است.
    """
    try:
        await callback.answer(
            text or "",
            show_alert=show_alert,
        )

    except TelegramBadRequest:
        pass

    except Exception:
        logger.exception(
            "Could not answer referral callback."
        )


async def safe_edit(
    callback: CallbackQuery,
    text: str,
    keyboard: InlineKeyboardMarkup,
) -> bool:
    if not callback.message:
        return False

    rendered = premiumize_html(
        text
    )

    try:
        await callback.message.edit_text(
            text=rendered,
            reply_markup=keyboard,
            parse_mode="HTML",
        )

        return True

    except TelegramBadRequest as exc:
        error = str(
            exc
        ).lower()

        if (
            "message is not modified"
            in error
        ):
            return True

        if (
            "message can't be edited"
            in error
            or "message to edit not found"
            in error
        ):
            try:
                await callback.message.answer(
                    rendered,
                    reply_markup=keyboard,
                    parse_mode="HTML",
                )

                return True

            except Exception:
                logger.exception(
                    "Could not send replacement referral message."
                )

                return False

        logger.warning(
            "Referral edit failed: %s",
            exc,
        )

        return False

    except Exception:
        logger.exception(
            "Unexpected referral edit error."
        )

        return False


# ============================================================
# Referral Link
# ============================================================

async def build_referral_link(
    callback: CallbackQuery,
) -> Optional[str]:
    bot_info = await callback.bot.get_me()

    bot_username = (
        bot_info.username
        or ""
    ).strip()

    if not bot_username:
        return None

    return (
        f"https://t.me/{bot_username}"
        f"?start=ref_{callback.from_user.id}"
    )


# ============================================================
# Main Dashboard
# ============================================================

@router.callback_query(
    F.data == "referral_menu"
)
async def callback_referral_system(
    callback: CallbackQuery,
) -> None:
    try:
        await ensure_referral_schema()

        user_id = int(
            callback.from_user.id
        )

        referral_link = (
            await build_referral_link(
                callback
            )
        )

        if not referral_link:
            await safe_answer(
                callback,
                "❌ امکان ساخت لینک دعوت وجود ندارد.",
                show_alert=True,
            )
            return

        stats = await get_referral_stats(
            user_id
        )

        can_withdraw = (
            stats.available_reward_toman
            >= MIN_REFERRAL_WITHDRAW_TOMAN
        )

        referral_text = (
            f"{ce('gift')} "
            "<b>سیستم دعوت و کسب درآمد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"{ce('sparkles')} "
            "<b>لینک اختصاصی دعوت شما:</b>\n"
            f"<code>{escape(referral_link)}</code>\n\n"

            f"{ce('history')} "
            "<b>آمار شبکه شما</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n"

            f"{ce('user')} سطح اول: "
            f"<code>{stats.tier_1_count:,} نفر</code>\n"

            f"{ce('users')} سطح دوم: "
            f"<code>{stats.tier_2_count:,} نفر</code>\n\n"

            f"{ce('money')} "
            "<b>پاداش‌های ثبت‌شده</b>\n"

            f"کل پاداش ثبت‌شده: "
            f"<code>{stats.registered_reward_toman:,} تومان</code>\n"

            f"انتقال داده‌شده به کیف پول: "
            f"<code>{stats.withdrawn_reward_toman:,} تومان</code>\n"

            f"{ce('wallet')} قابل برداشت: "
            f"<code>{stats.available_reward_toman:,} تومان</code>\n\n"

            "━━━━━━━━━━━━━━━━━━━━\n"

            f"{ce('success')} سطح اول: "
            f"<b>{REFERRAL_TIER_1_PERCENT:g}%</b>\n"

            f"{ce('bolt')} سطح دوم: "
            f"<b>{REFERRAL_TIER_2_PERCENT:g}%</b>\n\n"

            f"{ce('warning')} "
            "پاداش فقط زمانی در این صفحه نمایش داده می‌شود "
            "که کمیسیون خرید در دیتابیس ثبت شده باشد."
        )

        await safe_edit(
            callback,
            referral_text,
            get_referral_keyboard(
                can_withdraw=can_withdraw
            ),
        )

        await safe_answer(
            callback
        )

    except Exception:
        logger.exception(
            "Error in referral menu."
        )

        await safe_answer(
            callback,
            "❌ خطا در بارگذاری سیستم زیرمجموعه‌گیری.",
            show_alert=True,
        )


# ============================================================
# Referral List
# ============================================================

@router.callback_query(
    F.data == "referral_list"
)
async def callback_referral_list(
    callback: CallbackQuery,
) -> None:
    await _show_referral_list(
        callback,
        page=1,
    )


@router.callback_query(
    F.data.startswith(
        "referral_list_page_"
    )
)
async def callback_referral_list_page(
    callback: CallbackQuery,
) -> None:
    try:
        page = int(
            str(
                callback.data
            ).rsplit(
                "_",
                1,
            )[-1]
        )

    except (
        TypeError,
        ValueError,
    ):
        await safe_answer(
            callback,
            "❌ شماره صفحه معتبر نیست.",
            show_alert=True,
        )
        return

    await _show_referral_list(
        callback,
        page=max(
            1,
            page,
        ),
    )


async def _show_referral_list(
    callback: CallbackQuery,
    *,
    page: int,
) -> None:
    try:
        user_id = int(
            callback.from_user.id
        )

        members, total = (
            await get_referral_members(
                user_id,
                page=page,
            )
        )

        total_pages = max(
            1,
            (
                total
                + REFERRAL_LIST_PAGE_SIZE
                - 1
            )
            // REFERRAL_LIST_PAGE_SIZE,
        )

        page = max(
            1,
            min(
                page,
                total_pages,
            ),
        )

        # اگر page خارج از محدوده بود دوباره صفحه معتبر را بخوان.
        if (
            total > 0
            and not members
            and page <= total_pages
        ):
            members, total = (
                await get_referral_members(
                    user_id,
                    page=page,
                )
            )

        if not members:
            list_text = (
                f"{ce('users')} "
                "<b>لیست زیرمجموعه‌های شما</b>\n"
                "━━━━━━━━━━━━━━━━━━━━\n\n"

                f"{ce('list')} "
                "در حال حاضر زیرمجموعه‌ای برای حساب شما ثبت نشده است.\n\n"

                f"{ce('sparkles')} "
                "لینک دعوت خود را برای دوستانتان ارسال کنید."
            )

        else:
            lines = [
                (
                    f"{ce('users')} "
                    "<b>لیست زیرمجموعه‌های شما</b>"
                ),
                "━━━━━━━━━━━━━━━━━━━━",
                (
                    f"{ce('history')} صفحه "
                    f"<code>{page}</code> از "
                    f"<code>{total_pages}</code>"
                ),
                "",
            ]

            start_index = (
                page - 1
            ) * REFERRAL_LIST_PAGE_SIZE

            for index, member in enumerate(
                members,
                start=start_index + 1,
            ):
                display_name = (
                    f"@{member.username}"
                    if member.username
                    else (
                        member.full_name
                        or f"کاربر {member.user_id}"
                    )
                )

                reward_line = ""

                if (
                    member.level == 1
                    and member.reward_earned > 0
                ):
                    reward_line = (
                        "\n   "
                        f"{ce('money')} پاداش ثبت‌شده: "
                        f"<code>{member.reward_earned:,} تومان</code>"
                    )

                lines.append(
                    (
                        f"{index}. "
                        f"{ce('user')} "
                        f"<b>{escape(display_name)}</b>\n"
                        f"   سطح: <code>{member.level}</code>\n"
                        f"   UID: <code>{member.user_id}</code>"
                        f"{reward_line}"
                    )
                )

            list_text = "\n\n".join(
                lines
            )

        await safe_edit(
            callback,
            list_text,
            get_referral_list_keyboard(
                page=page,
                total_pages=total_pages,
            ),
        )

        await safe_answer(
            callback
        )

    except Exception:
        logger.exception(
            "Error in referral list."
        )

        await safe_answer(
            callback,
            "❌ خطا در دریافت لیست زیرمجموعه‌ها.",
            show_alert=True,
        )


# ============================================================
# Referral Tiers
# ============================================================

@router.callback_query(
    F.data == "referral_tiers"
)
async def callback_referral_tiers(
    callback: CallbackQuery,
) -> None:
    try:
        stats = await get_referral_stats(
            callback.from_user.id
        )

        tiers_text = (
            f"{ce('history')} "
            "<b>ساختار کمیسیون زیرمجموعه‌گیری</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"{ce('success')} <b>سطح اول</b>\n"
            "دعوت مستقیم شما\n"
            f"{ce('money')} کمیسیون تنظیم‌شده: "
            f"<b>{REFERRAL_TIER_1_PERCENT:g}%</b>\n"
            f"{ce('user')} اعضای فعلی: "
            f"<code>{stats.tier_1_count:,} نفر</code>\n\n"

            f"{ce('bolt')} <b>سطح دوم</b>\n"
            "کاربرانی که توسط زیرمجموعه‌های مستقیم شما دعوت شده‌اند\n"
            f"{ce('money')} کمیسیون تنظیم‌شده: "
            f"<b>{REFERRAL_TIER_2_PERCENT:g}%</b>\n"
            f"{ce('users')} اعضای فعلی: "
            f"<code>{stats.tier_2_count:,} نفر</code>\n\n"

            "━━━━━━━━━━━━━━━━━━━━\n"

            f"{ce('warning')} "
            "کمیسیون باید پس از تکمیل موفق سفارش در سیستم "
            "پاداش ثبت شود؛ این صفحه فقط داده ثبت‌شده را نمایش می‌دهد."
        )

        await safe_edit(
            callback,
            tiers_text,
            get_referral_back_keyboard(),
        )

        await safe_answer(
            callback
        )

    except Exception:
        logger.exception(
            "Error in referral tiers."
        )

        await safe_answer(
            callback,
            "❌ خطا در نمایش ساختار کمیسیون.",
            show_alert=True,
        )


# ============================================================
# Withdraw Referral
# ============================================================

@router.callback_query(
    F.data == "withdraw_referral"
)
async def callback_withdraw_referral(
    callback: CallbackQuery,
) -> None:
    try:
        stats = await get_referral_stats(
            callback.from_user.id
        )

        can_withdraw = (
            stats.available_reward_toman
            >= MIN_REFERRAL_WITHDRAW_TOMAN
        )

        remaining = max(
            0,
            MIN_REFERRAL_WITHDRAW_TOMAN
            - stats.available_reward_toman,
        )

        if can_withdraw:
            status_text = (
                f"{ce('success')} "
                "<b>امکان انتقال به کیف پول فعال است.</b>"
            )

        else:
            status_text = (
                f"{ce('warning')} "
                "برای فعال شدن برداشت، "
                f"<code>{remaining:,} تومان</code> "
                "پاداش دیگر نیاز دارید."
            )

        withdraw_text = (
            f"{ce('money')} "
            "<b>برداشت پاداش زیرمجموعه‌گیری</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"{ce('wallet')} موجودی قابل برداشت:\n"
            f"<code>{stats.available_reward_toman:,} تومان</code>\n\n"

            f"{ce('pin')} حداقل برداشت:\n"
            f"<code>{MIN_REFERRAL_WITHDRAW_TOMAN:,} تومان</code>\n\n"

            f"{ce('history')} برداشت‌های قبلی:\n"
            f"<code>{stats.withdrawn_reward_toman:,} تومان</code>\n\n"

            "━━━━━━━━━━━━━━━━━━━━\n"
            f"{status_text}"
        )

        await safe_edit(
            callback,
            withdraw_text,
            get_referral_withdraw_keyboard(
                can_withdraw=can_withdraw
            ),
        )

        await safe_answer(
            callback
        )

    except Exception:
        logger.exception(
            "Error in referral withdrawal page."
        )

        await safe_answer(
            callback,
            "❌ خطا در دریافت موجودی پاداش.",
            show_alert=True,
        )


@router.callback_query(
    F.data == "referral_withdraw_confirm"
)
async def callback_confirm_referral_withdraw(
    callback: CallbackQuery,
) -> None:
    """
    انتقال اتمیک کل پاداش قابل برداشت به کیف پول اصلی.
    """
    try:
        await safe_answer(
            callback,
            "⏳ در حال انتقال پاداش..."
        )

        result = await withdraw_referral_to_wallet(
            callback.from_user.id
        )

        if not result.success:
            if (
                result.reason
                == "below_minimum"
            ):
                text = (
                    f"{ce('warning')} "
                    "<b>برداشت انجام نشد.</b>\n\n"
                    "موجودی پاداش شما به حداقل برداشت نرسیده است.\n\n"
                    f"موجودی فعلی: "
                    f"<code>{result.amount:,} تومان</code>\n"
                    f"حداقل: "
                    f"<code>{MIN_REFERRAL_WITHDRAW_TOMAN:,} تومان</code>"
                )

            elif (
                result.reason
                == "user_not_found"
            ):
                text = (
                    f"{ce('error')} "
                    "<b>حساب کاربری در دیتابیس پیدا نشد.</b>"
                )

            elif (
                result.reason
                == "wallet_balance_column_missing"
            ):
                text = (
                    f"{ce('error')} "
                    "<b>کیف پول تومانی در دیتابیس قابل شناسایی نیست.</b>"
                )

            else:
                text = (
                    f"{ce('error')} "
                    "<b>انتقال پاداش انجام نشد.</b>\n\n"
                    "لطفاً کمی بعد دوباره تلاش کنید."
                )

            await safe_edit(
                callback,
                text,
                get_referral_back_keyboard(),
            )

            return

        success_text = (
            f"{ce('success')} "
            "<b>پاداش به کیف پول منتقل شد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"{ce('money')} مبلغ انتقال:\n"
            f"<code>{result.amount:,} تومان</code>\n\n"

            f"{ce('wallet')} موجودی قبل:\n"
            f"<code>{result.balance_before:,} تومان</code>\n\n"

            f"{ce('wallet')} موجودی جدید:\n"
            f"<code>{result.balance_after:,} تومان</code>\n\n"

            f"{ce('list')} شناسه انتقال:\n"
            f"<code>#{result.withdrawal_id}</code>\n\n"

            "━━━━━━━━━━━━━━━━━━━━\n"
            f"{ce('success')} "
            "تراکنش با موفقیت ثبت شد."
        )

        await safe_edit(
            callback,
            success_text,
            get_referral_back_keyboard(),
        )

    except Exception:
        logger.exception(
            "Unexpected referral withdrawal error."
        )

        await safe_answer(
            callback,
            "❌ خطا در انتقال پاداش.",
            show_alert=True,
        )


# ============================================================
# Referral Guide
# ============================================================

@router.callback_query(
    F.data == "referral_guide"
)
async def callback_referral_guide(
    callback: CallbackQuery,
) -> None:
    try:
        guide_text = (
            f"{ce('list')} "
            "<b>راهنمای سیستم دعوت دوستان</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"{ce('sparkles')} <b>۱. دریافت لینک اختصاصی</b>\n"
            "از صفحه رفرال لینک اختصاصی خود را دریافت کنید.\n\n"

            f"{ce('users')} <b>۲. دعوت دوستان</b>\n"
            "لینک را برای دوستان خود ارسال کنید.\n\n"

            f"{ce('success')} <b>۳. ثبت زیرمجموعه</b>\n"
            "کاربر باید برای اولین بار با لینک شما وارد ربات شود "
            "تا رابطه دعوت در دیتابیس ثبت شود.\n\n"

            f"{ce('money')} <b>۴. دریافت کمیسیون</b>\n"
            f"سطح اول: <b>{REFERRAL_TIER_1_PERCENT:g}%</b>\n"
            f"سطح دوم: <b>{REFERRAL_TIER_2_PERCENT:g}%</b>\n\n"

            f"{ce('wallet')} <b>۵. انتقال پاداش</b>\n"
            "پس از رسیدن پاداش ثبت‌شده به حداقل "
            f"<code>{MIN_REFERRAL_WITHDRAW_TOMAN:,} تومان</code>، "
            "امکان انتقال آن به کیف پول تومانی فعال می‌شود.\n\n"

            f"{ce('warning')} "
            "ثبت کمیسیون باید فقط بعد از تکمیل موفق سفارش "
            "انجام شود تا سفارش ردشده یا Refund‌شده پاداش ایجاد نکند."
        )

        await safe_edit(
            callback,
            guide_text,
            get_referral_back_keyboard(),
        )

        await safe_answer(
            callback
        )

    except Exception:
        logger.exception(
            "Error in referral guide."
        )

        await safe_answer(
            callback,
            "❌ خطا در نمایش راهنمای رفرال.",
            show_alert=True,
        )


# ============================================================
# Share Referral
# ============================================================

@router.callback_query(
    F.data == "referral_share"
)
async def callback_referral_share(
    callback: CallbackQuery,
) -> None:
    try:
        referral_link = (
            await build_referral_link(
                callback
            )
        )

        if not referral_link:
            await safe_answer(
                callback,
                "❌ نام کاربری ربات در دسترس نیست.",
                show_alert=True,
            )
            return

        share_text = (
            f"{ce('sparkles')} "
            "<b>لینک دعوت اختصاصی شما</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"<code>{escape(referral_link)}</code>\n\n"

            f"{ce('users')} "
            "این لینک را برای دوستان خود ارسال کنید.\n\n"

            f"{ce('money')} "
            "پاداش خریدهای واجد شرایط پس از ثبت کمیسیون "
            "در حساب رفرال شما نمایش داده می‌شود."
        )

        await safe_edit(
            callback,
            share_text,
            get_share_keyboard(
                referral_link
            ),
        )

        await safe_answer(
            callback
        )

    except Exception:
        logger.exception(
            "Error in referral share."
        )

        await safe_answer(
            callback,
            "❌ خطا در ساخت لینک دعوت.",
            show_alert=True,
        )


# ============================================================
# Noop Compatibility
# ============================================================

@router.callback_query(
    F.data == "noop"
)
async def referral_noop(
    callback: CallbackQuery,
) -> None:
    """
    اگر Router عمومی دیگری noop را نگرفت، spinner متوقف شود.
    """
    await safe_answer(
        callback
    )


# ============================================================
# Exports
# ============================================================

__all__ = [
    "router",
    "ReferralStats",
    "ReferralMember",
    "ReferralWithdrawalResult",
    "ensure_referral_schema",
    "get_referral_stats",
    "get_referral_members",
    "withdraw_referral_to_wallet",
    "make_button",
    "get_referral_keyboard",
    "get_referral_back_keyboard",
]