# handlers/user/shop.py

from __future__ import annotations

import asyncio
import logging
import os
import sqlite3
from datetime import datetime
from decimal import Decimal, ROUND_HALF_UP
from html import escape
from typing import Any, Final, Optional
from zoneinfo import ZoneInfo

from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError
from aiogram.fsm.context import FSMContext
from aiogram.types import (
    CallbackQuery,
    FSInputFile,
    InlineKeyboardButton,
    InlineKeyboardMarkup,
    Message,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder

# Telegram Bot API 10.2 / aiogram 3.30+
# Rich Tables are imported conditionally so older aiogram versions do not
# crash the whole bot at startup.
try:
    from aiogram.types import (
        InputRichBlockFooter,
        InputRichBlockParagraph,
        InputRichBlockSectionHeading,
        InputRichBlockTable,
        InputRichMessage,
        RichBlockTableCell,
        RichTextCustomEmoji,
    )

    RICH_PRICE_TABLE_AVAILABLE = True

except ImportError:
    InputRichBlockFooter = None
    InputRichBlockParagraph = None
    InputRichBlockSectionHeading = None
    InputRichBlockTable = None
    InputRichMessage = None
    RichBlockTableCell = None
    RichTextCustomEmoji = None

    RICH_PRICE_TABLE_AVAILABLE = False


from core.config import settings
from core.ui_emojis import ce, emoji_id, premiumize_html

from keyboards.user_kb import (
    get_products_menu_keyboard,
    get_services_packages_keyboard,
    get_user_main_menu,
    get_wallet_menu_keyboard,
)

try:
    from keyboards.admin_kb import get_owner_order_keyboard
except ImportError:
    get_owner_order_keyboard = None

from services.product_pricing_service import (
    ProductPriceNotConfigured,
    ProductPricingError,
    ProductPricingService,
    ProductRateUnavailable,
)
from services.iran_usdt_rate_service import (
    IranUsdtAllProvidersFailed,
    IranUsdtRateService,
)
from services.order_receipt_renderer import (
    CompletedOrderReceipt,
    render_completed_order_receipt,
)

from services.order_service import (
    Order,
    OrderService,
    STATUS_COMPLETED,
    STATUS_FAILED_REFUNDED,
    STATUS_CANCELLED_BY_USER,
    STATUS_INSUFFICIENT_BALANCE,
    STATUS_OWNER_PROCESSING,
    STATUS_PENDING_OWNER,
    STATUS_PRICE_CHANGED,
    STATUS_PROVIDER_PROCESSING,
    STATUS_PROVIDER_UNKNOWN,
    STATUS_REJECTED_BY_OWNER,
)
from states.order import OrderStates
from utils.order_datetime import (
    format_order_datetime,
    now_tehran_text,
    order_event_time,
)


logger = logging.getLogger(__name__)

router = Router(
    name="user_shop"
)


# ============================================================
# Products
# ============================================================

PACKAGE_NAMES: Final[dict[str, str]] = {
    # Premium
    "prem_1m":
        "اشتراک پرمیوم ۱ ماهه تلگرام",

    "prem_3m":
        "اشتراک پرمیوم ۳ ماهه تلگرام",

    "prem_6m":
        "اشتراک پرمیوم ۶ ماهه تلگرام",

    "prem_1y":
        "اشتراک پرمیوم ۱ ساله تلگرام",

    # Stars
    "stars_50":
        "۵۰ عدد استارز تلگرام",

    "stars_100":
        "۱۰۰ عدد استارز تلگرام",

    "stars_250":
        "۲۵۰ عدد استارز تلگرام",

    "stars_500":
        "۵۰۰ عدد استارز تلگرام",

    "stars_1000":
        "۱۰۰۰ عدد استارز تلگرام",

    "stars_5000":
        "۵۰۰۰ عدد استارز تلگرام",
}


PREMIUM_MONTHS: Final[
    dict[str, int]
] = {
    "prem_1m": 1,
    "prem_3m": 3,
    "prem_6m": 6,
    "prem_1y": 12,
}


STARS_AMOUNTS: Final[
    dict[str, int]
] = {
    "stars_50": 50,
    "stars_100": 100,
    "stars_250": 250,
    "stars_500": 500,
    "stars_1000": 1000,
    "stars_5000": 5000,
}


MIN_CUSTOM_STARS: Final[int] = 50
MAX_CUSTOM_STARS: Final[int] = 10_000


# ============================================================
# Database Path
# ============================================================

def resolve_database_path(
    db: Any = None,
) -> str:
    if db is not None:
        for attribute in (
            "db_path",
            "database_path",
            "path",
        ):
            value = getattr(
                db,
                attribute,
                None,
            )

            if value:
                return str(value)

    return str(
        getattr(
            settings,
            "DB_PATH",
            None,
        )
        or getattr(
            settings,
            "DATABASE_PATH",
            None,
        )
        or "matrix_bot.db"
    )


# ============================================================
# Manual Product Price + Live USDT/Toman Preview
# ============================================================

PREMIUM_MENU_ORDER: Final[
    tuple[str, ...]
] = (
    "prem_1m",
    "prem_3m",
    "prem_6m",
    "prem_1y",
)

STARS_MENU_ORDER: Final[
    tuple[str, ...]
] = (
    "stars_50",
    "stars_100",
    "stars_250",
    "stars_500",
    "stars_1000",
    "stars_5000",
)


# ============================================================
# Telegram Rich Price List
# ============================================================

PRICE_LIST_ORDER: Final[
    tuple[str, ...]
] = (
    # Unit Stars first, then actual purchasable Stars packages.
    "stars_unit",
    "stars_50",
    "stars_100",
    "stars_250",
    "stars_500",
    "stars_1000",
    "stars_5000",

    # Premium packages.
    "prem_1m",
    "prem_3m",
    "prem_6m",
    "prem_1y",
)


PRICE_LIST_LABELS: Final[
    dict[str, str]
] = {
    "stars_unit":
        "هر 1 Stars",

    "stars_50":
        "50 Stars",

    "stars_100":
        "100 Stars",

    "stars_250":
        "250 Stars",

    "stars_500":
        "500 Stars",

    "stars_1000":
        "1,000 Stars",

    "stars_5000":
        "5,000 Stars",

    "prem_1m":
        "پرمیوم 1 ماهه",

    "prem_3m":
        "پرمیوم 3 ماهه",

    "prem_6m":
        "پرمیوم 6 ماهه",

    "prem_1y":
        "پرمیوم 12 ماهه",
}


PRICE_RICH_EMOJI: Final[
    dict[str, tuple[str, str]]
] = {
    # ID + its real alternative emoji.
    "list": (
        "5431577498364158238",
        "📜",
    ),
    "stars": (
        "5843892082447487088",
        "⭐️",
    ),
    "premium": (
        "5823537588186647980",
        "💎",
    ),
    "money": (
        "5990147899403539264",
        "💵",
    ),
    "bolt": (
        "5449683594425410231",
        "⚡️",
    ),
    "history": (
        "5985774024968379294",
        "📊",
    ),
}


PRICE_LIST_TEHRAN: Final = ZoneInfo(
    "Asia/Tehran"
)


def _price_rich_emoji(
    key: str,
):
    """
    Telegram RichTextCustomEmoji with the exact alternative emoji
    associated with the custom-emoji document.
    """

    if (
        not RICH_PRICE_TABLE_AVAILABLE
        or RichTextCustomEmoji is None
    ):
        return PRICE_RICH_EMOJI[
            key
        ][1]

    custom_emoji_id, alternative_text = (
        PRICE_RICH_EMOJI[
            key
        ]
    )

    return RichTextCustomEmoji(
        custom_emoji_id=custom_emoji_id,
        alternative_text=alternative_text,
    )


def _price_product_rich_text(
    package_id: str,
    title: str,
):
    icon_key = (
        "stars"
        if package_id.startswith(
            "stars"
        )
        else "premium"
    )

    return [
        _price_rich_emoji(
            icon_key
        ),
        " ",
        title,
    ]


def _price_list_tehran_timestamp() -> str:
    """
    Exact price-list generation timestamp in Tehran, including seconds.
    """

    return datetime.now(
        PRICE_LIST_TEHRAN
    ).strftime(
        "%Y/%m/%d - %H:%M:%S"
    )



def build_price_list_keyboard() -> InlineKeyboardMarkup:
    """
    Inline keyboard shown directly under Telegram Rich Message.
    """

    builder = InlineKeyboardBuilder()

    builder.row(
        _button(
            text="مشاهده محصولات",
            callback_data="buy_product",
            style="success",
            icon_key="shop",
        )
    )

    builder.row(
        _button(
            text="بروزرسانی قیمت‌ها",
            callback_data="price_list",
            style="primary",
            icon_key="money",
        ),
        _button(
            text="بازگشت",
            callback_data="main_menu",
            style="primary",
            icon_key="back",
        ),
    )

    return builder.as_markup()


async def get_public_price_list(
    *,
    db: Any = None,
) -> tuple[
    list[tuple[str, str, int]],
    Decimal,
    str,
]:
    """
    Calculates the whole public price list with ONE USDT/Toman quote.

    Product prices stay compatible with either pricing implementation:
      - exact Stars package USDT prices
      - per-Star pricing via stars_unit

    because ProductPricingService.get_package_base_usdt() is used for every
    package.
    """

    pricing = ProductPricingService(
        resolve_database_path(
            db
        )
    )

    await pricing.ensure_schema()

    rates = IranUsdtRateService(
        timeout=4.0,
        cache_ttl=15.0,
        stale_ttl=300.0,
    )

    try:
        try:
            rate_quote = await rates.get_quote(
                force_refresh=False,
                allow_stale=True,
            )

        except IranUsdtAllProvidersFailed as exc:
            raise ProductRateUnavailable(
                "USDT/Toman rate is unavailable."
            ) from exc

        usdt_toman = Decimal(
            str(
                rate_quote.rate_toman
            )
        )

        rows: list[
            tuple[
                str,
                str,
                int,
            ]
        ] = []

        for package_id in PRICE_LIST_ORDER:
            try:
                base_usdt = (
                    await pricing.get_package_base_usdt(
                        package_id
                    )
                )

            except ProductPriceNotConfigured:
                # Public table only displays products whose price is configured.
                continue

            except ProductPricingError:
                continue

            final_toman = int(
                (
                    Decimal(
                        str(
                            base_usdt
                        )
                    )
                    * usdt_toman
                ).quantize(
                    Decimal("1"),
                    rounding=ROUND_HALF_UP,
                )
            )

            if final_toman <= 0:
                continue

            rows.append(
                (
                    package_id,
                    PRICE_LIST_LABELS.get(
                        package_id,
                        package_id,
                    ),
                    final_toman,
                )
            )

        return (
            rows,
            usdt_toman,
            str(
                rate_quote.source
                or "نامشخص"
            ),
        )

    finally:
        await rates.close()


def build_rich_price_message(
    *,
    rows: list[
        tuple[
            str,
            str,
            int,
        ]
    ],
    usdt_toman: Decimal,
    rate_source: str,
):
    """
    Native Telegram Rich Message price table.

    - All visible emoji inside the Rich Message use RichTextCustomEmoji.
    - The exchange/provider name is intentionally NOT displayed.
    - Timestamp is generated explicitly in Asia/Tehran with seconds.
    """

    if not RICH_PRICE_TABLE_AVAILABLE:
        return None

    table_cells = [
        [
            RichBlockTableCell(
                align="right",
                valign="middle",
                text=[
                    _price_rich_emoji(
                        "list"
                    ),
                    " محصول",
                ],
                is_header=True,
            ),
            RichBlockTableCell(
                align="center",
                valign="middle",
                text=[
                    _price_rich_emoji(
                        "money"
                    ),
                    " قیمت",
                ],
                is_header=True,
            ),
        ]
    ]

    for (
        package_id,
        title,
        toman,
    ) in rows:
        table_cells.append(
            [
                RichBlockTableCell(
                    align="right",
                    valign="middle",
                    text=(
                        _price_product_rich_text(
                            package_id,
                            title,
                        )
                    ),
                ),
                RichBlockTableCell(
                    align="center",
                    valign="middle",
                    text=f"{int(toman):,}",
                ),
            ]
        )

    timestamp = (
        _price_list_tehran_timestamp()
    )

    return InputRichMessage(
        blocks=[
            InputRichBlockSectionHeading(
                text=[
                    _price_rich_emoji(
                        "list"
                    ),
                    " لیست قیمت‌ها",
                ],
                size=2,
            ),
            InputRichBlockParagraph(
                text=[
                    _price_rich_emoji(
                        "bolt"
                    ),
                    " لیست قیمت لحظه‌ای محصولات فروشگاه",
                ],
            ),
            InputRichBlockTable(
                cells=table_cells,
                is_bordered=True,
                is_striped=True,
                caption=[
                    _price_rich_emoji(
                        "money"
                    ),
                    " قیمت لحظه‌ای محصولات",
                ],
            ),
            InputRichBlockParagraph(
                text=[
                    _price_rich_emoji(
                        "bolt"
                    ),
                    " قیمت‌ها بر اساس نرخ لحظه‌ای تتر محاسبه می‌شوند.",
                ],
            ),
            InputRichBlockFooter(
                text=[
                    _price_rich_emoji(
                        "money"
                    ),
                    (
                        " نرخ تتر: "
                        f"{int(usdt_toman):,} تومان"
                    ),
                    "   •   ",
                    _price_rich_emoji(
                        "history"
                    ),
                    (
                        " آخرین بروزرسانی: "
                        f"{timestamp} "
                        "(به وقت تهران)"
                    ),
                ],
            ),
        ],
        is_rtl=True,
    )


def build_price_list_fallback_text(
    *,
    rows: list[
        tuple[
            str,
            str,
            int,
        ]
    ],
    usdt_toman: Decimal,
    rate_source: str,
) -> str:
    """
    Fallback for old aiogram builds.

    Source/provider name is intentionally hidden here as well.
    """

    timestamp = (
        _price_list_tehran_timestamp()
    )

    lines = [
        f"{ce('list')} <b>لیست قیمت‌ها</b>",
        "━━━━━━━━━━━━━━━━━━━━",
        "",
        (
            f"{ce('bolt')} "
            "<b>قیمت لحظه‌ای محصولات فروشگاه</b>"
        ),
        "",
    ]

    for (
        package_id,
        title,
        toman,
    ) in rows:
        icon_key = (
            "stars"
            if package_id.startswith(
                "stars"
            )
            else "premium"
        )

        lines.append(
            (
                f"{ce(icon_key)} "
                f"{escape(title)} — "
                f"<code>{int(toman):,}</code> تومان"
            )
        )

    lines += [
        "",
        "━━━━━━━━━━━━━━━━━━━━",
        (
            f"{ce('money')} "
            "نرخ تتر: "
            f"<code>{int(usdt_toman):,}</code> تومان"
        ),
        (
            f"{ce('history')} "
            "آخرین بروزرسانی: "
            f"<code>{escape(timestamp)}</code>"
        ),
        "به وقت تهران",
    ]

    return "\n".join(
        lines
    )


def _package_short_title(
    package_id: str,
) -> str:
    titles = {
        "prem_1m":
            "Premium ۱ ماهه",

        "prem_3m":
            "Premium ۳ ماهه",

        "prem_6m":
            "Premium ۶ ماهه",

        "prem_1y":
            "Premium ۱ ساله",

        "stars_50":
            "50 Stars",

        "stars_100":
            "100 Stars",

        "stars_250":
            "250 Stars",

        "stars_500":
            "500 Stars",

        "stars_1000":
            "1,000 Stars",

        "stars_5000":
            "5,000 Stars",
    }

    if package_id.startswith(
        "custom_stars_"
    ):
        try:
            amount = int(
                package_id[
                    len(
                        "custom_stars_"
                    ):
                ]
            )

            return (
                f"{amount:,} Stars"
            )

        except ValueError:
            pass

    return titles.get(
        package_id,
        PACKAGE_NAMES.get(
            package_id,
            package_id,
        ),
    )


async def get_manual_product_quote(
    package_id: str,
    *,
    db: Any = None,
    force_refresh_rate: bool = False,
):
    service = ProductPricingService(
        resolve_database_path(
            db
        )
    )

    return await service.quote_toman(
        package_id,
        force_refresh_rate=force_refresh_rate,
        allow_stale_rate=True,
    )


async def get_live_preview_prices(
    package_ids: tuple[str, ...]
    | list[str],
    *,
    db: Any = None,
) -> tuple[
    dict[str, Optional[int]],
    Optional[str],
]:
    """
    Compatibility helper used by shop UI.

    Product base prices are configured by admin in USDT.
    Only USDT/Toman is fetched live.
    """
    prices: dict[
        str,
        Optional[int],
    ] = {}

    errors: list[
        str
    ] = []

    for package_id in package_ids:
        package_id = str(
            package_id
        )

        try:
            quote = await get_manual_product_quote(
                package_id,
                db=db,
                force_refresh_rate=False,
            )

            prices[
                package_id
            ] = int(
                quote.final_toman
            )

        except Exception as exc:
            prices[
                package_id
            ] = None

            errors.append(
                f"{package_id}: {exc}"
            )

            logger.warning(
                "Shop preview unavailable | "
                "package=%s error=%s",
                package_id,
                exc,
            )

    return (
        prices,
        (
            " | ".join(
                errors[:3]
            )
            if errors
            else None
        ),
    )


async def get_live_preview_price(
    package_id: str,
    *,
    db: Any = None,
) -> Optional[int]:
    try:
        quote = await get_manual_product_quote(
            package_id,
            db=db,
            force_refresh_rate=False,
        )

        return int(
            quote.final_toman
        )

    except (
        ProductPriceNotConfigured,
        ProductRateUnavailable,
        ProductPricingError,
    ) as exc:
        logger.warning(
            "Shop preview unavailable | "
            "package=%s error=%s",
            package_id,
            exc,
        )

        return None

    except Exception:
        logger.exception(
            "Unexpected shop preview failure | "
            "package=%s",
            package_id,
        )

        return None


def build_live_packages_keyboard(
    service: str,
    prices: dict[
        str,
        Optional[int],
    ],
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    if service == "premium":
        package_ids = (
            PREMIUM_MENU_ORDER
        )

        icon = "💎"

    elif service == "stars":
        package_ids = (
            STARS_MENU_ORDER
        )

        icon = "⭐"

    else:
        return build_back_keyboard(
            "buy_product"
        )

    for package_id in package_ids:
        price = prices.get(
            package_id
        )

        if (
            isinstance(
                price,
                int,
            )
            and price > 0
        ):
            price_text = (
                f"{price:,} تومان"
            )

            style = "primary"

        else:
            price_text = (
                "قیمت موقتاً ناموجود"
            )

            style = "danger"

        builder.row(
            InlineKeyboardButton(
                text=(
                    f"{icon} "
                    f"{_package_short_title(package_id)}"
                    f" — {price_text}"
                ),
                callback_data=package_id,
                style=style,
            )
        )

    if service == "stars":
        builder.row(
            InlineKeyboardButton(
                text="⭐ تعداد دلخواه Stars",
                callback_data="custom_stars",
                style="success",
            )
        )

    builder.row(
        InlineKeyboardButton(
            text="بازگشت",
            callback_data="buy_product",
            style="danger",
        )
    )

    return builder.as_markup()


# ============================================================
# Financial Display Helpers
# ============================================================

def _compact_decimal(
    value: Any,
    *,
    max_decimals: int = 8,
) -> str:
    """نمایش Decimal بدون صفرهای اضافه."""
    try:
        number = Decimal(str(value))
    except Exception:
        return str(value)

    if not number.is_finite():
        return str(value)

    raw = format(number, "f")

    if "." in raw:
        raw = raw.rstrip("0").rstrip(".")

    return raw or "0"


def _format_usdt(
    value: Any,
) -> str:
    return _compact_decimal(
        value,
        max_decimals=8,
    )


def _format_toman_rate(
    value: Any,
) -> str:
    try:
        number = Decimal(str(value))
        rounded = int(
            number.quantize(
                Decimal("1"),
                rounding=ROUND_HALF_UP,
            )
        )
        return f"{rounded:,}"
    except Exception:
        return str(value)


def _stars_amount_from_package(
    package_id: str,
) -> Optional[int]:
    package_id = str(package_id or "")

    if package_id in STARS_AMOUNTS:
        return int(
            STARS_AMOUNTS[package_id]
        )

    prefix = "custom_stars_"

    if package_id.startswith(prefix):
        try:
            amount = int(
                package_id[len(prefix):]
            )
            return amount if amount > 0 else None
        except ValueError:
            return None

    return None


def build_price_breakdown(
    package_id: str,
    quote: Any,
    *,
    charged_toman: int,
) -> str:
    """
    نمایش شفاف قیمت نهایی:
    - قیمت هر Stars برای سفارش Stars
    - قیمت کل USDT
    - نرخ USDT/Toman
    - منبع نرخ
    - فرمول دقیق محاسبه
    - مبلغ واقعی کسرشده
    """

    base_usdt = Decimal(
        str(quote.base_usdt)
    )
    usdt_toman = Decimal(
        str(quote.usdt_toman)
    )

    raw_toman = (
        base_usdt
        * usdt_toman
    )

    calculated_toman = int(
        raw_toman.quantize(
            Decimal("1"),
            rounding=ROUND_HALF_UP,
        )
    )

    base_text = _format_usdt(
        base_usdt
    )
    rate_text = _format_toman_rate(
        usdt_toman
    )

    source = escape(
        str(
            getattr(
                quote,
                "rate_source",
                "",
            )
            or "نامشخص"
        )
    )

    stars_amount = (
        _stars_amount_from_package(
            package_id
        )
    )

    lines = [
        "💵 <b>جزئیات قیمت</b>",
        "━━━━━━━━━━━━━━━━━━━━",
    ]

    if stars_amount:
        unit_usdt = (
            base_usdt
            / Decimal(stars_amount)
        )

        unit_toman = int(
            (
                raw_toman
                / Decimal(stars_amount)
            ).quantize(
                Decimal("1"),
                rounding=ROUND_HALF_UP,
            )
        )

        lines.extend(
            [
                f"⭐ تعداد: <code>{stars_amount:,} Stars</code>",
                (
                    "💲 قیمت هر Star: "
                    f"<code>{_format_usdt(unit_usdt)} USDT</code>"
                ),
                (
                    "💰 معادل هر Star: "
                    f"<code>{unit_toman:,} تومان</code>"
                ),
                (
                    "📦 قیمت کل پکیج: "
                    f"<code>{base_text} USDT</code>"
                ),
            ]
        )
    else:
        lines.append(
            (
                "📦 قیمت پایه پکیج: "
                f"<code>{base_text} USDT</code>"
            )
        )

    lines.extend(
        [
            (
                "💱 نرخ USDT: "
                f"<code>{rate_text} تومان</code>"
            ),
            (
                "🔌 منبع نرخ: "
                f"<code>{source}</code>"
            ),
            "",
            "🧮 <b>محاسبه قیمت</b>",
            (
                f"<code>{base_text} × {rate_text}"
                f" = {calculated_toman:,} تومان</code>"
            ),
            "",
            (
                "💳 <b>مبلغ نهایی کسرشده:</b> "
                f"<code>{int(charged_toman):,} تومان</code>"
            ),
        ]
    )

    if int(charged_toman) != calculated_toman:
        difference = (
            int(charged_toman)
            - calculated_toman
        )

        sign = "+" if difference > 0 else ""

        lines.append(
            (
                "⚠️ اختلاف با محاسبه نرخ: "
                f"<code>{sign}{difference:,} تومان</code>"
            )
        )

    return "\n".join(lines)


# ============================================================
# Wallet Read
# ============================================================

def _get_user_balance_sync(
    db_path: str,
    user_id: int,
) -> int:
    conn = sqlite3.connect(
        db_path,
        timeout=10,
    )

    conn.row_factory = (
        sqlite3.Row
    )

    try:
        columns = {
            row["name"]
            for row
            in conn.execute(
                "PRAGMA table_info(users)"
            ).fetchall()
        }

        balance_column = (
            "balance_toman"
            if "balance_toman"
            in columns
            else (
                "balance"
                if "balance"
                in columns
                else None
            )
        )

        if not balance_column:
            return 0

        row = conn.execute(
            f"""
            SELECT
                {balance_column}
                AS balance
            FROM users
            WHERE user_id = ?
            LIMIT 1
            """,
            (
                int(user_id),
            ),
        ).fetchone()

        if not row:
            return 0

        return max(
            0,
            int(
                row["balance"]
                or 0
            ),
        )

    finally:
        conn.close()


async def get_user_balance(
    user_id: int,
    db: Any = None,
) -> int:
    return await asyncio.to_thread(
        _get_user_balance_sync,
        resolve_database_path(
            db
        ),
        int(user_id),
    )


def insufficient_balance_text(
    *,
    price: int,
    balance: int,
) -> str:
    price = max(
        0,
        int(
            price
        ),
    )

    balance = max(
        0,
        int(
            balance
        ),
    )

    shortage = max(
        0,
        price - balance,
    )

    return (
        f"{ce('warning')} "
        "<b>موجودی کیف پول برای ثبت این سفارش کافی نیست</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"{ce('money')} مبلغ موردنیاز\n"
        f"<code>{price:,} تومان</code>\n\n"

        f"{ce('wallet')} موجودی کیف پول\n"
        f"<code>{balance:,} تومان</code>\n\n"

        f"{ce('error')} مبلغ کسری\n"
        f"<code>{shortage:,} تومان</code>\n\n"

        "برای ادامه، ابتدا کیف پول را حداقل به اندازه مبلغ کسری "
        "شارژ کنید. تا قبل از کافی شدن موجودی، "
        "<b>هیچ درخواستی برای مالک ثبت نمی‌شود.</b>"
    )


# ============================================================
# OrderService Resolver
# ============================================================

async def resolve_order_service(
    order_service: Optional[
        OrderService
    ],
    db: Any = None,
) -> OrderService:
    service = order_service

    if service is None:
        service = OrderService(
            db_path=(
                resolve_database_path(
                    db
                )
            )
        )

    await service.ensure_schema()

    return service


# ============================================================
# Admin Access
# ============================================================

def get_admin_ids() -> frozenset[int]:
    method = getattr(
        settings,
        "get_admin_list",
        None,
    )

    if callable(
        method
    ):
        try:
            return frozenset(
                int(user_id)
                for user_id
                in method()
                if int(user_id) > 0
            )

        except Exception:
            logger.exception(
                "Could not read admin list."
            )

    raw = str(
        getattr(
            settings,
            "ADMIN_IDS",
            "",
        )
        or ""
    )

    result: set[int] = set()

    for item in raw.split(","):
        item = item.strip()

        if not item:
            continue

        try:
            user_id = int(
                item
            )

        except ValueError:
            continue

        if user_id > 0:
            result.add(
                user_id
            )

    return frozenset(
        result
    )


def is_admin(
    user_id: int,
) -> bool:
    return (
        int(user_id)
        in get_admin_ids()
    )


# ============================================================
# Target Validation
# ============================================================

def normalize_username(
    raw: str,
) -> Optional[str]:
    value = str(
        raw
        or ""
    ).strip()

    if value.startswith("@"):
        value = value[1:]

    if (
        len(value) < 4
        or len(value) > 32
    ):
        return None

    allowed = (
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "0123456789_"
    )

    if any(
        char not in allowed
        for char in value
    ):
        return None

    return value


# ============================================================
# UI
# ============================================================

def _button(
    *,
    text: str,
    callback_data: str,
    style: str = "primary",
    icon_key: str = "sparkles",
) -> InlineKeyboardButton:
    return InlineKeyboardButton(
        text=text,
        callback_data=callback_data,
        style=style,
        icon_custom_emoji_id=(
            emoji_id(
                icon_key
            )
        ),
    )


def build_back_keyboard(
    callback_data: str = "buy_product",
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        _button(
            text="بازگشت",
            callback_data=(
                callback_data
            ),
            style="primary",
            icon_key="back",
        )
    )

    return builder.as_markup()


def build_target_selection_keyboard(
    *,
    back_callback: str,
) -> InlineKeyboardMarkup:
    """
    مقصد سفارش.

    دکمه «خودم» عمداً بدون style ساخته می‌شود تا ظاهر پیش‌فرض
    شیشه‌ای/شفاف Inline Keyboard تلگرام را داشته باشد.
    """

    builder = InlineKeyboardBuilder()

    builder.row(
        InlineKeyboardButton(
            text="خودم",
            callback_data="shop_target_self",
        )
    )

    builder.row(
        _button(
            text="بازگشت",
            callback_data=back_callback,
            style="danger",
            icon_key="back",
        )
    )

    return builder.as_markup()


def build_purchase_keyboard() -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        _button(
            text="ثبت درخواست خرید",
            callback_data=(
                "finalize_purchase"
            ),
            style="success",
            icon_key="success",
        )
    )

    builder.row(
        _button(
            text="شارژ کیف پول",
            callback_data="charge_fiat",
            style="primary",
            icon_key="wallet",
        ),
        _button(
            text="لغو سفارش",
            callback_data="cancel_shop",
            style="danger",
            icon_key="error",
        ),
    )

    return builder.as_markup()


def build_insufficient_balance_keyboard(
    *,
    back_callback: str = "buy_product",
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        _button(
            text="شارژ کیف پول",
            callback_data="charge_fiat",
            style="success",
            icon_key="wallet",
        )
    )

    builder.row(
        _button(
            text="بازگشت",
            callback_data=back_callback,
            style="primary",
            icon_key="back",
        ),
        _button(
            text="لغو",
            callback_data="cancel_shop",
            style="danger",
            icon_key="error",
        ),
    )

    return builder.as_markup()


def build_result_keyboard() -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        _button(
            text="بازگشت به فروشگاه",
            callback_data="buy_product",
            style="primary",
            icon_key="shop",
        )
    )

    builder.row(
        _button(
            text="منوی اصلی",
            callback_data="main_menu",
            style="primary",
            icon_key="back",
        )
    )

    return builder.as_markup()


