# utils/finance.py

from __future__ import annotations

import asyncio
import logging
import os
import sqlite3
import threading
from decimal import Decimal, InvalidOperation
from pathlib import Path
from typing import Any, Final, Optional

from core.config import settings


logger = logging.getLogger(__name__)


# ============================================================
# Constants
# ============================================================

SUPPORTED_RATES: Final[frozenset[str]] = frozenset(
    {
        "usdt",
        "ton",
        "stars",
        "premium_monthly",
    }
)

TABLE_NAME: Final[str] = "bot_finance_rates"


# ============================================================
# Helpers
# ============================================================

def _resolve_db_path() -> str:
    value = (
        getattr(settings, "DB_PATH", None)
        or getattr(settings, "DATABASE_PATH", None)
        or "matrix_bot.db"
    )

    return str(value)


def _to_decimal(
    value: Any,
) -> Optional[Decimal]:
    try:
        result = Decimal(str(value))

    except (
        InvalidOperation,
        TypeError,
        ValueError,
    ):
        return None

    if (
        not result.is_finite()
        or result <= 0
    ):
        return None

    return result


# ============================================================
# Finance Manager
# ============================================================

class FinanceManager:
    """
    Persistent finance-rate manager.

    Stored rates:
        usdt
            Toman per 1 USDT.

        ton
            Toman per 1 TON.

        stars
            Toman per 1 Telegram Star.

        premium_monthly
            Toman per one month of Telegram Premium.

    Tabdeal integration:
        sync_rates() refreshes only:
            usdt
            ton

        Stars/Premium are product selling prices and therefore remain
        admin-controlled.

    No Tabdeal private API credential is needed for public market data.
    """

    def __init__(
        self,
        db_path: str | Path | None = None,
    ) -> None:
        self.db_path = str(
            db_path
            or _resolve_db_path()
        )

        self._write_lock = (
            threading.RLock()
        )

        self._ensure_schema()

        self._seed_from_environment()

    # ========================================================
    # SQLite
    # ========================================================

    def _connect(
        self,
    ) -> sqlite3.Connection:
        conn = sqlite3.connect(
            self.db_path,
            timeout=30,
            check_same_thread=False,
        )

        conn.row_factory = sqlite3.Row

        conn.execute(
            "PRAGMA busy_timeout = 30000"
        )

        return conn

    def _ensure_schema(
        self,
    ) -> None:
        with self._write_lock:
            conn = self._connect()

            try:
                conn.execute(
                    f"""
                    CREATE TABLE IF NOT EXISTS {TABLE_NAME} (
                        rate_key TEXT PRIMARY KEY,
                        rate_value TEXT NOT NULL,
                        source TEXT NOT NULL DEFAULT 'admin',
                        updated_at TIMESTAMP NOT NULL
                            DEFAULT CURRENT_TIMESTAMP
                    )
                    """
                )

                conn.commit()

            finally:
                conn.close()

    # ========================================================
    # Environment Seed
    # ========================================================

    def _seed_from_environment(
        self,
    ) -> None:
        """
        Optional first-run seed.

        Existing DB values are never overwritten.

        Supported optional env names:
            FINANCE_USDT_TOMAN
            FINANCE_TON_TOMAN
            FINANCE_STARS_TOMAN
            FINANCE_PREMIUM_MONTHLY_TOMAN
        """
        env_map = {
            "usdt":
                "FINANCE_USDT_TOMAN",

            "ton":
                "FINANCE_TON_TOMAN",

            "stars":
                "FINANCE_STARS_TOMAN",

            "premium_monthly":
                "FINANCE_PREMIUM_MONTHLY_TOMAN",
        }

        for key, env_name in (
            env_map.items()
        ):
            if self.get_rate(key) is not None:
                continue

            raw = os.getenv(
                env_name,
                "",
            ).strip()

            if not raw:
                continue

            value = _to_decimal(
                raw
            )

            if value is None:
                logger.warning(
                    "Invalid %s ignored.",
                    env_name,
                )
                continue

            self.update_rate(
                key,
                value,
                source="env",
            )

    # ========================================================
    # Public API
    # ========================================================

    @staticmethod
    def normalize_key(
        key: str,
    ) -> str:
        result = str(
            key
            or ""
        ).strip().lower()

        aliases = {
            "premium":
                "premium_monthly",

            "premium_month":
                "premium_monthly",

            "usdt_toman":
                "usdt",

            "ton_toman":
                "ton",
        }

        result = aliases.get(
            result,
            result,
        )

        if result not in SUPPORTED_RATES:
            raise ValueError(
                f"Unsupported finance rate: {result}"
            )

        return result

    def get_rate(
        self,
        key: str,
    ) -> Optional[Decimal]:
        key = self.normalize_key(
            key
        )

        conn = self._connect()

        try:
            row = conn.execute(
                f"""
                SELECT rate_value
                FROM {TABLE_NAME}
                WHERE rate_key = ?
                LIMIT 1
                """,
                (
                    key,
                ),
            ).fetchone()

            if not row:
                return None

            return _to_decimal(
                row["rate_value"]
            )

        finally:
            conn.close()

    def update_rate(
        self,
        key: str,
        value: Any,
        *,
        source: str = "admin",
    ) -> bool:
        key = self.normalize_key(
            key
        )

        decimal_value = _to_decimal(
            value
        )

        if decimal_value is None:
            return False

        source = str(
            source
            or "admin"
        ).strip()[:50]

        with self._write_lock:
            conn = self._connect()

            try:
                conn.execute(
                    "BEGIN IMMEDIATE"
                )

                conn.execute(
                    f"""
                    INSERT INTO {TABLE_NAME} (
                        rate_key,
                        rate_value,
                        source,
                        updated_at
                    )
                    VALUES (?, ?, ?, CURRENT_TIMESTAMP)

                    ON CONFLICT(rate_key)
                    DO UPDATE SET
                        rate_value = excluded.rate_value,
                        source = excluded.source,
                        updated_at = CURRENT_TIMESTAMP
                    """,
                    (
                        key,
                        format(
                            decimal_value,
                            "f",
                        ),
                        source,
                    ),
                )

                conn.commit()

                logger.info(
                    "Finance rate updated | "
                    "key=%s source=%s",
                    key,
                    source,
                )

                return True

            except Exception:
                conn.rollback()

                logger.exception(
                    "Could not update finance rate | "
                    "key=%s",
                    key,
                )

                return False

            finally:
                conn.close()

    def delete_rate(
        self,
        key: str,
    ) -> bool:
        key = self.normalize_key(
            key
        )

        with self._write_lock:
            conn = self._connect()

            try:
                cursor = conn.execute(
                    f"""
                    DELETE FROM {TABLE_NAME}
                    WHERE rate_key = ?
                    """,
                    (
                        key,
                    ),
                )

                conn.commit()

                return cursor.rowcount > 0

            finally:
                conn.close()

    def get_all_rates(
        self,
    ) -> dict[str, Optional[Decimal]]:
        return {
            key: self.get_rate(
                key
            )
            for key
            in sorted(
                SUPPORTED_RATES
            )
        }

    @staticmethod
    def format_currency(
        value: Any,
    ) -> str:
        decimal_value = _to_decimal(
            value
        )

        if decimal_value is None:
            return "نامشخص"

        if (
            decimal_value
            == decimal_value.to_integral()
        ):
            return (
                f"{int(decimal_value):,}"
            )

        text = format(
            decimal_value.normalize(),
            "f",
        )

        if "." in text:
            integer, fraction = (
                text.split(
                    ".",
                    1,
                )
            )

            try:
                integer = (
                    f"{int(integer):,}"
                )

            except ValueError:
                pass

            fraction = (
                fraction.rstrip("0")
            )

            return (
                f"{integer}.{fraction}"
                if fraction
                else integer
            )

        return text

    # ========================================================
    # Tabdeal Sync
    # ========================================================

    async def sync_rates(
        self,
    ) -> dict[str, Any]:
        """
        Refresh market conversion rates.

        USDT/Toman:
            Tabdeal -> Wallex -> Bitpin -> Nobitex -> Exir -> Sarrafex

        TON-network native coin/Toman:
            Tabdeal GRAM/TON market discovery.

        Premium/Stars product base prices are not used by the active
        MarketApp pricing flow.
        """
        from services.iran_usdt_rate_service import (
            IranUsdtRateService,
        )
        from services.tabdeal_rate_service import (
            TabdealRateService,
        )

        usdt_client = IranUsdtRateService(
            timeout=4.0,
            cache_ttl=15.0,
            stale_ttl=300.0,
        )

        native_client = TabdealRateService(
            timeout=6.0,
            cache_ttl=15.0,
            retries=2,
        )

        errors: dict[str, str] = {}
        updated: dict[str, Decimal] = {}
        sources: dict[str, str] = {}

        try:
            # USDT/Toman is independent from native-coin pricing.
            try:
                quote = await usdt_client.get_quote(
                    force_refresh=True,
                    allow_stale=True,
                )

                persisted = self.update_rate(
                    "usdt",
                    quote.rate_toman,
                    source=f"iran:{quote.source}",
                )

                if persisted:
                    updated["usdt"] = quote.rate_toman
                    sources["usdt"] = quote.source
                else:
                    errors["usdt"] = (
                        "Could not persist USDT/Toman."
                    )

            except Exception as exc:
                errors["usdt"] = (
                    f"{type(exc).__name__}: {exc}"
                )
                logger.warning(
                    "Multi-exchange USDT sync failed | error=%s",
                    exc,
                )

            # GRAM/TON -> Toman remains independent.
            try:
                native_toman = (
                    await native_client
                    .get_ton_toman_rate(
                        force_refresh=True
                    )
                )

                persisted = self.update_rate(
                    "ton",
                    native_toman,
                    source="tabdeal_native",
                )

                if persisted:
                    updated["ton"] = native_toman
                    sources["ton"] = "tabdeal_native"
                else:
                    errors["ton"] = (
                        "Could not persist native/Toman."
                    )

            except Exception as exc:
                errors["ton"] = (
                    f"{type(exc).__name__}: {exc}"
                )
                logger.warning(
                    "Native GRAM/TON sync failed | error=%s",
                    exc,
                )

            if not updated:
                raise RuntimeError(
                    "No market rate could be refreshed. "
                    + " | ".join(
                        f"{key}={value}"
                        for key, value in errors.items()
                    )
                )

            return {
                "success": not errors,
                "partial": bool(errors),
                "updated": tuple(sorted(updated.keys())),
                "errors": errors,
                "sources": sources,
                "usdt": self.get_rate("usdt"),
                "ton": self.get_rate("ton"),
            }

        finally:
            await usdt_client.close()
            await native_client.close()


# ============================================================
# Shared Singleton
# ============================================================

finance_manager = FinanceManager()


__all__ = [
    "FinanceManager",
    "finance_manager",
    "SUPPORTED_RATES",
]
