"""End-to-end smoke test against a running API.

    python scripts/smoke.py                        # http://127.0.0.1:8000
    BASE_URL=https://example.com python scripts/smoke.py

Reads credentials from the project .env (BOT_API_TOKEN, ADMIN_USERNAME,
ADMIN_PASSWORD). It creates a throwaway Telegram user, orders, invoices and a
test coupon, so run it against a development or staging deployment.
Existing wallet/bank/TRON settings are never overwritten: checks that need a
setting are skipped when it is not configured.
"""
import os
import sys
from decimal import Decimal
from pathlib import Path

import httpx

ROOT = Path(__file__).resolve().parents[2]


def env(key: str, default: str = "") -> str:
    if os.getenv(key):
        return os.environ[key]
    env_file = ROOT / ".env"
    if env_file.exists():
        for line in env_file.read_text().splitlines():
            if line.startswith(f"{key}="):
                return line.split("=", 1)[1].strip()
    return default


BASE = env("BASE_URL", "http://127.0.0.1:8000").rstrip("/") + "/api/v1"
BOT = {"X-Bot-Token": env("BOT_API_TOKEN")}
TELEGRAM_ID = int(env("SMOKE_TELEGRAM_ID", "999000001"))
USER = {**BOT, "X-Telegram-Id": str(TELEGRAM_ID)}

client = httpx.Client(timeout=30)
passed, failed, skipped = 0, [], []


def check(name: str, condition: bool, extra: object = "") -> bool:
    global passed
    if condition:
        passed += 1
        print(f"  ok    {name}")
    else:
        failed.append(name)
        print(f"  FAIL  {name} {extra}")
    return condition


def skip(name: str, why: str) -> None:
    skipped.append(name)
    print(f"  skip  {name} ({why})")


def call(method: str, path: str, headers: dict | None = None, **kw):
    response = client.request(method, BASE + path, headers=headers or {}, **kw)
    try:
        body = response.json()
    except ValueError:
        body = {"raw": response.text[:200]}
    return response.status_code, body


print("== auth ==")
status, body = call("POST", "/auth/login",
                    json={"username": env("ADMIN_USERNAME"), "password": env("ADMIN_PASSWORD")})
if not check("admin login", status == 200, body):
    sys.exit(1)
ADMIN = {"Authorization": f"Bearer {body['data']['access_token']}"}

print("== bot user ==")
status, body = call("POST", "/telegram/start", headers=BOT,
                    json={"telegram_id": TELEGRAM_ID, "username": "smoke_user", "first_name": "Smoke"})
check("telegram start", status == 200, body)
user_id = body["data"]["user"]["id"]
check("start returns menu and settings", bool(body["data"]["menu"]))

print("== catalog ==")
status, body = call("GET", "/categories", headers=BOT)
check("categories load", status == 200 and bool(body["data"]), body)
categories = body["data"]

product = None
for category in categories:
    status, body = call("GET", f"/categories/{category['slug']}/products", headers=BOT)
    if status == 200 and body["data"]:
        product = body["data"][0]
        break
if not check("products load", product is not None):
    sys.exit(1)
price = Decimal(str(product["effective_price"]))

print("== admin product management ==")
status, body = call("POST", "/products", headers=ADMIN, json={
    "category_id": product["category_id"], "title": "smoke test product", "price": "12345",
    "flow": product["flow"], "data": product.get("data", {}), "sort_order": 999,
})
check("create product", status == 200, body)
temp_id = body["data"]["id"] if status == 200 else None

if temp_id:
    status, body = call("PUT", f"/products/{temp_id}", headers=ADMIN,
                        json={"price": "9999", "enabled": False})
    check("update price and disable",
          status == 200 and Decimal(str(body["data"]["price"])) == Decimal("9999")
          and not body["data"]["enabled"], body)

    status, body = call("GET", f"/categories/{category['slug']}/products", headers=BOT)
    check("disabled product hidden from bot", all(p["id"] != temp_id for p in body["data"]))

    status, body = call("POST", f"/products/{temp_id}/duplicate", headers=ADMIN)
    check("duplicate product", status == 200 and body["data"]["id"] != temp_id, body)
    if status == 200:
        call("DELETE", f"/products/{body['data']['id']}", headers=ADMIN)
    status, _ = call("DELETE", f"/products/{temp_id}", headers=ADMIN)
    check("delete product", status == 200)


def new_order():
    return call("POST", "/orders", headers=USER,
                json={"product_id": product["id"], "input_data": {"username": "smoke_target"}})


print("== order ==")
status, body = new_order()
check("order creation", status == 200, body)
order = body["data"]
order_id = order["id"]
check("order number generated", bool(order.get("order_number")))
check("order stores product snapshot", bool(order.get("product_snapshot")))

