"""
BRL -> MMK conversion helpers.

Uses an optional env override for FX rate, otherwise attempts a public FX API.
"""

from __future__ import annotations

import os
import re
import time
from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from typing import Optional

import requests


_FX_CACHE: dict[str, object] = {}
# Cached (rate, unix_ts) for live 1 PHP = X BRL from public API
_PHP_BRL_LIVE: tuple[Decimal, int] | None = None


def invalidate_brl_fx_cache() -> None:
    _FX_CACHE.pop("brl_to_mmk", None)


def _shop_php_mmk() -> Optional[Decimal]:
    try:
        import models

        v = models.shop_fx_get_rate("php_to_mmk_rate")
        if v is not None:
            return Decimal(str(v))
    except Exception:
        pass
    return None


def _shop_brl_fallback_mmk() -> Optional[Decimal]:
    try:
        import models

        v = models.shop_fx_get_rate("brl_to_mmk_fallback_rate")
        if v is not None:
            return Decimal(str(v))
    except Exception:
        pass
    return None


def _parse_mixed_brl_number(raw_num: str) -> Decimal:
    """
    Parse numeric portion from price strings that may use:
    - Brazilian: 1.234,56
    - Dot-as-decimal (site UI): 82.80
    """
    s = raw_num.strip()
    if not s:
        raise ValueError("empty")

    if "," in s:
        # Brazilian thousands (.) + decimal (,)
        cleaned = s.replace(".", "").replace(",", ".")
        return Decimal(cleaned)

    if "." not in s:
        return Decimal(s)

    parts = s.split(".")
    if len(parts) == 2 and len(parts[1]) <= 2:
        # Single dot, 1–2 fractional digits → decimal separator (e.g. 82.80)
        return Decimal(f"{parts[0]}.{parts[1]}")

    # Multiple dots or long last segment → thousands-only separators
    if len(parts[-1]) <= 2 and len(parts) >= 2:
        int_part = "".join(parts[:-1])
        return Decimal(f"{int_part}.{parts[-1]}")

    return Decimal(s.replace(".", ""))


@dataclass(frozen=True)
class FxRate:
    rate: Decimal  # 1 BRL -> MMK
    source: str
    fetched_at: int


def parse_brl_to_cents(price_text: str) -> Optional[int]:
    """
    Parse strings like:
      - "R$ 1.234,56"
      - "R$1,23"
    Returns cents as integer (BRL*100).
    """
    if not price_text:
        return None

    t = price_text.strip()
    # Extract the numeric portion after "R$"
    m = re.search(r"R\$\s*([0-9\.\,]+)", t, flags=re.IGNORECASE)
    if not m:
        # Fallback: maybe already only numeric
        m2 = re.search(r"([0-9\.\,]+)", t)
        if not m2:
            return None
        raw_num = m2.group(1)
    else:
        raw_num = m.group(1)

    # Brazilian: "." thousands and "," decimal (e.g. 1.234,56).
    # Smile.one sometimes renders US-style decimals: "R$ 82.80" (no comma).
    try:
        d = _parse_mixed_brl_number(raw_num)
    except Exception:
        return None

    cents = (d * Decimal(100)).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
    return int(cents)


def _fetch_php_to_brl_live() -> Optional[Decimal]:
    """
    Mid-market: 1 PHP equals how many BRL (same intermediate unit BR storefront uses before BRL→MMK).
    Source: open.er-api.com latest PHP rates.
    """
    try:
        r = requests.get(
            "https://open.er-api.com/v6/latest/PHP",
            headers={"User-Agent": "Mozilla/5.0 (compatible; SmileHub/1.0)"},
            timeout=10,
        )
        if r.status_code != 200:
            return None
        data = r.json()
        brl = (data.get("rates") or {}).get("BRL")
        if brl is None:
            return None
        return Decimal(str(brl))
    except Exception:
        return None


def get_php_to_brl_rate(cache_ttl_s: int = 3600) -> Decimal:
    """
    1 PHP → BRL (for converting ₱ prices into the same BRL-cent pipeline as Brazil /br).

    Priority:
    1) env PHP_TO_BRL_RATE (manual)
    2) Cached live API rate (open.er-api.com, refreshed every cache_ttl_s)
    3) config DEFAULT_PHP_TO_BRL_RATE (~mid-market fallback)
    """
    env = (os.getenv("PHP_TO_BRL_RATE") or "").strip()
    if env:
        return Decimal(env)

    global _PHP_BRL_LIVE
    now = int(time.time())
    if _PHP_BRL_LIVE is not None:
        rate, ts = _PHP_BRL_LIVE
        if now - ts < cache_ttl_s:
            return rate

    live = _fetch_php_to_brl_live()
    if live is not None and live > 0:
        _PHP_BRL_LIVE = (live, now)
        return live

    try:
        from config import DEFAULT_PHP_TO_BRL_RATE

        return Decimal(str(DEFAULT_PHP_TO_BRL_RATE))
    except Exception:
        return Decimal("0.089")


