# main.py

from __future__ import annotations

import asyncio
import logging
import sys
from typing import Any

from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode

from core.config import settings
from core.database import DatabaseManager

from middlewares.auth_check import AuthCheckMiddleware
from middlewares.throttling import ThrottlingMiddleware

from services.order_service import OrderService
from services.product_pricing_service import ProductPricingService
from services.tabdeal_rate_service import TabdealRateService


# =========================================================
# Logging
# =========================================================

logging.basicConfig(
    level=(
        logging.DEBUG
        if bool(
            getattr(
                settings,
                "DEBUG_MODE",
                False,
            )
        )
        else logging.INFO
    ),
    format=(
        "%(asctime)s - %(levelname)s - "
        "[%(filename)s:%(lineno)d] - %(message)s"
    ),
    handlers=[
        logging.StreamHandler(
            sys.stdout
        ),
        logging.FileHandler(
            "matrix_bot.log",
            encoding="utf-8",
        ),
    ],
)

logger = logging.getLogger(
    __name__
)


# =========================================================
# Routers
# =========================================================

from handlers.user.start import (
    router as user_start_router,
)
from handlers.user.wallet import (
    router as user_wallet_router,
)
from handlers.user.kyc import (
    router as user_kyc_router,
)
from handlers.user.shop import (
    router as user_shop_router,
)
from handlers.user.referral import (
    router as user_referral_router,
)

# Admin panel + unified admin actions.
#
# All visible admin buttons are handled by these two routers.
# We intentionally do NOT register the old scattered finance/KYC routers
# here, because they contained overlapping callbacks and broad message
# handlers that could swallow later admin FSM messages.
from handlers.admin.panel import (
    router as admin_panel_router,
)
from handlers.admin.actions import (
    router as admin_actions_router,
)


# =========================================================
# Helpers
# =========================================================

def get_setting(
    name: str,
    default: Any = None,
):
    """
    خواندن امن تنظیمات Pydantic Settings.
    """
    value = getattr(
        settings,
        name,
        default,
    )

    if value is None:
        return default

    if isinstance(
        value,
        str,
    ):
        value = value.strip()

    return value


def get_database_path() -> str:
    """
    تنظیمات فعلی پروژه از DB_PATH استفاده می‌کند.

    DATABASE_PATH فقط برای سازگاری با نسخه‌های قدیمی
    به عنوان fallback نگه داشته شده است.
    """
    value = (
        get_setting(
            "DB_PATH",
            None,
        )
        or get_setting(
            "DATABASE_PATH",
            None,
        )
        or "matrix_bot.db"
    )

    return str(value)


async def close_service(
    service: Any,
    service_name: str,
) -> None:
    """
    بستن امن سرویس‌های sync/async.
    """
    if service is None:
        return

    close_method = getattr(
        service,
        "close",
        None,
    )

    if not callable(
        close_method
    ):
        return

    try:
        result = close_method()

        if asyncio.iscoroutine(
            result
        ):
            await result

        logger.info(
            "%s closed successfully.",
            service_name,
        )

    except Exception:
        logger.exception(
            "Could not close %s.",
            service_name,
        )


# =========================================================
# Startup
# =========================================================