def fallback_owner_keyboard(
    order_id: int,
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        _button(
            text="تأیید و شروع پردازش",
            callback_data=(
                f"owner_approve_order_{order_id}"
            ),
            style="success",
            icon_key="success",
        )
    )

    builder.row(
        _button(
            text="رد درخواست",
            callback_data=(
                f"owner_reject_order_{order_id}"
            ),
            style="danger",
            icon_key="error",
        )
    )

    return builder.as_markup()


def owner_keyboard(
    order_id: int,
) -> InlineKeyboardMarkup:
    if callable(
        get_owner_order_keyboard
    ):
        try:
            return get_owner_order_keyboard(
                order_id
            )

        except Exception:
            logger.exception(
                "Admin owner keyboard failed; "
                "using local fallback."
            )

    return fallback_owner_keyboard(
        order_id
    )


# ============================================================
# Telegram Helpers
# ============================================================

async def safe_answer(
    callback: CallbackQuery,
    text: Optional[str] = None,
    *,
    show_alert: bool = False,
) -> None:
    try:
        await callback.answer(
            text=text,
            show_alert=show_alert,
        )

    except TelegramBadRequest:
        pass

    except Exception:
        logger.exception(
            "Could not answer shop callback."
        )


async def safe_edit_message(
    callback: CallbackQuery,
    text: str,
    keyboard: Optional[
        InlineKeyboardMarkup
    ] = None,
) -> bool:
    if not callback.message:
        return False

    rendered = premiumize_html(
        text
    )

    try:
        await callback.message.edit_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 shop message."
                )

                return False

        logger.warning(
            "Shop edit failed: %s",
            exc,
        )

        return False

    except Exception:
        logger.exception(
            "Unexpected shop edit error."
        )

        return False


