"""مینی‌اپ تلگرام: صفحه‌ی /app و APIهای /api/*.

سفارش، پرداخت (کارت‌به‌کارت/کیف پول)، رسید، احراز هویت، یوزرنیم و تیکت کاملاً داخل مینی‌اپ انجام می‌شود
ولی روی همان جدول‌ها و وضعیت‌های ربات کار می‌کند؛ پس پنل ادمین و ربات تغییری نمی‌خواهند.
"""
import hashlib
import hmac
import json
import os
import time
from functools import wraps
from urllib.parse import parse_qsl

from flask import jsonify, request, send_from_directory

import config
import database as db
import telegram_api as tg

_HERE = os.path.dirname(os.path.abspath(__file__))
_rate, _member, _usdt = {}, {}, {"v": None, "t": 0}
MAX_PHOTO = 8 * 1024 * 1024

DEFAULT_FAQ = [
    ["پرداخت چطور انجام می‌شود؟", "بعد از ثبت سفارش، اطلاعات کارت‌به‌کارت را داخل همین مینی‌اپ می‌بینید. پس از واریز، عکس رسید را همان‌جا آپلود کنید."],
    ["بعد از پرداخت چقدر طول می‌کشد؟", "سفارش‌ها معمولاً بعد از بررسی رسید توسط ادمین، در کوتاه‌ترین زمان ممکن انجام می‌شود."],
    ["قیمت‌ها چرا تغییر می‌کنند؟", "قیمت‌ها بر پایه‌ی نرخ لحظه‌ای تتر محاسبه می‌شود و هر سفارش چند دقیقه اعتبار قیمت دارد؛ بعد از آن با «خرید مجدد» قیمت جدید بگیرید."],
    ["کد تخفیف را کجا وارد کنم؟", "در صفحه‌ی خرید هر بسته، قبل از ثبت سفارش."],
    ["برای پرمیوم و استارز چه اطلاعاتی لازم است؟", "فقط یوزرنیم تلگرامی که می‌خواهید خرید برایش فعال شود؛ لازم نیست وارد حساب شوید یا رمزی بدهید."],
]


def _ba():
    import app as botapp  # import تنبل برای جلوگیری از چرخه
    return botapp


def verify_init_data(init_data):
    try:
        pairs = dict(parse_qsl(init_data or "", keep_blank_values=True))
        got = pairs.pop("hash", None)
        if not got:
            return None
        check = "\n".join(f"{k}={v}" for k, v in sorted(pairs.items()))
        secret = hmac.new(b"WebAppData", config.BOT_TOKEN.encode(), hashlib.sha256).digest()
        if not hmac.compare_digest(hmac.new(secret, check.encode(), hashlib.sha256).hexdigest(), got):
            return None
        if time.time() - int(pairs.get("auth_date", "0")) > 86400:
            return None
        user = json.loads(pairs.get("user", "{}"))
        return user if user.get("id") else None
    except Exception:
        return None


def _name(u):
    return (u.get("first_name", "") + " " + u.get("last_name", "")).strip()


def authed(write=False):
    def deco(fn):
        @wraps(fn)
        def wrapper(*a, **k):
            user = verify_init_data(request.headers.get("X-Init-Data", ""))
            if not user:
                return jsonify({"ok": False, "error": "auth"}), 401
            uid = user["id"]
            if write:
                now = time.time()
                if now - _rate.get(uid, 0) < 0.4:
                    return jsonify({"ok": False, "error": "کمی صبر کنید و دوباره تلاش کنید."}), 429
                _rate[uid] = now
            if uid not in config.ADMIN_IDS and (time.time() - _member.get(uid, 0) > 300):
                if not _ba().is_member_of_channel(uid):
                    return jsonify({"ok": False, "error": "join", "link": config.FORCE_JOIN_CHANNEL_LINK}), 403
                _member[uid] = time.time()
            db.upsert_user(uid, user.get("username"), _name(user))
            return fn(user, *a, **k)
        return wrapper
    return deco


def _rate_usdt():
    if _usdt["v"] and time.time() - _usdt["t"] < 30:
        return _usdt["v"]
    v = _ba().get_usdt_price()
    if v:
        _usdt.update(v=v, t=time.time())
    return v


def _mult(product):
    return db.get_premium_multiplier() if product == "premium" else db.get_stars_multiplier()


def _price(product, key):
    """(بسته, قیمت)؛ از بسته‌ی دلخواه استارز (custom<N>) هم پشتیبانی می‌کند."""
    ba = _ba()
    pkg = ba.find_package(product, key)
    usdt = _rate_usdt()
    if not pkg or not usdt:
        return pkg, None
    return pkg, ba.compute_price(pkg["base_usd"], usdt, _mult(product))