async def on_startup(
    bot: Bot,
    dispatcher: Dispatcher,
) -> None:
    """
    آماده‌سازی سرویس‌ها قبل از شروع کار ربات.

    ترتیب:
    1) Bot identity
    2) Main database
    3) Order schema
    4) Product pricing status
    5) TON watcher
    """

    # -----------------------------------------------------
    # Bot
    # -----------------------------------------------------

    bot_info = await bot.get_me()

    logger.info(
        "Bot authorized successfully: @%s | ID: %s",
        bot_info.username,
        bot_info.id,
    )

    # -----------------------------------------------------
    # Main Database
    # -----------------------------------------------------

    db: DatabaseManager = dispatcher[
        "db"
    ]

    await db.init()

    logger.info(
        "Database initialized successfully | path=%s",
        get_database_path(),
    )

    # -----------------------------------------------------
    # Order Service / migration
    # -----------------------------------------------------

    order_service: OrderService = dispatcher[
        "order_service"
    ]

    await order_service.ensure_schema()

    logger.info(
        "Order service schema verified successfully."
    )

    product_pricing: ProductPricingService = dispatcher[
        "product_pricing"
    ]

    await product_pricing.ensure_schema()

    logger.info(
        "Product pricing schema verified successfully."
    )

    # -----------------------------------------------------
    # Fulfillment mode
    # -----------------------------------------------------

    logger.info(
        "Product pricing mode: admin USDT base prices + "
        "live Iranian USDT/Toman fallback."
    )

    logger.info(
        "Delivery mode: owner manual fulfillment."
    )

    # -----------------------------------------------------
    # Admin / Owner approval configuration
    # -----------------------------------------------------

    get_admin_list = getattr(
        settings,
        "get_admin_list",
        None,
    )

    admin_ids: list[int] = []

    if callable(
        get_admin_list
    ):
        try:
            admin_ids = list(
                get_admin_list()
            )
        except Exception:
            logger.exception(
                "Could not parse ADMIN_IDS."
            )

    if not admin_ids:
        logger.warning(
            "ADMIN_IDS is empty. "
            "Owner-approval purchase requests cannot be approved."
        )
    else:
        logger.info(
            "Owner approval enabled | admins=%s",
            len(admin_ids),
        )

    # -----------------------------------------------------
    # Ready
    # -----------------------------------------------------

    logger.info(
        "=========================================="
    )
    logger.info(
        "Matrix Telegram Bot is ready."
    )
    logger.info(
        "Purchase flow: User -> Owner Approval -> Manual Fulfillment"
    )
    logger.info(
        "=========================================="
    )


# =========================================================
# Shutdown
# =========================================================

async def on_shutdown(
    bot: Bot,
    dispatcher: Dispatcher,
) -> None:
    """
    بستن امن منابع.
    """

    logger.info(
        "Starting graceful shutdown..."
    )

    await close_service(
        dispatcher.get(
            "tabdeal_rates"
        ),
        "Tabdeal rate service",
    )

    await close_service(
        dispatcher.get(
            "db"
        ),
        "Database manager",
    )

    # OrderService از sqlite connection دائمی استفاده نمی‌کند
    # و نیاز به close ندارد.

    logger.info(
        "Graceful shutdown completed."
    )


# =========================================================
# Main
# =========================================================