async def send_html(
    message: Message,
    text: str,
    *,
    keyboard: Optional[
        InlineKeyboardMarkup
    ] = None,
) -> None:
    await message.answer(
        premiumize_html(
            text
        ),
        reply_markup=keyboard,
        parse_mode="HTML",
    )


async def notify_user(
    bot,
    user_id: int,
    text: str,
) -> bool:
    try:
        await bot.send_message(
            chat_id=int(
                user_id
            ),
            text=premiumize_html(
                text
            ),
            reply_markup=(
                build_result_keyboard()
            ),
            parse_mode="HTML",
        )

        return True

    except TelegramForbiddenError:
        return False

    except Exception:
        logger.exception(
            "Could not notify user %s.",
            user_id,
        )

        return False


# ============================================================
# Owner Notification
# ============================================================

async def notify_owners(
    *,
    bot,
    order: Order,
    package_name: str,
    buyer_name: str,
    buyer_username: Optional[str],
) -> int:
    targets = get_admin_ids()

    if not targets:
        logger.error(
            "No ADMIN_IDS configured for order %s.",
            order.id,
        )

        return 0

    username = (
        f"@{buyer_username}"
        if buyer_username
        else "ثبت نشده"
    )

    text = (
        f"{ce('package')} "
        "<b>سفارش جدید منتظر بررسی شماست</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"🧾 شماره سفارش\n"
        f"<code>#{order.id}</code>\n\n"

        f"{ce('user')} خریدار\n"
        f"<b>{escape(buyer_name)}</b>\n"
        f"یوزرنیم: <code>{escape(username)}</code>\n"
        f"UID: <code>{order.user_id}</code>\n\n"

        f"{ce('package')} محصول\n"
        f"<code>{escape(package_name)}</code>\n\n"

        f"{ce('target')} مقصد تحویل\n"
        f"<code>@{escape(order.target or '')}</code>\n\n"

        f"{ce('money')} مبلغ قفل‌شده توسط کاربر\n"
        + (
            f"<code>{order.price:,} تومان</code>\n\n"
            if int(order.price or 0) > 0
            else "<code>نامشخص</code>\n\n"
        )
        + "🕒 زمان ثبت\n"
        f"<code>{format_order_datetime(order.created_at)}</code>\n"
        "<i>به وقت تهران</i>\n\n"

        f"{ce('warning')} "
        "<b>وضعیت: در انتظار تصمیم مالک</b>\n"
        "این مبلغ هنگام تأیید کاربر قفل شده است. "
        "با تأیید شما فقط موجودی فعلی کاربر بررسی و همان مبلغ کسر می‌شود؛ "
        "نرخ تتر دوباره محاسبه نخواهد شد."
    )

    sent = 0

    for admin_id in targets:
        try:
            await bot.send_message(
                chat_id=admin_id,
                text=premiumize_html(
                    text
                ),
                reply_markup=(
                    owner_keyboard(
                        order.id
                    )
                ),
                parse_mode="HTML",
            )

            sent += 1

        except Exception:
            logger.exception(
                "Owner notification failed | "
                "admin=%s order=%s",
                admin_id,
                order.id,
            )

    return sent