def _packages():
    ba = _ba()
    usdt = _rate_usdt()
    out = {}
    for product in ("premium", "stars"):
        out[product] = [{"key": p["key"], "label": p["label"],
                         "price": ba.compute_price(p["base_usd"], usdt, _mult(product)) if usdt else None}
                        for p in db.list_packages(product)]
    return out, usdt


def _order_json(o):
    ba = _ba()
    topup = o["product_type"] == "wallet_topup"
    valid = config.TOPUP_VALIDITY_SECONDS if topup else config.PRICE_VALIDITY_SECONDS
    d = {
        "id": o["id"], "type": o["product_type"], "label": o["package_label"], "price": o["price_toman"],
        "status": o["status"], "status_label": ba.STATUS_LABELS.get(o["status"], o["status"]),
        "ts": o["created_at"], "expires_at": o["created_at"] + valid, "username": o.get("target_username"),
        "code": o.get("discount_code"), "discount": o.get("discount_amount") or 0,
        "package_key": o.get("package_key"), "kyc_count": 0, "kyc_min": ba.KYC_MIN_PHOTOS,
    }
    if o["status"] == "awaiting_kyc":
        d["kyc_count"] = len(db.get_kyc_files(o["id"]))
    return d


def _orders(uid):
    ba = _ba()
    rows = {o["id"]: o for o in db.list_user_orders_visible(uid, 30)}
    for o in db.list_user_orders(uid, 10):  # سفارش‌های نیمه‌کاره هم دیده شوند
        if o["status"] in ("awaiting_kyc", "awaiting_username") or \
                (o["status"] == "awaiting_payment" and not ba._order_is_stale(o)):
            rows[o["id"]] = o
    return sorted(rows.values(), key=lambda o: o["id"], reverse=True)


def _tickets(uid):
    out = []
    with db.get_conn() as conn:
        for t in db.list_tickets_for_user(uid, 8):
            m = conn.execute("SELECT sender, text, created_at FROM ticket_messages WHERE ticket_id = ? "
                             "ORDER BY id DESC LIMIT 1", (t["id"],)).fetchone()
            if m:
                out.append({"id": t["id"], "status": t["status"], "sender": m["sender"],
                            "text": (m["text"] or "")[:80], "ts": m["created_at"]})
    return out


def _faq():
    try:
        v = json.loads(db.get_setting("mini_faq", "") or "[]")
        if v:
            return v
    except ValueError:
        pass
    return DEFAULT_FAQ


def _upload_photo(uid, caption):
    f = request.files.get("photo")
    data = f.read() if f else b""
    if not data or len(data) > MAX_PHOTO:
        return None, "عکس نامعتبر است (حداکثر ۸ مگابایت)."
    if not (data[:3] == b"\xff\xd8\xff" or data[:8] == b"\x89PNG\r\n\x1a\n" or data[8:12] == b"WEBP"):
        return None, "فقط فایل عکس بفرستید."
    resp = tg._post("sendPhoto", chat_id=uid, caption=caption, files={"photo": ("photo.jpg", data, "image/jpeg")})
    try:
        big = resp["result"]["photo"][-1]
        return (big["file_id"], big.get("file_unique_id")), None
    except Exception:
        return None, "ارسال عکس ناموفق بود؛ دوباره تلاش کنید."


def _my_order(user, src, statuses):
    try:
        o = db.get_order(int(src.get("id") or src.get("order_id")))
    except (TypeError, ValueError):
        o = None
    if not o or o["user_id"] != user["id"] or o["status"] not in statuses:
        return None
    return o


