#!/usr/bin/env python3
"""Full MySQL cutover E2E — stop on first failure."""
from __future__ import annotations

import hashlib
import json
import sys
import time
import urllib.error
import urllib.request
from datetime import date, timedelta
from pathlib import Path

import subprocess

BASE = "http://127.0.0.1:4000"
PROP = "cmrnmicpg3480b58ce68332ecd34b"  # instant booking
LOG = Path("/Users/saleh/.cursor/projects/Users-saleh-Documents-SafarLibya/terminals/887154.txt")


def mysql_exec(sql: str) -> str:
    r = subprocess.run(
        ["mysql", "-uroot", "-psafar_dev", "safar_libya", "-N", "-e", sql],
        capture_output=True,
        text=True,
    )
    if r.returncode != 0:
        raise Fail(f"mysql error: {r.stderr.strip()}")
    return r.stdout.strip()


class Fail(Exception):
    pass


def api(method: str, path: str, token: str | None = None, data: dict | None = None, timeout=30):
    body = None if data is None else json.dumps(data).encode()
    headers = {"Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    req = urllib.request.Request(BASE + path, data=body, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            raw = resp.read().decode()
            code = resp.status
    except urllib.error.HTTPError as e:
        raw = e.read().decode()
        code = e.code
    try:
        parsed = json.loads(raw) if raw else {}
    except json.JSONDecodeError:
        parsed = {"_raw": raw[:500]}
    return code, parsed


def must(ok: bool, label: str, code: int, payload: dict):
    print(f"{label}: HTTP {code}")
    if not ok:
        raise Fail(f"{label} failed: {json.dumps(payload, ensure_ascii=False)[:600]}")


def main():
    log_start = len(LOG.read_text().splitlines()) if LOG.exists() else 0
    ts = int(time.time())
    email = f"cutover{ts}@example.com"
    phone = f"+21891{ts % 100000000:08d}"
    password = "CutoverTest123!"
    known_otp = "123456"
    otp_hash = hashlib.sha256(known_otp.encode()).hexdigest()

    print("=== 1. REGISTER ===")
    code, d = api(
        "POST",
        "/api/auth/register",
        data={
            "fullName": "Cutover User",
            "email": email,
            "phone": phone,
            "password": password,
            "role": "CUSTOMER",
            "locale": "ar",
        },
    )
    must(code == 201, "register", code, d)
    token = d["accessToken"]
    uid = d["user"]["id"]
    print("  _db", d.get("_db"), "needsOtp", d.get("needsOtp"))

    print("=== 2. OTP VERIFY ===")
    mysql_exec(
        f"""
        UPDATE otp_challenges
        SET code_hash='{otp_hash}', expires_at=DATE_ADD(UTC_TIMESTAMP(), INTERVAL 10 MINUTE),
            attempts=0, consumed_at=NULL
        WHERE user_id='{uid}' AND channel='EMAIL'
        ORDER BY created_at DESC
        LIMIT 1
        """
    )
    code, d = api("POST", "/api/auth/verify-otp", token=token, data={"code": known_otp})
    must(code == 200, "verify-otp", code, d)
    print("  status", (d.get("user") or {}).get("status"), "needsOtp", d.get("needsOtp"))

    print("=== 3. LOGIN ===")
    code, d = api("POST", "/api/auth/login", data={"email": email, "password": password})
    must(code == 200, "login", code, d)
    token = d["accessToken"]

    print("=== 4. QUOTE ===")
    check_in = (date.today() + timedelta(days=25)).isoformat()
    check_out = (date.today() + timedelta(days=28)).isoformat()
    code, d = api(
        "POST",
        "/api/bookings/quote",
        token=token,
        data={"propertyId": PROP, "checkIn": check_in, "checkOut": check_out, "guests": 2},
    )
    must(code in (200, 201), "quote", code, d)
    booking = d.get("booking") or d
    bid = booking.get("id") or booking.get("_id")
    must(bool(bid), "quote-id", code, d)
    print("  bid", bid, "status", booking.get("status"), "totalLyd", booking.get("totalLyd"), "_db", d.get("_db"))

    print("=== 5. PAY ===")
    code, d = api("POST", f"/api/bookings/{bid}/pay", token=token, data={})
    must(code == 200, "pay", code, d)
    booking = d.get("booking") or {}
    print(
        "  status",
        booking.get("status"),
        "pay",
        (booking.get("payment") or {}).get("status"),
        "inv",
        (booking.get("invoice") or {}).get("invoiceNumber"),
    )

    print("=== 6. INVOICE ===")
    code, d = api("GET", f"/api/bookings/{bid}/invoice", token=token)
    must(code == 200, "invoice", code, d)
    inv = d.get("invoice") or {}
    print("  _db", d.get("_db"), "num", inv.get("invoiceNumber"), "has_booking", bool(inv.get("booking")))

    print("=== 7. CANCEL + REFUND ===")
    code, d = api("POST", f"/api/bookings/{bid}/cancel", token=token, data={"reason": "e2e cutover"})
    must(code == 200, "cancel", code, d)
    booking = d.get("booking") or {}
    print("  status", booking.get("status"), "refund", d.get("refund"))

    print("=== 8. OWNER WITHDRAW ===")
    code, d = api("POST", "/api/auth/login", data={"email": "owner@safarlibya.com", "password": "Password123!"})
    must(code == 200, "owner-login", code, d)
    owner_token = d["accessToken"]
    code, d = api(
        "POST",
        "/api/dashboard/owner/withdraw",
        token=owner_token,
        data={"amountTnd": 1, "method": "BANK_LYD", "note": "cutover e2e"},
    )
    must(code == 200, "withdraw", code, d)
    wid = (d.get("request") or {}).get("id")
    must(bool(wid), "withdraw-id", code, d)
    print("  _db", d.get("_db"), "id", wid)
    row = mysql_exec(f"SELECT id, status FROM withdrawal_requests WHERE id='{wid}'")
    must(bool(row), "withdraw-mysql-row", code, {"wid": wid, "row": row})
    print("  mysql row", row)

    print("=== 9. ADMIN PAGES ===")
    code, d = api("POST", "/api/auth/login", data={"email": "admin@safarlibya.com", "password": "19992000"})
    must(code == 200, "admin-login", code, d)
    admin = d["accessToken"]
    endpoints = [
        "/api/admin/analytics",
        "/api/admin/users",
        "/api/admin/bookings",
        "/api/admin/properties",
        "/api/admin/payments",
        "/api/admin/reviews",
        "/api/admin/withdrawals",
        "/api/admin/wallets",
        "/api/admin/reconciliation/summary",
        "/api/admin/reconciliation/ledger",
        "/api/admin/exchange-rates",
        "/api/admin/coupons",
        "/api/admin/activity",
        "/api/admin/contact-messages",
        "/api/admin/team",
    ]
    for ep in endpoints:
        code, d = api("GET", ep, token=admin)
        must(code == 200, f"admin {ep}", code, d)

    print("=== 10. BACKEND LOG SCAN (no mongoose) ===")
    lines = LOG.read_text().splitlines()[log_start:]
    bad = [
        ln
        for ln in lines
        if ("MongooseError" in ln)
        or ("buffering timed out" in ln)
        or ("MongoNetworkError" in ln)
        or ("Operation `" in ln and "buffering" in ln)
    ]
    if bad:
        raise Fail("mongoose activity in logs:\n" + "\n".join(bad[:20]))
    print("  OK: no mongoose errors during E2E")
    print(f"ALL_E2E_PASSED email={email} bid={bid}")


if __name__ == "__main__":
    try:
        main()
    except Fail as e:
        print("FAIL:", e, file=sys.stderr)
        sys.exit(1)