# ============================================================
# Shop Menus
# ============================================================

@router.callback_query(
    F.data == "price_list"
)
async def show_public_price_list(
    callback: CallbackQuery,
    state: FSMContext,
    db: Any = None,
) -> None:
    """
    Main-menu public price list using Telegram Native Rich Table.
    """

    await state.clear()

    await safe_answer(
        callback,
        "در حال دریافت قیمت‌های لحظه‌ای..."
    )

    try:
        (
            rows,
            usdt_toman,
            rate_source,
        ) = await get_public_price_list(
            db=db,
        )

    except ProductRateUnavailable:
        await safe_edit_message(
            callback,
            (
                f"{ce('warning')} "
                "<b>نرخ لحظه‌ای تتر در دسترس نیست.</b>\n\n"
                "چند لحظه دیگر دوباره «لیست قیمت‌ها» را باز کنید."
            ),
            build_back_keyboard(
                "main_menu"
            ),
        )
        return

    except Exception:
        logger.exception(
            "Could not build public price list."
        )

        await safe_edit_message(
            callback,
            (
                f"{ce('error')} "
                "<b>لیست قیمت‌ها موقتاً قابل نمایش نیست.</b>"
            ),
            build_back_keyboard(
                "main_menu"
            ),
        )
        return

    if not rows:
        await safe_edit_message(
            callback,
            (
                f"{ce('warning')} "
                "<b>هنوز قیمت محصولی برای نمایش تنظیم نشده است.</b>"
            ),
            build_back_keyboard(
                "main_menu"
            ),
        )
        return

    keyboard = build_price_list_keyboard()

    rich_message = build_rich_price_message(
        rows=rows,
        usdt_toman=usdt_toman,
        rate_source=rate_source,
    )

    if (
        rich_message is not None
        and hasattr(
            callback.bot,
            "send_rich_message",
        )
    ):
        try:
            await callback.bot.send_rich_message(
                chat_id=callback.from_user.id,
                rich_message=rich_message,
                reply_markup=keyboard,
            )

            # Keep the chat clean: remove the old main-menu message after
            # the Rich Message has been delivered successfully.
            if callback.message:
                try:
                    await callback.message.delete()
                except Exception:
                    pass

            return

        except Exception:
            logger.exception(
                "Native Telegram Rich price table failed; using HTML fallback."
            )

    # Compatibility fallback — never crash the bot on an older aiogram build.
    fallback = build_price_list_fallback_text(
        rows=rows,
        usdt_toman=usdt_toman,
        rate_source=rate_source,
    )

    if callback.message:
        try:
            await callback.message.edit_text(
                fallback,
                reply_markup=keyboard,
                parse_mode="HTML",
            )
            return

        except Exception:
            logger.debug(
                "Could not edit price-list fallback; sending a new message.",
                exc_info=True,
            )

    await callback.bot.send_message(
        chat_id=callback.from_user.id,
        text=fallback,
        reply_markup=keyboard,
        parse_mode="HTML",
    )