def parse_php_amount_from_text(price_text: str) -> Optional[Decimal]:
    """Extract numeric PHP amount from a ₱ / PHP price string.

    When multiple amounts exist (sale + list), use the **minimum** (sale) so
    catalog php_cents matches what order_automation_ph row matching expects.
    """
    t = (price_text or "").strip()
    if not t:
        return None
    if not ("₱" in t or "\u20b1" in t or re.search(r"\bPHP\s*[\d]", t, flags=re.IGNORECASE)):
        return None
    amounts: list[Decimal] = []
    for m in re.finditer(r"(?:₱|\u20B1)\s*([0-9][0-9\.,]*)", t, flags=re.IGNORECASE):
        raw_num = m.group(1).strip().replace(",", "")
        try:
            d = Decimal(raw_num)
        except Exception:
            continue
        if d > 0 and d <= 500_000:
            amounts.append(d)
    for m in re.finditer(r"PHP\s*([0-9][0-9\.,]*)", t, flags=re.IGNORECASE):
        raw_num = m.group(1).strip().replace(",", "")
        try:
            d = Decimal(raw_num)
        except Exception:
            continue
        if d > 0 and d <= 500_000:
            amounts.append(d)
    if not amounts:
        return None
    return min(amounts)


def php_amount_to_mmk_str(php_amt: Decimal, markup_pct: float) -> tuple[str, Decimal, str]:
    """
    Direct retail MMK for Philippines: MMK = ₱ × (MMK per 1 PHP) × (1+markup).
    Admin DB overrides env PHP_TO_MMK_RATE when set.
    """
    shop = _shop_php_mmk()
    try:
        from config import DEFAULT_PHP_TO_MMK_RATE
    except Exception:
        DEFAULT_PHP_TO_MMK_RATE = 85

    if shop is not None:
        mmk_per_php = shop
        src = "admin:PHP_TO_MMK_RATE"
    else:
        s = (os.getenv("PHP_TO_MMK_RATE") or "").strip() or str(DEFAULT_PHP_TO_MMK_RATE)
        mmk_per_php = Decimal(s)
        src = (
            "env:PHP_TO_MMK_RATE"
            if (os.getenv("PHP_TO_MMK_RATE") or "").strip()
            else "config:DEFAULT_PHP_TO_MMK_RATE"
        )

    mark = Decimal(1) + Decimal(str(markup_pct))
    mmk = php_amt * mmk_per_php * mark
    mmk_display = mmk.quantize(Decimal("1"), rounding=ROUND_HALF_UP)
    return format_mmk(mmk_display), mmk_display, src


def parse_price_to_brl_cents(price_text: str) -> Optional[int]:
    """
    Parse smile.one price strings for UI + scraping.
    - Brazilian Real (R$) — primary on smile.one/br
    - Philippine Peso (₱) — only when not using /ph/ direct PHP→MMK in API: ₱→BRL via get_php_to_brl_rate()
    - US$ / USD / $ (some game pages) — converted to BRL cents via USD_TO_BRL_RATE (env, default 5.5)
    """
    if not price_text:
        return None
    t = price_text.strip()
    if re.search(r"R\$\s*[\d]", t, flags=re.IGNORECASE):
        return parse_brl_to_cents(t)

    # Philippine Peso (Unicode ₱ U+20B1 or "PHP") — non-/ph/ pages & scraper fallback: ₱→BRL→MMK
    php_amt = parse_php_amount_from_text(t)
    if php_amt is not None:
        rate = get_php_to_brl_rate()
        brl = php_amt * rate
        cents = (brl * Decimal(100)).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
        return int(cents)

    m = re.search(r"(?:US\$|USD)\s*([0-9]+(?:[\.,][0-9]{1,2})?)", t, flags=re.IGNORECASE)
    if not m:
        m = re.search(r"(?<![\w€£¥])(?<!\d)\$\s*([0-9]+(?:[\.,][0-9]{1,2})?)", t)
    if not m:
        return None
    raw = m.group(1).replace(",", "")
    try:
        usd = float(raw)
    except ValueError:
        return None
    if usd <= 0 or usd > 500_000:
        return None
    try:
        rate = Decimal(str(os.environ.get("USD_TO_BRL_RATE", "5.5")))
    except Exception:
        rate = Decimal("5.5")
    brl = Decimal(str(usd)) * rate
    cents = (brl * Decimal(100)).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
    return int(cents)