print("== coupon ==")
status, body = call("POST", "/admin/coupons", headers=ADMIN,
                    json={"code": "SMOKETEST20", "type": "percent", "value": "20"})
check("create coupon", status == 200 or body.get("message") == "coupon_exists", body)

status, body = call("POST", f"/orders/{order_id}/coupon", headers=USER, json={"code": "SMOKETEST20"})
if check("apply coupon", status == 200, body):
    expected = (price * Decimal("0.8")).quantize(Decimal("1"))
    got = Decimal(str(body["data"]["final_price"])).quantize(Decimal("1"))
    check("discount applied", got == expected, f"expected {expected}, got {got}")

status, body = call("DELETE", f"/orders/{order_id}/coupon", headers=USER)
check("remove coupon", status == 200 and Decimal(str(body["data"]["final_price"])) == price, body)

status, body = call("POST", f"/orders/{order_id}/coupon", headers=USER, json={"code": "NO_SUCH_CODE"})
check("invalid coupon rejected", status >= 400, body)

print("== wallet payment ==")
_, before = call("GET", "/wallet", headers=USER)
balance_before = Decimal(str(before["data"]["balance"]))
status, body = call("POST", "/admin/wallet/adjust", headers=ADMIN,
                    json={"user_id": user_id, "amount": str(price), "description": "smoke test"})
check("wallet credit", status == 200, body)

status, body = call("POST", "/payments", headers=USER, json={"order_id": order_id, "method": "wallet"})
check("wallet payment", status == 200, body)

status, body = call("GET", "/wallet", headers=USER)
check("wallet debited", Decimal(str(body["data"]["balance"])) == balance_before, body["data"])

status, body = call("GET", f"/orders/{order_id}", headers=USER)
check("paid order queued for fulfilment",
      body["data"]["status"] in ("paid", "processing", "completed"), body["data"]["status"])

status, body = call("PUT", f"/admin/orders/{order_id}/status", headers=ADMIN,
                    json={"status": "completed", "note": "smoke test"})
check("admin completes order", status == 200 and body["data"]["status"] == "completed", body)

print("== tron invoices ==")
_, settings_body = call("GET", "/admin/settings", headers=ADMIN)
current = (settings_body.get("data") or {}).get("values") or {}
if not (current.get("tron_wallet_address") and current.get("trx_rate_irt")):
    skip("tron invoice", "tron_wallet_address / trx_rate_irt not configured")
else:
    invoices = []
    for _ in range(3):
        _, created = new_order()
        status, body = call("POST", "/payments", headers=USER,
                            json={"order_id": created["data"]["id"], "method": "tron"})
        if status != 200:
            check("tron invoice", False, body)
            break
        payment = body["data"]["payment"]
        invoices.append((Decimal(str(payment["original_amount"])),
                         Decimal(str(payment["payable_amount"]))))
    else:
        check("tron invoice generation", len(invoices) == 3)
        check("payable amount differs from original",
              all(payable != original for original, payable in invoices), invoices)
        check("payable amounts unique", len({p for _, p in invoices}) == 3, invoices)
        check("payable amount within tolerance",
              all(0 < payable - original < Decimal("0.01") for original, payable in invoices),
              invoices)

print("== manual payment ==")
if not current.get("bank_card_number"):
    skip("manual payment", "bank_card_number not configured")
else:
    _, created = new_order()
    manual_order = created["data"]["id"]
    status, body = call("POST", "/payments", headers=USER,
                        json={"order_id": manual_order, "method": "bank"})
    if check("manual payment created", status == 200 and bool(body["data"].get("card_number")), body):
        payment_id = body["data"]["payment"]["id"]
        status, body = call("POST", f"/admin/payments/{payment_id}/approve", headers=ADMIN, json={})
        check("admin approves manual payment", status == 200, body)
        status, body = call("GET", f"/orders/{manual_order}", headers=USER)
        check("order paid after approval",
              body["data"]["status"] in ("paid", "processing", "completed"), body["data"]["status"])

print("== admin screens ==")
for name, path in [
    ("dashboard", "/admin/dashboard"), ("orders", "/admin/orders"), ("payments", "/admin/payments"),
    ("users", "/users"), ("coupons", "/admin/coupons"), ("referrals", "/admin/referrals"),
    ("settings", "/admin/settings"), ("messages", "/admin/messages"), ("menu", "/admin/menu"),
    ("logs", "/admin/logs"),
]:
    status, body = call("GET", path, headers=ADMIN)
    check(f"admin {name}", status == 200, body)

print(f"\npassed: {passed}   failed: {len(failed)}   skipped: {len(skipped)}")
if failed:
    print("failed checks: " + ", ".join(failed))
sys.exit(1 if failed else 0)