@router.callback_query(
    F.data == "buy_product"
)
async def open_shop(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    await state.clear()

    text = (
        f"{ce('shop')} "
        "<b>فروشگاه خدمات تلگرام</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"{ce('premium')} "
        "Premium و Stars را از منوی زیر انتخاب کنید.\n\n"

        f"{ce('money')} "
        "بعد از انتخاب هر پکیج، قیمت بر اساس نرخ زنده تتر داخل "
        "توضیحات سفارش نمایش داده می‌شود.\n\n"

        f"{ce('warning')} "
        "بعد از تأیید شما همین مبلغ برای سفارش قفل می‌شود."
    )

    await safe_edit_message(
        callback,
        text,
        get_products_menu_keyboard(),
    )

    await safe_answer(
        callback
    )


@router.callback_query(
    F.data == "buy_stars"
)
async def buy_stars_menu(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    await state.clear()

    text = (
        f"{ce('sparkles', '⭐')} "
        "<b>خرید Stars تلگرام</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        "بسته موردنظر را انتخاب کنید.\n\n"

        f"{ce('money')} "
        "قیمت لحظه‌ای بعد از انتخاب بسته، داخل توضیحات سفارش "
        "نمایش داده می‌شود."
    )

    await safe_edit_message(
        callback,
        text,
        get_services_packages_keyboard(
            "stars"
        ),
    )

    await safe_answer(
        callback
    )


@router.callback_query(
    F.data == "buy_premium"
)
async def buy_premium_menu(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    await state.clear()

    text = (
        f"{ce('premium', '💎')} "
        "<b>Telegram Premium</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        "مدت اشتراک را انتخاب کنید.\n\n"

        f"{ce('money')} "
        "قیمت لحظه‌ای بعد از انتخاب پکیج، داخل توضیحات سفارش "
        "نمایش داده می‌شود."
    )

    await safe_edit_message(
        callback,
        text,
        get_services_packages_keyboard(
            "premium"
        ),
    )

    await safe_answer(
        callback
    )


# ============================================================
# Select Standard Package
# ============================================================

@router.callback_query(
    F.data.startswith(
        "stars_"
    )
    | F.data.startswith(
        "prem_"
    )
)
async def select_package(
    callback: CallbackQuery,
    state: FSMContext,
    db: Any = None,
) -> None:
    package_id = str(
        callback.data
        or ""
    )

    package_name = PACKAGE_NAMES.get(
        package_id
    )

    if not package_name:
        await safe_answer(
            callback,
            "❌ این پکیج معتبر نیست.",
            show_alert=True,
        )
        return

    await state.clear()

    await safe_edit_message(
        callback,
        (
            f"{ce('loading')} "
            "<b>در حال محاسبه قیمت با نرخ لحظه‌ای تتر...</b>"
        ),
        build_back_keyboard(
            "buy_premium"
            if package_id.startswith("prem_")
            else "buy_stars"
        ),
    )

    preview_price = await get_live_preview_price(
        package_id,
        db=db,
    )

    if (
        preview_price is None
        or preview_price <= 0
    ):
        await safe_edit_message(
            callback,
            (
                f"{ce('error')} "
                "<b>در حال حاضر امکان محاسبه قیمت این محصول وجود ندارد.</b>\n\n"
                "قیمت دلاری محصول باید در پنل مدیریت تنظیم شده باشد "
                "و حداقل یکی از منابع نرخ تتر در دسترس باشد."
            ),
            build_back_keyboard(
                "buy_premium"
                if package_id.startswith("prem_")
                else "buy_stars"
            ),
        )

        await safe_answer(
            callback,
            "قیمت محصول یا نرخ تتر در دسترس نیست.",
            show_alert=True,
        )
        return

    preview_price = int(
        preview_price
    )

    balance = await get_user_balance(
        callback.from_user.id,
        db,
    )

    if balance < preview_price:
        await state.clear()

        await safe_edit_message(
            callback,
            insufficient_balance_text(
                price=preview_price,
                balance=balance,
            ),
            build_insufficient_balance_keyboard(
                back_callback=(
                    "buy_premium"
                    if package_id.startswith("prem_")
                    else "buy_stars"
                )
            ),
        )

        await safe_answer(
            callback,
            "موجودی کیف پول کافی نیست.",
            show_alert=True,
        )

        return

    await state.update_data(
        selected_package=package_id,
        readable_package_name=package_name,
        price=preview_price,
        preview_price_toman=preview_price,
    )

    if package_id in STARS_AMOUNTS:
        await state.update_data(
            stars_amount=STARS_AMOUNTS[
                package_id
            ]
        )

    if package_id in PREMIUM_MONTHS:
        await state.update_data(
            premium_months=PREMIUM_MONTHS[
                package_id
            ]
        )

    await state.set_state(
        OrderStates.waiting_for_target_account
    )

    text = (
        f"{ce('package')} <b>جزئیات خرید</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"{ce('package')} محصول\n"
        f"<code>{escape(package_name)}</code>\n\n"

        f"{ce('money')} مبلغ فعلی\n"
        f"<code>{preview_price:,} تومان</code>\n\n"

        f"{ce('wallet')} موجودی کیف پول\n"
        f"<code>{balance:,} تومان</code>\n"
        f"{ce('success')} وضعیت موجودی: <b>کافی</b>\n\n"

        f"{ce('target')} <b>مرحله بعد: مقصد سفارش</b>\n"
        "یوزرنیم تلگرام حسابی که محصول باید برای آن ارسال شود را بفرستید "
        "یا دکمه «خودم» را بزنید.\n"
        "مثال: <code>@username</code>\n\n"

        f"{ce('success')} "
        "<b>قیمت نمایش‌داده‌شده برای این سفارش ثابت می‌ماند.</b>\n"
        "بعد از تأیید نهایی شما، همین مبلغ داخل سفارش قفل می‌شود و "
        "مالک هر زمانی سفارش را تأیید کند، نرخ دوباره محاسبه نخواهد شد."
    )

    await safe_edit_message(
        callback,
        text,
        build_target_selection_keyboard(
            back_callback=(
                "buy_premium"
                if package_id.startswith("prem_")
                else "buy_stars"
            )
        ),
    )

    await safe_answer(
        callback
    )


# ============================================================
# Custom Stars
# ============================================================

@router.callback_query(
    F.data == "custom_stars"
)
async def custom_stars(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    await state.clear()

    await state.set_state(
        OrderStates
        .waiting_for_custom_amount
    )

    text = (
        f"{ce('sparkles', '⭐')} "
        "<b>تعداد دلخواه Stars</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"حداقل: <b>{MIN_CUSTOM_STARS:,}</b>\n"
        f"حداکثر: <b>{MAX_CUSTOM_STARS:,}</b>\n\n"

        "مثال: <code>350</code>"
    )

    await safe_edit_message(
        callback,
        text,
        build_back_keyboard(
            "buy_stars"
        ),
    )

    await safe_answer(
        callback
    )


@router.message(
    OrderStates.waiting_for_custom_amount,
    F.text,
)
async def process_custom_amount(
    message: Message,
    state: FSMContext,
    db: Any = None,
) -> None:
    raw = (message.text or "").strip()

    translation = str.maketrans(
        "۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩",
        "01234567890123456789",
    )

    value = (
        raw.translate(translation)
        .replace(",", "")
        .replace("،", "")
        .replace("_", "")
        .replace(" ", "")
    )

    if not value.isdigit():
        await send_html(
            message,
            f"{ce('error')} لطفاً فقط عدد ارسال کنید.",
            keyboard=build_back_keyboard("buy_stars"),
        )
        return

    amount = int(value)

    if (
        amount < MIN_CUSTOM_STARS
        or amount > MAX_CUSTOM_STARS
    ):
        await send_html(
            message,
            (
                f"{ce('warning')} تعداد Stars باید بین "
                f"<b>{MIN_CUSTOM_STARS:,}</b> و "
                f"<b>{MAX_CUSTOM_STARS:,}</b> باشد."
            ),
        )
        return

    package_id = f"custom_stars_{amount}"

    processing_message = await message.answer(
        premiumize_html(
            f"{ce('loading')} "
            f"در حال محاسبه قیمت <b>{amount:,}</b> Stars..."
        ),
        parse_mode="HTML",
    )

    preview_price = await get_live_preview_price(
        package_id,
        db=db,
    )

    if (
        preview_price is None
        or preview_price <= 0
    ):
        try:
            await processing_message.edit_text(
                premiumize_html(
                    f"{ce('error')} "
                    "<b>قیمت Stars دلخواه قابل محاسبه نیست.</b>\n\n"
                    "در پنل مدیریت باید قیمت دلاری هر ۱ Star "
                    "برای سفارش‌های دلخواه تنظیم شود."
                ),
                parse_mode="HTML",
            )
        except Exception:
            pass

        return

    preview_price = int(
        preview_price
    )

    balance = await get_user_balance(
        message.from_user.id,
        db,
    )

    if balance < preview_price:
        await state.clear()

        try:
            await processing_message.edit_text(
                premiumize_html(
                    insufficient_balance_text(
                        price=preview_price,
                        balance=balance,
                    )
                ),
                reply_markup=build_insufficient_balance_keyboard(
                    back_callback="buy_stars"
                ),
                parse_mode="HTML",
            )

        except Exception:
            await send_html(
                message,
                insufficient_balance_text(
                    price=preview_price,
                    balance=balance,
                ),
                keyboard=build_insufficient_balance_keyboard(
                    back_callback="buy_stars"
                ),
            )

        return

    await state.update_data(
        selected_package=package_id,
        readable_package_name=f"{amount:,} Stars تلگرام",
        price=preview_price,
        preview_price_toman=preview_price,
        stars_amount=amount,
    )

    await state.set_state(
        OrderStates.waiting_for_target_account
    )

    try:
        await processing_message.edit_text(
            premiumize_html(
                (
                    f"{ce('package')} <b>توضیحات سفارش</b>\n"
                    "━━━━━━━━━━━━━━━━━━━━\n\n"
                    f"{ce('package')} محصول: "
                    f"<b>{amount:,} Stars تلگرام</b>\n\n"
                    f"{ce('money')} قیمت لحظه‌ای: "
                    f"<code>{int(preview_price):,} تومان</code>\n\n"
                    f"{ce('wallet')} موجودی: "
                    f"<code>{balance:,} تومان</code>\n\n"
                    f"{ce('target')} حالا یوزرنیم مقصد را ارسال کنید "
                    "یا دکمه «خودم» را بزنید.\n\n"
                    f"{ce('success')} "
                    "<b>بعد از تأیید شما همین قیمت قفل می‌شود.</b> "
                    "تأیید مالک باعث تغییر نرخ یا مبلغ سفارش نمی‌شود."
                )
            ),
            reply_markup=build_target_selection_keyboard(
                back_callback="buy_stars"
            ),
            parse_mode="HTML",
        )
    except Exception:
        await send_html(
            message,
            (
                f"{ce('success')} <b>{amount:,} Stars انتخاب شد.</b>\n\n"
                f"{ce('money')} قیمت فعلی: "
                f"<code>{int(preview_price):,} تومان</code>\n"
                f"{ce('wallet')} موجودی: "
                f"<code>{balance:,} تومان</code>\n\n"
                f"{ce('target')} حالا یوزرنیم مقصد را ارسال کنید."
            ),
            keyboard=build_back_keyboard("buy_stars"),
        )


# ============================================================
# Target
# ============================================================

async def _prepare_target_invoice(
    *,
    user_id: int,
    target: str,
    state: FSMContext,
    db: Any = None,
) -> tuple[
    Optional[str],
    Optional[InlineKeyboardMarkup],
    Optional[str],
]:
    """
    Prepare the confirmation invoice using the price already shown to user.

    Returns:
        text, keyboard, error_message
    """

    data = await state.get_data()

    package_id = str(
        data.get(
            "selected_package",
            "",
        )
        or ""
    )

    package_name = str(
        data.get(
            "readable_package_name",
            "محصول",
        )
    )

    if not package_id:
        return (
            None,
            None,
            "اطلاعات سفارش منقضی شده است.",
        )

    try:
        locked_price = int(
            data.get(
                "price",
                0,
            )
            or 0
        )
    except (
        TypeError,
        ValueError,
    ):
        locked_price = 0

    # We deliberately do NOT fetch another live quote here.
    # The shown preview price is the price user is about to confirm.
    if locked_price <= 0:
        return (
            None,
            None,
            "قیمت اولیه سفارش معتبر نیست؛ لطفاً سفارش را دوباره شروع کنید.",
        )

    balance = await get_user_balance(
        user_id,
        db,
    )

    if balance < locked_price:
        return (
            None,
            build_insufficient_balance_keyboard(
                back_callback="buy_product"
            ),
            (
                "موجودی کیف پول برای مبلغ فعلی سفارش کافی نیست."
            ),
        )

    await state.update_data(
        target_account=target,
        price=locked_price,
        preview_price_toman=locked_price,
        locked_price_toman=locked_price,
    )

    await state.set_state(
        OrderStates.waiting_for_request_confirmation
    )

    remaining_balance = max(
        0,
        int(balance)
        - int(locked_price),
    )

    text = (
        f"{ce('list')} <b>پیش‌فاکتور سفارش</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"{ce('package')} محصول\n"
        f"<code>{escape(package_name)}</code>\n\n"

        f"{ce('target')} مقصد تحویل\n"
        f"<code>@{escape(target)}</code>\n\n"

        f"{ce('money')} مبلغ سفارش\n"
        f"<code>{locked_price:,} تومان</code>\n\n"

        f"{ce('wallet')} موجودی فعلی\n"
        f"<code>{balance:,} تومان</code>\n"

        "💳 موجودی پس از خرید\n"
        f"<code>{remaining_balance:,} تومان</code>\n\n"

        "🕒 زمان صدور پیش‌فاکتور\n"
        f"<code>{now_tehran_text()}</code>\n"
        "<i>به وقت تهران</i>\n\n"

        f"{ce('success')} "
        "<b>قیمت این پیش‌فاکتور ثابت است.</b>\n"
        "با زدن «ثبت درخواست خرید»، همین مبلغ داخل سفارش قفل می‌شود. "
        "مالک بعداً نرخ تتر را دوباره محاسبه نمی‌کند.\n\n"

        f"{ce('warning')} "
        "تا زمانی که مالک سفارش را تأیید نکرده، مبلغی از کیف پول کسر نمی‌شود."
    )

    return (
        text,
        build_purchase_keyboard(),
        None,
    )



@router.message(
    OrderStates.waiting_for_target_account,
    F.text,
)
async def receive_target_account(
    message: Message,
    state: FSMContext,
    db: Any = None,
) -> None:
    target = normalize_username(
        message.text
        or ""
    )

    if not target:
        await send_html(
            message,
            (
                f"{ce('error')} "
                "<b>یوزرنیم مقصد معتبر نیست.</b>\n\n"
                "مثال: <code>@username</code>"
            ),
        )
        return

    (
        invoice_text,
        keyboard,
        error,
    ) = await _prepare_target_invoice(
        user_id=message.from_user.id,
        target=target,
        state=state,
        db=db,
    )

    if error:
        if (
            "موجودی"
            in error
        ):
            data = await state.get_data()

            try:
                amount = int(
                    data.get(
                        "price",
                        0,
                    )
                    or 0
                )
            except Exception:
                amount = 0

            balance = await get_user_balance(
                message.from_user.id,
                db,
            )

            await state.clear()

            await send_html(
                message,
                insufficient_balance_text(
                    price=amount,
                    balance=balance,
                ),
                keyboard=(
                    keyboard
                    or build_result_keyboard()
                ),
            )
            return

        await state.clear()

        await send_html(
            message,
            f"{ce('error')} {escape(error)}",
            keyboard=build_result_keyboard(),
        )
        return

    await send_html(
        message,
        invoice_text or "",
        keyboard=keyboard,
    )


@router.callback_query(
    F.data == "shop_target_self"
)
async def use_self_as_target(
    callback: CallbackQuery,
    state: FSMContext,
    db: Any = None,
) -> None:
    """
    Glass button: use the Telegram username of the current buyer.
    """

    username = normalize_username(
        callback.from_user.username
        or ""
    )

    if not username:
        await safe_answer(
            callback,
            (
                "برای استفاده از «خودم» باید روی اکانت تلگرام‌تان "
                "Username تنظیم شده باشد."
            ),
            show_alert=True,
        )
        return

    current_state = await state.get_state()

    if (
        current_state
        != OrderStates.waiting_for_target_account.state
    ):
        await safe_answer(
            callback,
            "نشست سفارش منقضی شده است؛ دوباره محصول را انتخاب کنید.",
            show_alert=True,
        )
        return

    (
        invoice_text,
        keyboard,
        error,
    ) = await _prepare_target_invoice(
        user_id=callback.from_user.id,
        target=username,
        state=state,
        db=db,
    )

    if error:
        await safe_answer(
            callback,
            error,
            show_alert=True,
        )
        return

    await safe_edit_message(
        callback,
        invoice_text or "",
        keyboard,
    )

    await safe_answer(
        callback,
        "✅ مقصد سفارش روی اکانت خودتان قرار گرفت."
    )


# ============================================================
# Submit Owner Approval Request
# ============================================================

@router.callback_query(
    F.data == "finalize_purchase"
)
async def finalize_purchase(
    callback: CallbackQuery,
    state: FSMContext,
    db: Any = None,
    order_service: Optional[
        OrderService
    ] = None,
) -> None:
    current_state = await state.get_state()

    valid_states = {
        OrderStates.waiting_for_request_confirmation.state,
        OrderStates.waiting_for_payment_confirmation.state,
    }

    if (
        current_state
        and current_state not in valid_states
    ):
        await safe_answer(
            callback,
            "❌ نشست سفارش معتبر نیست.",
            show_alert=True,
        )
        return

    data = await state.get_data()

    package_id = str(
        data.get("selected_package", "")
        or ""
    )

    package_name = str(
        data.get("readable_package_name", "محصول")
    )

    target = normalize_username(
        str(
            data.get("target_account", "")
            or ""
        )
    )

    try:
        preview_price = int(
            data.get(
                "price",
                0,
            )
            or 0
        )
    except (
        TypeError,
        ValueError,
    ):
        preview_price = 0

    if (
        not package_id
        or not target
        or preview_price <= 0
    ):
        await state.clear()
        await safe_answer(
            callback,
            "❌ اطلاعات سفارش یا قیمت منقضی شده است.",
            show_alert=True,
        )
        return

    try:
        service = await resolve_order_service(
            order_service,
            db,
        )

        # Atomic final eligibility check:
        # check current wallet balance + create pending order in ONE DB
        # transaction. No wallet debit happens here.
        create_result = (
            await service
            .create_pending_owner_order_if_balance_sufficient(
                user_id=callback.from_user.id,
                product_id=package_id,
                target=target,
                price=preview_price,
            )
        )

        if not create_result.success:
            if (
                create_result.reason
                == "insufficient_balance"
            ):
                await state.clear()

                await safe_edit_message(
                    callback,
                    insufficient_balance_text(
                        price=create_result.required_price,
                        balance=create_result.balance,
                    ),
                    build_insufficient_balance_keyboard(
                        back_callback="buy_product"
                    ),
                )

                await safe_answer(
                    callback,
                    "موجودی کیف پول کافی نیست؛ درخواستی برای مالک ارسال نشد.",
                    show_alert=True,
                )

                return

            await safe_answer(
                callback,
                "❌ امکان ثبت درخواست وجود ندارد.",
                show_alert=True,
            )

            logger.warning(
                "Pending order rejected before admin notification | "
                "user=%s reason=%s balance=%s required=%s",
                callback.from_user.id,
                create_result.reason,
                create_result.balance,
                create_result.required_price,
            )

            return

        order = create_result.order

        if order is None:
            await safe_answer(
                callback,
                "❌ ثبت سفارش انجام نشد.",
                show_alert=True,
            )
            return

        await state.clear()

        sent = await notify_owners(
            bot=callback.bot,
            order=order,
            package_name=package_name,
            buyer_name=(
                callback.from_user.full_name
                or "بدون نام"
            ),
            buyer_username=callback.from_user.username,
        )

        text = (
            f"{ce('success')} <b>درخواست خرید با موفقیت ثبت شد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"🧾 شماره سفارش\n"
            f"<code>#{order.id}</code>\n\n"

            f"{ce('package')} محصول\n"
            f"<code>{escape(package_name)}</code>\n\n"

            f"{ce('target')} مقصد تحویل\n"
            f"<code>@{escape(target)}</code>\n\n"

            f"{ce('money')} مبلغ قفل‌شده سفارش\n"
            f"<code>{preview_price:,} تومان</code>\n\n"

            "🕒 زمان ثبت\n"
            f"<code>{format_order_datetime(order.created_at)}</code>\n"
            "<i>به وقت تهران</i>\n\n"

            f"{ce('loading')} <b>وضعیت فعلی</b>\n"
            "<code>در انتظار بررسی مالک</code>\n\n"

            f"{ce('success')} "
            "<b>این مبلغ برای سفارش شما قفل شد و دیگر با نرخ تتر تغییر نمی‌کند.</b>\n"
            "هنوز مبلغی از کیف پول کسر نشده است؛ "
            "کسر وجه فقط پس از تأیید مالک انجام می‌شود."
        )

        if sent == 0:
            text += (
                "\n\n"
                f"{ce('warning')} اعلان مستقیم مالک ارسال نشد، "
                "اما سفارش داخل دیتابیس ثبت شده است."
            )

        await safe_edit_message(
            callback,
            text,
            build_result_keyboard(),
        )

        await safe_answer(
            callback,
            "✅ درخواست خرید ثبت شد و در انتظار بررسی مالک است.",
            show_alert=True,
        )

    except Exception:
        logger.exception(
            "Could not persist owner approval order."
        )

        await safe_answer(
            callback,
            "❌ ثبت سفارش انجام نشد.",
            show_alert=True,
        )


# ============================================================
# Cancel
# ============================================================

@router.callback_query(
    F.data == "cancel_shop"
)
async def cancel_shop(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    await state.clear()

    await safe_edit_message(
        callback,
        (
            f"{ce('error')} "
            "<b>فرآیند سفارش لغو شد.</b>"
        ),
        get_user_main_menu(),
    )

    await safe_answer(
        callback
    )


# ============================================================
# Price Change / Manual Fulfillment Keyboards
# ============================================================

def price_change_keyboard(
    order_id: int,
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        InlineKeyboardButton(
            text="✅ تأیید مبلغ جدید",
            callback_data=(
                f"accept_new_price_order_{int(order_id)}"
            ),
            style="success",
        )
    )

    builder.row(
        InlineKeyboardButton(
            text="❌ لغو درخواست",
            callback_data=(
                f"cancel_new_price_order_{int(order_id)}"
            ),
            style="danger",
        )
    )

    return builder.as_markup()


def manual_fulfillment_keyboard(
    order_id: int,
) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()

    builder.row(
        InlineKeyboardButton(
            text="✅ ثبت تحویل سفارش",
            callback_data=(
                f"manual_complete_order_{int(order_id)}"
            ),
            style="success",
        )
    )

    builder.row(
        InlineKeyboardButton(
            text="↩️ لغو و بازگشت وجه",
            callback_data=(
                f"manual_refund_order_{int(order_id)}"
            ),
            style="danger",
        )
    )

    return builder.as_markup()


# ============================================================
# Owner Reject
# ============================================================

@router.callback_query(
    F.data.startswith(
        "owner_reject_order_"
    )
)
async def owner_reject_order(
    callback: CallbackQuery,
    db: Any = None,
    order_service: Optional[
        OrderService
    ] = None,
) -> None:
    owner_id = int(
        callback.from_user.id
    )

    if not is_admin(
        owner_id
    ):
        await safe_answer(
            callback,
            "⛔ دسترسی ندارید.",
            show_alert=True,
        )
        return

    try:
        order_id = int(
            str(
                callback.data
            ).removeprefix(
                "owner_reject_order_"
            )
        )

    except (
        TypeError,
        ValueError,
    ):
        await safe_answer(
            callback,
            "❌ شناسه سفارش نامعتبر است.",
            show_alert=True,
        )
        return

    service = await resolve_order_service(
        order_service,
        db,
    )

    try:
        order = await service.get_order(
            order_id
        )

    except Exception:
        await safe_answer(
            callback,
            "❌ سفارش پیدا نشد.",
            show_alert=True,
        )
        return

    rejected = (
        await service.reject_by_owner(
            order_id,
            owner_id,
        )
    )

    if not rejected:
        fresh = await service.get_order(
            order_id
        )

        await safe_answer(
            callback,
            (
                "⚠️ سفارش قبلاً پردازش شده است.\n"
                f"وضعیت: {fresh.status}"
            ),
            show_alert=True,
        )
        return

    fresh = await service.get_order(
        order.id
    )

    registered_at = format_order_datetime(
        fresh.created_at
    )

    rejected_at = order_event_time(
        fresh,
        "updated_at",
    )

    await notify_user(
        callback.bot,
        order.user_id,
        (
            f"{ce('error')} "
            "<b>درخواست خرید رد شد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"🧾 شماره سفارش\n"
            f"<code>#{order.id}</code>\n\n"

            f"🕒 زمان ثبت\n"
            f"<code>{registered_at}</code>\n"

            f"❌ زمان رد درخواست\n"
            f"<code>{rejected_at}</code>\n"
            "<i>به وقت تهران</i>\n\n"

            "<b>هیچ مبلغی از کیف پول شما کسر نشده است.</b>\n"
            "می‌توانید از فروشگاه یک سفارش جدید ثبت کنید."
        ),
    )

    await safe_edit_message(
        callback,
        (
            f"{ce('error')} "
            "<b>درخواست با موفقیت رد شد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"🧾 شماره سفارش: <code>#{order.id}</code>\n"
            f"🕒 ثبت: <code>{registered_at}</code>\n"
            f"❌ رد درخواست: <code>{rejected_at}</code>\n"
            "<i>به وقت تهران</i>\n\n"

            "این سفارش بسته شد و "
            "<b>هیچ مبلغی از کیف پول کاربر کسر نشده است.</b>"
        ),
        None,
    )

    await safe_answer(
        callback,
        "سفارش رد شد.",
        show_alert=True,
    )


# ============================================================
# Owner Approve
# ============================================================

@router.callback_query(
    F.data.startswith(
        "owner_approve_order_"
    )
)
async def owner_approve_order(
    callback: CallbackQuery,
    db: Any = None,
    order_service: Optional[
        OrderService
    ] = None,
) -> None:
    """
    Owner approval in LOCKED-PRICE mode.

    Price contract:
        User sees price -> user confirms -> order.price is locked.
        Owner approval NEVER requests a new USDT/Toman quote.
        The exact stored order.price is charged, regardless of approval time.
    """

    owner_id = int(
        callback.from_user.id
    )

    if not is_admin(
        owner_id
    ):
        await safe_answer(
            callback,
            "⛔ دسترسی ندارید.",
            show_alert=True,
        )
        return

    try:
        order_id = int(
            str(
                callback.data
            ).removeprefix(
                "owner_approve_order_"
            )
        )

    except (
        TypeError,
        ValueError,
    ):
        await safe_answer(
            callback,
            "❌ شناسه سفارش نامعتبر است.",
            show_alert=True,
        )
        return

    service = await resolve_order_service(
        order_service,
        db,
    )

    order = await service.claim_for_owner(
        order_id,
        owner_id,
    )

    if order is None:
        try:
            fresh = await service.get_order(
                order_id
            )
            status = fresh.status
        except Exception:
            status = "unknown"

        await safe_answer(
            callback,
            (
                "⚠️ این سفارش قبلاً پردازش شده است.\n"
                f"وضعیت: {status}"
            ),
            show_alert=True,
        )
        return

    locked_price = int(
        order.price
        or 0
    )

    if locked_price <= 0:
        await service.restore_owner_pending(
            order.id,
            error=(
                "Locked order price is invalid."
            ),
        )

        await safe_edit_message(
            callback,
            (
                f"{ce('error')} "
                "<b>مبلغ قفل‌شده سفارش معتبر نیست.</b>\n\n"
                "سفارش بدون کسر وجه به حالت انتظار برگشت."
            ),
            owner_keyboard(
                order.id
            ),
        )
        return

    await safe_answer(
        callback,
        (
            f"در حال تأیید مبلغ قفل‌شده "
            f"{locked_price:,} تومان..."
        )
    )

    try:
        # Important: reserve_wallet_for_provider charges order.price.
        # No rate provider / ProductPricingService call happens here.
        reservation = (
            await service.reserve_wallet_for_provider(
                order.id
            )
        )

        if not reservation.success:
            if (
                reservation.reason
                == "insufficient_balance"
            ):
                await notify_user(
                    callback.bot,
                    order.user_id,
                    (
                        f"{ce('warning')} "
                        "<b>پردازش سفارش به دلیل کمبود موجودی متوقف شد</b>\n"
                        "━━━━━━━━━━━━━━━━━━━━\n\n"

                        f"🧾 شماره سفارش: <code>#{order.id}</code>\n\n"

                        f"💰 مبلغ قفل‌شده: "
                        f"<code>{reservation.amount:,} تومان</code>\n"

                        f"💳 موجودی فعلی: "
                        f"<code>{reservation.balance_before:,} تومان</code>\n"

                        f"❌ مبلغ کسری: "
                        f"<code>{max(0, reservation.amount - reservation.balance_before):,} تومان</code>\n\n"

                        "<b>قیمت سفارش تغییر نکرده است.</b> "
                        "فقط موجودی کیف پول برای همان مبلغ کافی نبود."
                    ),
                )

                await safe_edit_message(
                    callback,
                    (
                        f"{ce('warning')} "
                        "<b>تأیید سفارش انجام نشد؛ موجودی کاربر کافی نیست</b>\n"
                        "━━━━━━━━━━━━━━━━━━━━\n\n"

                        f"🧾 سفارش: <code>#{order.id}</code>\n"
                        f"🔒 مبلغ قفل‌شده: <code>{reservation.amount:,} تومان</code>\n"
                        f"💳 موجودی: <code>{reservation.balance_before:,} تومان</code>\n\n"

                        "هیچ وجهی کسر نشده است."
                    ),
                    None,
                )
                return

            await service.restore_owner_pending(
                order.id,
                error=(
                    "Wallet reserve failed: "
                    + reservation.reason
                ),
            )

            await safe_edit_message(
                callback,
                (
                    f"{ce('error')} "
                    "<b>پرداخت از کیف پول انجام نشد.</b>\n\n"
                    "سفارش بدون کسر وجه به حالت انتظار بررسی بازگشت."
                ),
                owner_keyboard(
                    order.id
                ),
            )
            return

        processing_order = await service.get_order(
            order.id
        )

        registered_at = format_order_datetime(
            processing_order.created_at
        )

        processing_at = order_event_time(
            processing_order,
            "provider_started_at",
            "updated_at",
        )

        await notify_user(
            callback.bot,
            order.user_id,
            (
                f"{ce('success')} "
                "<b>سفارش تأیید شد و وارد مرحله پردازش شد</b>\n"
                "━━━━━━━━━━━━━━━━━━━━\n\n"

                f"🧾 شماره سفارش\n"
                f"<code>#{order.id}</code>\n\n"

                f"{ce('package')} محصول\n"
                f"<code>{escape(_package_short_title(order.product_id))}</code>\n\n"

                f"{ce('target')} مقصد\n"
                f"<code>@{escape(order.target or '')}</code>\n\n"

                f"{ce('money')} مبلغ کسرشده\n"
                f"<code>{reservation.amount:,} تومان</code>\n"
                "🔒 <b>همان مبلغی که هنگام ثبت سفارش تأیید کرده بودید</b>\n\n"

                f"🕒 ثبت سفارش: <code>{registered_at}</code>\n"
                f"⚙️ شروع پردازش: <code>{processing_at}</code>\n"
                "<i>به وقت تهران</i>\n\n"

                f"{ce('success')} "
                "<b>وضعیت فعلی: در حال آماده‌سازی و تحویل</b>\n"
                "پس از تحویل نهایی، نتیجه از طریق همین ربات برای شما ارسال می‌شود."
            ),
        )

        await safe_edit_message(
            callback,
            (
                f"{ce('success')} "
                "<b>سفارش تأیید شد؛ آماده انجام و تحویل است</b>\n"
                "━━━━━━━━━━━━━━━━━━━━\n\n"

                f"🧾 شماره سفارش: <code>#{order.id}</code>\n"
                f"📦 محصول: <code>{escape(_package_short_title(order.product_id))}</code>\n"
                f"🎯 مقصد: <code>@{escape(order.target or '')}</code>\n\n"

                "💵 <b>اطلاعات مالی</b>\n"
                f"🔒 مبلغ قفل‌شده کاربر: "
                f"<code>{locked_price:,} تومان</code>\n"
                f"💳 مبلغ کسرشده: "
                f"<code>{reservation.amount:,} تومان</code>\n"
                "💱 نرخ جدید: <b>محاسبه نشد</b>\n\n"

                "<b>زمان‌بندی</b>\n"
                f"• ثبت سفارش: <code>{registered_at}</code>\n"
                f"• شروع پردازش: <code>{processing_at}</code>\n"
                "<i>به وقت تهران</i>\n\n"

                f"{ce('warning')} "
                "<b>فقط بعد از تحویل واقعی محصول، «ثبت تحویل سفارش» را بزنید.</b>\n"
                "اگر امکان انجام سفارش وجود ندارد، از «لغو و بازگشت وجه» استفاده کنید."
            ),
            manual_fulfillment_keyboard(
                order.id
            ),
        )

    except Exception as exc:
        logger.exception(
            "Manual owner approval failed | "
            "order=%s",
            order.id,
        )

        try:
            fresh = await service.get_order(
                order.id
            )

            if (
                fresh.status
                == STATUS_OWNER_PROCESSING
            ):
                await service.restore_owner_pending(
                    order.id,
                    error=(
                        f"Manual owner approval failed: "
                        f"{type(exc).__name__}: {exc}"
                    ),
                )

        except Exception:
            logger.exception(
                "Could not recover manual order state."
            )

        await safe_edit_message(
            callback,
            (
                f"{ce('error')} "
                "<b>تأیید سفارش با خطا روبه‌رو شد.</b>\n\n"
                "در صورت عدم کسر وجه، سفارش به‌صورت خودکار "
                "به حالت انتظار بررسی بازمی‌گردد."
            ),
            owner_keyboard(
                order.id
            ),
        )


# ============================================================
# User accepts/rejects increased price
# ============================================================

@router.callback_query(
    F.data.startswith(
        "accept_new_price_order_"
    )
)
async def accept_new_price_order(
    callback: CallbackQuery,
    db: Any = None,
    order_service: Optional[
        OrderService
    ] = None,
) -> None:
    try:
        order_id = int(
            str(
                callback.data
            ).removeprefix(
                "accept_new_price_order_"
            )
        )

    except (
        TypeError,
        ValueError,
    ):
        await safe_answer(
            callback,
            "شناسه سفارش نامعتبر است.",
            show_alert=True,
        )
        return

    service = await resolve_order_service(
        order_service,
        db,
    )

    try:
        order = await service.get_order(
            order_id
        )
    except Exception:
        await safe_answer(
            callback,
            "سفارش پیدا نشد.",
            show_alert=True,
        )
        return

    if int(
        order.user_id
    ) != int(
        callback.from_user.id
    ):
        await safe_answer(
            callback,
            "این سفارش متعلق به شما نیست.",
            show_alert=True,
        )
        return

    accepted = await service.accept_price_change(
        order_id,
        user_id=callback.from_user.id,
    )

    if not accepted:
        await safe_answer(
            callback,
            "این درخواست دیگر قابل تایید نیست.",
            show_alert=True,
        )
        return

    updated = await service.get_order(
        order_id
    )

    await safe_edit_message(
        callback,
        (
            f"{ce('success')} "
            "<b>مبلغ جدید سفارش تأیید شد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"🧾 شماره سفارش: <code>#{order_id}</code>\n"
            f"💰 مبلغ مورد تأیید: <code>{updated.price:,} تومان</code>\n\n"

            f"🕒 ثبت سفارش: "
            f"<code>{format_order_datetime(updated.created_at)}</code>\n"
            f"✅ تأیید مبلغ: "
            f"<code>{order_event_time(updated, 'updated_at')}</code>\n"
            "<i>به وقت تهران</i>\n\n"

            "<b>وضعیت: دوباره در انتظار بررسی مالک</b>\n"
            "هنوز هیچ مبلغی از کیف پول شما کسر نشده است."
        ),
        build_result_keyboard(),
    )

    # Re-notify owners so they can approve again.
    for admin_id in get_admin_ids():
        try:
            await callback.bot.send_message(
                chat_id=admin_id,
                text=premiumize_html(
                    (
                        f"{ce('success')} "
                        "<b>کاربر مبلغ جدید را تأیید کرد</b>\n"
                        "━━━━━━━━━━━━━━━━━━━━\n\n"
                        f"🧾 سفارش: <code>#{order_id}</code>\n"
                        f"💰 مبلغ تأییدشده: "
                        f"<code>{updated.price:,} تومان</code>\n\n"
                        f"🕒 ثبت سفارش: "
                        f"<code>{format_order_datetime(updated.created_at)}</code>\n"
                        f"✅ تأیید کاربر: "
                        f"<code>{order_event_time(updated, 'updated_at')}</code>\n"
                        "<i>به وقت تهران</i>\n\n"
                        "<b>سفارش دوباره آماده بررسی و تأیید شماست.</b>"
                    )
                ),
                reply_markup=owner_keyboard(
                    order_id
                ),
                parse_mode="HTML",
            )
        except Exception:
            logger.exception(
                "Could not re-notify owner | order=%s",
                order_id,
            )

    await safe_answer(
        callback,
        "✅ قیمت جدید تایید شد.",
        show_alert=True,
    )


@router.callback_query(
    F.data.startswith(
        "cancel_new_price_order_"
    )
)
async def cancel_new_price_order(
    callback: CallbackQuery,
    db: Any = None,
    order_service: Optional[
        OrderService
    ] = None,
) -> None:
    try:
        order_id = int(
            str(
                callback.data
            ).removeprefix(
                "cancel_new_price_order_"
            )
        )

    except (
        TypeError,
        ValueError,
    ):
        await safe_answer(
            callback,
            "شناسه سفارش نامعتبر است.",
            show_alert=True,
        )
        return

    service = await resolve_order_service(
        order_service,
        db,
    )

    try:
        order = await service.get_order(
            order_id
        )
    except Exception:
        await safe_answer(
            callback,
            "سفارش پیدا نشد.",
            show_alert=True,
        )
        return

    if int(
        order.user_id
    ) != int(
        callback.from_user.id
    ):
        await safe_answer(
            callback,
            "این سفارش متعلق به شما نیست.",
            show_alert=True,
        )
        return

    cancelled = await service.cancel_price_change(
        order_id,
        user_id=callback.from_user.id,
    )

    if not cancelled:
        await safe_answer(
            callback,
            "این درخواست دیگر قابل لغو نیست.",
            show_alert=True,
        )
        return

    cancelled_order = await service.get_order(
        order_id
    )

    await safe_edit_message(
        callback,
        (
            f"{ce('error')} "
            "<b>درخواست خرید لغو شد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"🧾 شماره سفارش: <code>#{order_id}</code>\n"
            f"🕒 ثبت سفارش: "
            f"<code>{format_order_datetime(cancelled_order.created_at)}</code>\n"
            f"❌ زمان لغو: "
            f"<code>{order_event_time(cancelled_order, 'updated_at')}</code>\n"
            "<i>به وقت تهران</i>\n\n"

            "<b>هیچ مبلغی از کیف پول شما کسر نشده است.</b>\n"
            "این درخواست بسته شد و در صورت نیاز می‌توانید سفارش جدیدی ثبت کنید."
        ),
        build_result_keyboard(),
    )

    await safe_answer(
        callback,
        "سفارش لغو شد.",
        show_alert=True,
    )


# ============================================================
# Manual fulfillment completion / refund
# ============================================================

@router.callback_query(
    F.data.startswith(
        "manual_complete_order_"
    )
)
async def manual_complete_order(
    callback: CallbackQuery,
    db: Any = None,
    order_service: Optional[
        OrderService
    ] = None,
) -> None:
    if not is_admin(
        callback.from_user.id
    ):
        await safe_answer(
            callback,
            "⛔ دسترسی ندارید.",
            show_alert=True,
        )
        return

    try:
        order_id = int(
            str(
                callback.data
            ).removeprefix(
                "manual_complete_order_"
            )
        )
    except (
        TypeError,
        ValueError,
    ):
        await safe_answer(
            callback,
            "شناسه سفارش نامعتبر است.",
            show_alert=True,
        )
        return

    service = await resolve_order_service(
        order_service,
        db,
    )

    try:
        order = await service.get_order(
            order_id
        )
    except Exception:
        await safe_answer(
            callback,
            "سفارش پیدا نشد.",
            show_alert=True,
        )
        return

    completed = await service.complete(
        order_id,
        market_order_id=(
            f"manual:{int(callback.from_user.id)}"
        ),
    )

    if not completed:
        await safe_answer(
            callback,
            "این سفارش در وضعیت قابل تکمیل نیست.",
            show_alert=True,
        )
        return

    completed_order = await service.get_order(
        order_id
    )

    registered_at = format_order_datetime(
        completed_order.created_at
    )

    processing_at = order_event_time(
        completed_order,
        "provider_started_at",
        "updated_at",
    )

    completed_at = order_event_time(
        completed_order,
        "completed_at",
        "updated_at",
    )

    completion_text = (
        f"{ce('success')} "
        "<b>سفارش با موفقیت تحویل شد 🎉</b>\n"
        "━━━━━━━━━━━━━━━━━━━━\n\n"

        f"🧾 شماره سفارش\n"
        f"<code>#{order_id}</code>\n\n"

        f"📦 محصول\n"
        f"<code>{escape(_package_short_title(order.product_id))}</code>\n\n"

        f"🎯 مقصد\n"
        f"<code>@{escape(order.target or '')}</code>\n\n"

        f"💰 مبلغ نهایی\n"
        f"<code>{order.price:,} تومان</code>\n\n"

        "<b>زمان‌بندی سفارش</b>\n"
        f"• ثبت: <code>{registered_at}</code>\n"
        f"• شروع پردازش: <code>{processing_at}</code>\n"
        f"• تحویل نهایی: <code>{completed_at}</code>\n"
        "<i>به وقت تهران</i>\n\n"

        f"{ce('success')} <b>وضعیت نهایی: تکمیل‌شده</b>\n\n"
        "از خرید شما از <b>Star Vanc</b> ممنونیم 💜"
    )

    # --------------------------------------------------------
    # Dynamic branded receipt image
    # --------------------------------------------------------

    buyer_display = (
        f"User {order.user_id}"
    )

    try:
        buyer_chat = await callback.bot.get_chat(
            order.user_id
        )

        if getattr(
            buyer_chat,
            "username",
            None,
        ):
            buyer_display = (
                f"@{buyer_chat.username}"
            )
        else:
            buyer_display = (
                getattr(
                    buyer_chat,
                    "full_name",
                    None,
                )
                or buyer_display
            )

    except Exception:
        logger.debug(
            "Could not resolve buyer display name for receipt | user=%s",
            order.user_id,
            exc_info=True,
        )

    receipt_path = None

    try:
        receipt_path = await render_completed_order_receipt(
            CompletedOrderReceipt(
                order_id=order_id,
                completed_at=completed_at,
                buyer=buyer_display,
                product=_package_short_title(
                    order.product_id
                ),
                amount_toman=int(
                    order.price
                    or 0
                ),
            )
        )

        await callback.bot.send_photo(
            chat_id=order.user_id,
            photo=FSInputFile(
                str(receipt_path)
            ),
            caption=premiumize_html(
                completion_text
            ),
            parse_mode="HTML",
            reply_markup=build_result_keyboard(),
        )

    except TelegramForbiddenError:
        logger.warning(
            "Completed-order receipt could not be sent because bot is blocked | user=%s order=%s",
            order.user_id,
            order_id,
        )

    except Exception:
        logger.exception(
            "Dynamic completed-order receipt failed | user=%s order=%s",
            order.user_id,
            order_id,
        )

        # Important fallback: even if Pillow/font/template fails,
        # the existing completion message is still delivered.
        await notify_user(
            callback.bot,
            order.user_id,
            completion_text,
        )


    await safe_edit_message(
        callback,
        (
            f"{ce('success')} "
            "<b>تحویل سفارش ثبت شد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"
            f"🧾 سفارش: <code>#{order_id}</code>\n"
            f"📦 محصول: <code>{escape(_package_short_title(order.product_id))}</code>\n"
            f"🎯 مقصد: <code>@{escape(order.target or '')}</code>\n\n"
            f"🕒 ثبت: <code>{registered_at}</code>\n"
            f"⚙️ شروع پردازش: <code>{processing_at}</code>\n"
            f"✅ تحویل نهایی: <code>{completed_at}</code>\n"
            "<i>به وقت تهران</i>\n\n"
            "<b>وضعیت سفارش به «تکمیل‌شده» تغییر کرد.</b>"
        ),
        None,
    )

    await safe_answer(
        callback,
        "✅ تکمیل شد.",
        show_alert=True,
    )


@router.callback_query(
    F.data.startswith(
        "manual_refund_order_"
    )
)
async def manual_refund_order(
    callback: CallbackQuery,
    db: Any = None,
    order_service: Optional[
        OrderService
    ] = None,
) -> None:
    if not is_admin(
        callback.from_user.id
    ):
        await safe_answer(
            callback,
            "⛔ دسترسی ندارید.",
            show_alert=True,
        )
        return

    try:
        order_id = int(
            str(
                callback.data
            ).removeprefix(
                "manual_refund_order_"
            )
        )
    except (
        TypeError,
        ValueError,
    ):
        await safe_answer(
            callback,
            "شناسه سفارش نامعتبر است.",
            show_alert=True,
        )
        return

    service = await resolve_order_service(
        order_service,
        db,
    )

    try:
        order = await service.get_order(
            order_id
        )
    except Exception:
        await safe_answer(
            callback,
            "سفارش پیدا نشد.",
            show_alert=True,
        )
        return

    refunded = await service.refund_explicit_provider_failure(
        order_id,
        error=(
            "Manual fulfillment cancelled by owner."
        ),
    )

    if not refunded:
        await safe_answer(
            callback,
            "بازگشت وجه انجام نشد؛ وضعیت سفارش را بررسی کنید.",
            show_alert=True,
        )
        return

    refunded_order = await service.get_order(
        order_id
    )

    registered_at = format_order_datetime(
        refunded_order.created_at
    )

    processing_at = order_event_time(
        refunded_order,
        "provider_started_at",
        "updated_at",
    )

    refunded_at = order_event_time(
        refunded_order,
        "refunded_at",
        "updated_at",
    )

    await notify_user(
        callback.bot,
        order.user_id,
        (
            f"{ce('warning')} "
            "<b>سفارش لغو شد و وجه به کیف پول شما بازگشت</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"

            f"🧾 شماره سفارش: <code>#{order_id}</code>\n"
            f"📦 محصول: <code>{escape(_package_short_title(order.product_id))}</code>\n"
            f"💰 مبلغ بازگشتی: <code>{order.price:,} تومان</code>\n\n"

            "<b>زمان‌بندی</b>\n"
            f"• ثبت سفارش: <code>{registered_at}</code>\n"
            f"• شروع پردازش: <code>{processing_at}</code>\n"
            f"• بازگشت وجه: <code>{refunded_at}</code>\n"
            "<i>به وقت تهران</i>\n\n"

            f"{ce('success')} "
            "<b>مبلغ کامل این سفارش به موجودی کیف پول شما اضافه شد.</b>"
        ),
    )

    await safe_edit_message(
        callback,
        (
            f"{ce('success')} "
            "<b>لغو سفارش و بازگشت وجه ثبت شد</b>\n"
            "━━━━━━━━━━━━━━━━━━━━\n\n"
            f"🧾 سفارش: <code>#{order_id}</code>\n"
            f"📦 محصول: <code>{escape(_package_short_title(order.product_id))}</code>\n"
            f"💰 مبلغ بازگشتی: <code>{order.price:,} تومان</code>\n\n"
            f"🕒 ثبت: <code>{registered_at}</code>\n"
            f"⚙️ شروع پردازش: <code>{processing_at}</code>\n"
            f"↩️ بازگشت وجه: <code>{refunded_at}</code>\n"
            "<i>به وقت تهران</i>\n\n"
            "<b>وضعیت نهایی سفارش: لغوشده با بازگشت کامل وجه</b>"
        ),
        None,
    )

    await safe_answer(
        callback,
        "↩️ وجه بازگردانده شد.",
        show_alert=True,
    )


# ============================================================
# Unsupported legacy service callbacks
# ============================================================

@router.callback_query(
    F.data.in_(
        {
            "buy_ton",
            "gift_stars",
            "boost_telegram",
            "buy_trx",
            "star_reaction",
            "star_giveaway",
        }
    )
)
async def unsupported_service(
    callback: CallbackQuery,
) -> None:
    await safe_answer(
        callback,
        "این سرویس هنوز در این مسیر فعال نشده است.",
        show_alert=True,
    )


__all__ = [
    "router",
    "PACKAGE_NAMES",
    "PREMIUM_MONTHS",
    "STARS_AMOUNTS",
]