def format_mmk(mmk_value: Decimal) -> str:
    # Keep as integer MMK for display.
    mmk_int = int(mmk_value.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
    return f"{mmk_int:,} MMK".replace(",", ",")


def _fetch_fx_rate_from_api(timeout_s: int = 8) -> Optional[FxRate]:
    candidates = [
        # Open Exchange Rates-compatible service
        ("https://open.er-api.com/v6/latest/BRL", "open.er-api.com"),
        # Exchangerate.host
        ("https://api.exchangerate.host/latest?base=BRL&symbols=MMK", "exchangerate.host"),
    ]

    headers = {"User-Agent": "Mozilla/5.0"}
    for url, source in candidates:
        try:
            r = requests.get(url, headers=headers, timeout=timeout_s)
            if r.status_code != 200:
                continue
            payload = r.json()
            if source == "open.er-api.com":
                rate = payload["rates"]["MMK"]
            else:
                # exchangerate.host
                rate = payload["rates"]["MMK"]
            d = Decimal(str(rate))
            return FxRate(rate=d, source=source, fetched_at=int(time.time()))
        except Exception:
            continue
    return None


def get_brl_to_mmk_rate(cache_ttl_s: int = 3600) -> FxRate:
    """
    Resolve FX rate for conversion.

    Priority:
    1) env `BRL_TO_MMK_RATE`
    2) `config.DEFAULT_BRL_TO_MMK_RATE` (default 850 MMK per 1 BRL) unless
       `BRL_USE_LIVE_FX_API=1`
    3) Public FX API (when `BRL_USE_LIVE_FX_API=1`)
    4) env `BRL_TO_MMK_FALLBACK_RATE` (default 850) if API fails
    """
    now = int(time.time())
    cached = _FX_CACHE.get("brl_to_mmk")
    if isinstance(cached, FxRate) and now - cached.fetched_at < cache_ttl_s:
        return cached

    env_override = os.getenv("BRL_TO_MMK_RATE")
    if env_override:
        d = Decimal(env_override.strip())
        fx = FxRate(rate=d, source="env:BRL_TO_MMK_RATE", fetched_at=now)
        _FX_CACHE["brl_to_mmk"] = fx
        return fx

    try:
        from config import DEFAULT_BRL_TO_MMK_RATE

        default_d = Decimal(str(DEFAULT_BRL_TO_MMK_RATE))
    except Exception:
        default_d = Decimal("850")

    shop_brl = _shop_brl_fallback_mmk()
    use_live = os.getenv("BRL_USE_LIVE_FX_API", "").strip().lower() in ("1", "true", "yes")
    if not use_live:
        rate_d = shop_brl if shop_brl is not None else default_d
        src = "admin:BRL_TO_MMK_FALLBACK_RATE" if shop_brl is not None else "config:DEFAULT_BRL_TO_MMK_RATE"
        fx = FxRate(rate=rate_d, source=src, fetched_at=now)
        _FX_CACHE["brl_to_mmk"] = fx
        return fx

    api_rate = _fetch_fx_rate_from_api()
    if api_rate:
        _FX_CACHE["brl_to_mmk"] = api_rate
        return api_rate

    env_fb = (os.getenv("BRL_TO_MMK_FALLBACK_RATE") or "").strip()
    if shop_brl is not None:
        fallback = shop_brl
        src = "admin:BRL_TO_MMK_FALLBACK_RATE"
    elif env_fb:
        fallback = Decimal(env_fb)
        src = "env:BRL_TO_MMK_FALLBACK_RATE"
    else:
        fallback = default_d
        src = "env/fallback"
    fx = FxRate(rate=fallback, source=src, fetched_at=now)
    _FX_CACHE["brl_to_mmk"] = fx
    return fx


def brl_cents_to_mmk_str(
    brl_cents: int,
    markup_pct: float = 0.0,
    currency_round: str = "1",
) -> tuple[str, Decimal, FxRate]:
    """
    Convert BRL cents to formatted MMK string.

    MMK = (BRL * (1 + markup_pct)) * (BRL->MMK FX)
    """
    brl = Decimal(brl_cents) / Decimal(100)
    brl_with_markup = brl * (Decimal(1) + Decimal(str(markup_pct)))
    fx = get_brl_to_mmk_rate()
    mmk = brl_with_markup * fx.rate
    if currency_round == "1":
        mmk_display = mmk.quantize(Decimal("1"), rounding=ROUND_HALF_UP)
    else:
        mmk_display = mmk
    return format_mmk(mmk_display), mmk_display, fx