def register(app):
    @app.route("/app", methods=["GET"])
    def miniapp_page():
        resp = send_from_directory(os.path.join(_HERE, "static"), "miniapp.html")
        resp.headers["Cache-Control"] = "no-cache"
        return resp

    @app.route("/api/bootstrap", methods=["GET"])
    @authed()
    def api_bootstrap(user):
        ba = _ba()
        uid = user["id"]
        packages, usdt = _packages()
        balance, ref_balance = db.get_wallet(uid)
        bot_username = ba.get_bot_username()
        thr, amt = db.get_topup_bonus()
        txs = [{"kind": t["kind"], "label": ba.WALLET_TX_LABELS.get(t["kind"], t["kind"]), "amount": t["amount"],
                "ref": t.get("referral_amount") or 0, "ts": t["created_at"]} for t in db.list_wallet_transactions(uid, 20)]
        return jsonify({
            "ok": True,
            "user": {"id": uid, "first_name": user.get("first_name", ""), "last_name": user.get("last_name", ""),
                     "username": user.get("username")},
            "balance": balance, "ref_balance": ref_balance, "usdt": usdt, "rate_error": usdt is None,
            "packages": packages,
            "stars_custom": {"min": config.STARS_MIN_AMOUNT, "max": config.STARS_CUSTOM_MAX,
                             "unit": (usdt or 0) * config.STARS_USD_PER_STAR * db.get_stars_multiplier()},
            "paused": {"premium": db.get_pause("premium"), "stars": db.get_pause("stars")},
            "bonus": {"threshold": thr, "amount": amt},
            "topup": {"min": config.TOPUP_MIN_TOMAN, "max": config.TOPUP_MAX_TOMAN},
            "pay": {"card": config.CARD_NUMBER, "holder": config.CARD_HOLDER},
            "orders": [_order_json(o) for o in _orders(uid)], "tx": txs, "tickets": _tickets(uid),
            "banner": db.get_setting("mini_banner", "") or None, "faq": _faq(),
            "referral": {"count": db.count_referrals(uid), "percent": db.get_referral_percent(),
                         "link": f"https://t.me/{bot_username}?start=ref_{uid}" if bot_username else None},
            "links": {"channel": config.FORCE_JOIN_CHANNEL_LINK,
                      "bot": f"https://t.me/{bot_username}" if bot_username else None},
        })

    @app.route("/api/discount", methods=["POST"])
    @authed(write=True)
    def api_discount(user):
        b = request.get_json(silent=True) or {}
        pkg, price = _price(b.get("product"), b.get("key"))
        if not pkg or not price:
            return jsonify({"ok": False, "error": "بسته پیدا نشد."})
        res, info = db.check_discount(user["id"], b["product"], price, str(b.get("code", "")))
        msgs = {"invalid": "کد تخفیف نامعتبر است.", "expired": "این کد منقضی شده است.",
                "exhausted": "ظرفیت این کد تمام شده است.", "used": "شما قبلاً از این کد استفاده کرده‌اید.",
                "product": f"این کد فقط برای {'پرمیوم' if info == 'premium' else 'استارز'} است."}
        if res != "ok":
            return jsonify({"ok": False, "error": msgs.get(res, "کد نامعتبر است.")})
        return jsonify({"ok": True, **info})

    @app.route("/api/order", methods=["POST"])
    @authed(write=True)
    def api_order(user):
        b = request.get_json(silent=True) or {}
        product = b.get("product")
        if product not in ("premium", "stars"):
            return jsonify({"ok": False, "error": "محصول نامعتبر است."})
        if db.get_pause(product):
            return jsonify({"ok": False, "error": "فروش این بخش موقتاً متوقف است."})
        pkg, price = _price(product, b.get("key"))
        if not pkg:
            return jsonify({"ok": False, "error": "این بسته دیگر موجود نیست."})
        if not price:
            return jsonify({"ok": False, "error": "دریافت نرخ ناموفق بود؛ چند لحظه بعد دوباره تلاش کنید."})
        oid = db.create_order(user_id=user["id"], username=user.get("username"), full_name=_name(user),
                              product_type=product, package_key=pkg["key"], package_label=pkg["label"],
                              base_usd=pkg["base_usd"], multiplier=_mult(product), usdt_rate=_rate_usdt(),
                              price_toman=price)
        code = (b.get("code") or "").strip().upper()
        if code:
            db.apply_discount(oid, user["id"], code)
        return jsonify({"ok": True, "order": _order_json(db.get_order(oid))})

    @app.route("/api/topup", methods=["POST"])
    @authed(write=True)
    def api_topup(user):
        b = request.get_json(silent=True) or {}
        try:
            amount = int(b.get("amount"))
        except (TypeError, ValueError):
            return jsonify({"ok": False, "error": "مبلغ نامعتبر است."})
        if amount < config.TOPUP_MIN_TOMAN:
            return jsonify({"ok": False, "error": f"حداقل مبلغ شارژ {config.TOPUP_MIN_TOMAN:,} تومان است."})
        if amount > config.TOPUP_MAX_TOMAN:
            return jsonify({"ok": False, "error": f"حداکثر مبلغ شارژ {config.TOPUP_MAX_TOMAN:,} تومان است."})
        oid = db.create_order(user_id=user["id"], username=user.get("username"), full_name=_name(user),
                              product_type="wallet_topup", package_key="topup", package_label="شارژ کیف پول",
                              base_usd=0, multiplier=1, usdt_rate=None, price_toman=amount)
        return jsonify({"ok": True, "order": _order_json(db.get_order(oid))})

    @app.route("/api/cancel", methods=["POST"])
    @authed(write=True)
    def api_cancel(user):
        o = _my_order(user, request.get_json(silent=True) or {}, ("awaiting_payment", "awaiting_username"))
        if not o or not db.cancel_order_atomic(o["id"], user["id"]):
            return jsonify({"ok": False, "error": "این سفارش دیگر قابل لغو نیست."})
        db.refund_wallet_order(o["id"])  # اگر با کیف پول پرداخت شده بود برمی‌گردد
        return jsonify({"ok": True})

    @app.route("/api/pay_wallet", methods=["POST"])
    @authed(write=True)
    def api_pay_wallet(user):
        ba = _ba()
        o = _my_order(user, request.get_json(silent=True) or {}, ("awaiting_payment",))
        if not o or o["product_type"] == "wallet_topup":
            return jsonify({"ok": False, "error": "این سفارش دیگر قابل پرداخت نیست."})
        if ba._order_is_stale(o):
            db.expire_order_atomic(o["id"])
            return jsonify({"ok": False, "error": "اعتبار قیمت تمام شده؛ با «خرید مجدد» قیمت جدید بگیرید."})
        if db.get_pause(o["product_type"]):
            return jsonify({"ok": False, "error": "فروش این بخش موقتاً متوقف است."})
        res, info = db.pay_order_with_wallet(o["id"], user["id"])
        if res == "insufficient":
            return jsonify({"ok": False, "error": f"موجودی کافی نیست؛ کسری {info[1] - info[0]:,} تومان."})
        if res != "ok":
            return jsonify({"ok": False, "error": "این سفارش دیگر قابل پرداخت نیست."})
        return jsonify({"ok": True})

    @app.route("/api/receipt", methods=["POST"])
    @authed(write=True)
    def api_receipt(user):
        ba = _ba()
        o = _my_order(user, request.form, ("awaiting_payment",))
        if not o:
            return jsonify({"ok": False, "error": "این سفارش دیگر در انتظار پرداخت نیست."})
        if ba._order_is_stale(o):
            db.expire_order_atomic(o["id"])
            return jsonify({"ok": False, "error": "مهلت پرداخت تمام شده است."})
        up, err = _upload_photo(user["id"], f"🧾 رسید سفارش #{o['id']}")
        if err:
            return jsonify({"ok": False, "error": err})
        file_id, uniq = up
        dup = db.find_order_by_receipt_unique_id(uniq)
        if dup:
            for aid in config.ADMIN_IDS:
                try:
                    ba.send_rich(aid, [("e", "warning"), f" رسید تکراری شناسایی شد! سفارش #{o['id']} (کاربر {user['id']}) — "
                                                          f"قبلاً برای سفارش #{dup['id']} ثبت شده بود."])
                except Exception:
                    pass
            return jsonify({"ok": False, "error": "این رسید قبلاً برای سفارش دیگری ثبت شده و پذیرفته نمی‌شود."})
        needs_kyc = ba.kyc_needed(o)
        try:
            ok = db.attach_receipt(o["id"], file_id, uniq, kyc_required=needs_kyc)
        except Exception:
            ok = False
        if not ok:
            return jsonify({"ok": False, "error": "ثبت رسید ناموفق بود (ممکن است قبلاً ثبت شده باشد)."})
        fresh = db.get_order(o["id"])
        if needs_kyc:
            ba._notify_admins_waiting_kyc(fresh)
        elif o["product_type"] == "wallet_topup":
            ba.notify_admins_new_order(fresh)
        return jsonify({"ok": True, "order": _order_json(fresh)})

    @app.route("/api/kyc", methods=["POST"])
    @authed(write=True)
    def api_kyc(user):
        o = _my_order(user, request.form, ("awaiting_kyc",))
        if not o:
            return jsonify({"ok": False, "error": "این سفارش در مرحله‌ی احراز هویت نیست."})
        up, err = _upload_photo(user["id"], f"🪪 مدرک احراز هویت سفارش #{o['id']}")
        if err:
            return jsonify({"ok": False, "error": err})
        db.add_kyc_file(o["id"], up[0], up[1])
        return jsonify({"ok": True, "order": _order_json(db.get_order(o["id"]))})

    @app.route("/api/kyc_confirm", methods=["POST"])
    @authed(write=True)
    def api_kyc_confirm(user):
        ba = _ba()
        o = _my_order(user, request.get_json(silent=True) or {}, ("awaiting_kyc",))
        if not o:
            return jsonify({"ok": False, "error": "مدارک این سفارش قبلاً ارسال شده است."})
        if len(db.get_kyc_files(o["id"])) < ba.KYC_MIN_PHOTOS:
            return jsonify({"ok": False, "error": "هر دو عکس (کارت بانکی و کارت ملی) لازم است."})
        if not db.confirm_kyc_atomic(o["id"]):
            return jsonify({"ok": False, "error": "مدارک این سفارش قبلاً ارسال شده است."})
        fresh = db.get_order(o["id"])
        if fresh["product_type"] == "wallet_topup":
            ba.notify_admins_new_order(fresh)
        return jsonify({"ok": True, "order": _order_json(fresh)})

    @app.route("/api/username", methods=["POST"])
    @authed(write=True)
    def api_username(user):
        ba = _ba()
        b = request.get_json(silent=True) or {}
        o = _my_order(user, b, ("awaiting_username",))
        cand = str(b.get("username", "")).strip().lstrip("@")
        if not o:
            return jsonify({"ok": False, "error": "این مرحله دیگر فعال نیست."})
        if not ba.USERNAME_RE.match(cand):
            return jsonify({"ok": False, "error": "یوزرنیم نامعتبر است (۵ تا ۳۲ کاراکتر، با حرف انگلیسی شروع شود، فقط حروف/عدد/زیرخط)."})
        db.attach_target_username(o["id"], cand)
        ba.notify_admins_new_order(db.get_order(o["id"]))
        return jsonify({"ok": True, "order": _order_json(db.get_order(o["id"]))})

    # ------------------------------ تیکت ------------------------------
    @app.route("/api/ticket", methods=["GET"])
    @authed()
    def api_ticket(user):
        t = db.get_ticket(request.args.get("id", type=int) or 0)
        if not t or t["user_id"] != user["id"]:
            return jsonify({"ok": False, "error": "تیکت پیدا نشد."})
        return jsonify({"ok": True, "status": t["status"], "has_admin": db.ticket_has_admin_reply(t["id"]),
                        "messages": [{"sender": m["sender"], "text": m["text"], "ts": m["created_at"]}
                                     for m in db.list_ticket_messages(t["id"], 200)]})

    @app.route("/api/ticket/send", methods=["POST"])
    @authed(write=True)
    def api_ticket_send(user):
        ba = _ba()
        b = request.get_json(silent=True) or {}
        text = str(b.get("text", "")).strip()[:2000]
        if not text:
            return jsonify({"ok": False, "error": "متن پیام خالی است."})
        tid = b.get("id") or 0
        if tid:
            t = db.get_ticket(int(tid))
            if not t or t["user_id"] != user["id"] or t["status"] != "open":
                return jsonify({"ok": False, "error": "این تیکت بسته شده؛ تیکت جدید بسازید."})
        else:
            t = db.get_open_ticket_for_user(user["id"])
            tid = t["id"] if t else db.create_ticket(user["id"])
        db.add_ticket_message(tid, "user", text)
        uname = f"@{user['username']}" if user.get("username") else "—"
        admin_text, ents = ba.build_message([("e", "contact"), f" پیام جدید در تیکت #{tid}\n\n",
                                             f"از: {_name(user) or 'بدون‌نام'} ({uname}) | آیدی: {user['id']}\n\n",
                                             f"«{text}»\n", "📱 ارسال‌شده از مینی‌اپ"])
        kb = ba.ticket_admin_keyboard(tid, user["id"])
        for aid in config.ADMIN_IDS:
            try:
                tg.send_message(aid, admin_text, reply_markup=kb, entities=ents)
            except Exception:
                pass
        return jsonify({"ok": True, "id": tid})

    @app.route("/api/ticket/close", methods=["POST"])
    @authed(write=True)
    def api_ticket_close(user):
        t = db.get_ticket(int((request.get_json(silent=True) or {}).get("id") or 0))
        if not t or t["user_id"] != user["id"] or t["status"] != "open":
            return jsonify({"ok": False, "error": "این تیکت بسته شده است."})
        if not db.ticket_has_admin_reply(t["id"]):
            return jsonify({"ok": False, "error": "هنوز پشتیبانی پاسخی نداده؛ لطفاً منتظر پاسخ بمانید."})
        db.close_ticket(t["id"])
        return jsonify({"ok": True})