async def main() -> None:
    logger.info(
        "Starting Matrix Telegram Bot..."
    )

    # =====================================================
    # Validate Bot Token
    # =====================================================

    bot_token = get_setting(
        "BOT_TOKEN",
        "",
    )

    if not bot_token:
        logger.critical(
            "BOT_TOKEN is missing."
        )
        return

    placeholder_tokens = {
        "YOUR_BOT_TOKEN_HERE",
        "PASTE_YOUR_NEW_BOT_TOKEN_HERE",
    }

    if bot_token in placeholder_tokens:
        logger.critical(
            "BOT_TOKEN still contains a placeholder value."
        )
        return

    # =====================================================
    # Bot / Dispatcher
    # =====================================================

    bot = Bot(
        token=bot_token,
        default=DefaultBotProperties(
            parse_mode=ParseMode.HTML,
        ),
    )

    dp = Dispatcher()

    # =====================================================
    # Database
    # =====================================================

    db_path = get_database_path()

    db_manager = DatabaseManager(
        db_path=db_path
    )

    dp["db"] = db_manager

    logger.info(
        "Database manager registered | path=%s",
        db_path,
    )

    # =====================================================
    # Order Service
    # =====================================================

    order_service = OrderService(
        db_path=db_path
    )

    dp["order_service"] = order_service

    logger.info(
        "Order service registered."
    )

    product_pricing = ProductPricingService(
        db_path=db_path
    )

    dp["product_pricing"] = product_pricing

    logger.info(
        "Product pricing service registered."
    )

    # =====================================================
    # Tabdeal Live Rates
    # =====================================================

    # Public market-data endpoints are used here.
    # No API key/secret is stored in the bot for rate reads.
    tabdeal_rates = TabdealRateService(
        timeout=10.0,
        cache_ttl=10.0,
        retries=3,
    )

    dp["tabdeal_rates"] = tabdeal_rates

    logger.info(
        "Tabdeal live-rate service registered."
    )

    # =====================================================
    # Product pricing / fulfillment
    # =====================================================

    logger.info(
        "Product pricing mode enabled | "
        "admin USDT base prices + manual fulfillment."
    )

    # =====================================================
    # Middlewares
    # =====================================================

    try:
        auth_middleware = AuthCheckMiddleware()

        dp.message.middleware(
            auth_middleware
        )

        dp.callback_query.middleware(
            AuthCheckMiddleware()
        )

        logger.info(
            "AuthCheckMiddleware registered."
        )

    except Exception:
        logger.exception(
            "Could not register AuthCheckMiddleware."
        )

        try:
            await bot.session.close()
        except Exception:
            pass

        raise

    # -----------------------------------------------------
    # Throttling
    # -----------------------------------------------------

    rate_limit = get_setting(
        "RATE_LIMIT_DEFAULT",
        1.0,
    )

    try:
        rate_limit = float(
            rate_limit
        )

        if rate_limit <= 0:
            raise ValueError

    except (
        TypeError,
        ValueError,
    ):
        rate_limit = 1.0

    dp.message.middleware(
        ThrottlingMiddleware(
            rate_limit=rate_limit,
        )
    )

    dp.callback_query.middleware(
        ThrottlingMiddleware(
            rate_limit=rate_limit,
        )
    )

    logger.info(
        "ThrottlingMiddleware registered | rate=%.2fs",
        rate_limit,
    )

    # =====================================================
    # Routers
    # =====================================================

    # ترتیب Routerها عمداً مشخص است.
    # shop router شامل callbackهای مالک:
    # owner_approve_order_*
    # owner_reject_order_*
    # است و نیاز به Router جداگانه ندارد.

    # -----------------------------------------------------
    # Admin Routers FIRST
    # -----------------------------------------------------

    dp.include_router(
        admin_panel_router
    )

    dp.include_router(
        admin_actions_router
    )

    logger.info(
        "Admin routers registered | panel + unified actions."
    )

    # -----------------------------------------------------
    # User Routers
    # -----------------------------------------------------

    dp.include_router(
        user_start_router
    )
    dp.include_router(
        user_wallet_router
    )
    dp.include_router(
        user_referral_router
    )
    dp.include_router(
        user_kyc_router
    )
    dp.include_router(
        user_shop_router
    )

    logger.info(
        "All routers registered."
    )

    # =====================================================
    # Startup / Shutdown
    # =====================================================

    dp.startup.register(
        on_startup
    )

    dp.shutdown.register(
        on_shutdown
    )

    # =====================================================
    # Polling
    # =====================================================

    try:
        logger.info(
            "Removing webhook and pending updates..."
        )

        await bot.delete_webhook(
            drop_pending_updates=True
        )

        logger.info(
            "Bot is starting polling..."
        )

        await dp.start_polling(
            bot,
            allowed_updates=(
                dp.resolve_used_update_types()
            ),
        )

    except asyncio.CancelledError:
        logger.info(
            "Polling cancelled."
        )
        raise

    except Exception:
        logger.exception(
            "Critical error during polling."
        )

    finally:
        logger.info(
            "Closing Telegram bot session..."
        )

        try:
            await bot.session.close()

        except Exception:
            logger.exception(
                "Error closing Telegram bot session."
            )


# =========================================================
# Entry Point
# =========================================================

if __name__ == "__main__":
    try:
        asyncio.run(
            main()
        )

    except KeyboardInterrupt:
        logger.info(
            "Bot stopped manually."
        )

    except SystemExit:
        logger.info(
            "Bot exited."
        )