#!/usr/bin/env python3
"""Task 16 — PWA (installable web app) test suite.

Covers: root-scope SW + manifest routes, manifest content & icon files,
offline page self-containment, head metas, install banner markup/JS on
both template families, i18n keys, CSS layout rules, cache strategy
safety (admin/api/auth bypass), and page-render regressions.
"""
import json
import os
import re
import sys
import urllib.request
import urllib.error
import urllib.parse
import http.cookiejar

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)

BASE = "http://127.0.0.1:5000"
STATIC = os.path.join(ROOT, "app", "static")

# cookie-preserving opener (login must survive redirects)
JAR = http.cookiejar.CookieJar()
OPENER = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(JAR))

PASS = 0
FAIL = 0


def check(name, cond, detail=""):
    global PASS, FAIL
    if cond:
        PASS += 1
        print(f"  ✓ {name}")
    else:
        FAIL += 1
        print(f"  ✗ {name}  {detail}")


def get(path, cookie=None):
    req = urllib.request.Request(BASE + path)
    if cookie:
        req.add_header("Cookie", cookie)
    try:
        with OPENER.open(req) as r:
            return r.status, dict(r.headers), r.read().decode("utf-8", "replace")
    except urllib.error.HTTPError as e:
        return e.code, dict(e.headers), e.read().decode("utf-8", "replace")


def post(path, data, cookie=None):
    body = urllib.parse.urlencode(data).encode()
    req = urllib.request.Request(BASE + path, data=body, method="POST")
    if cookie:
        req.add_header("Cookie", cookie)
    try:
        with OPENER.open(req) as r:
            return r.status, dict(r.headers), r.read().decode("utf-8", "replace")
    except urllib.error.HTTPError as e:
        return e.code, dict(e.headers), e.read().decode("utf-8", "replace")


import urllib.error
import urllib.parse

print("── 1) Service worker route (/sw.js) ──")
st, hd, sw = get("/sw.js")
check("SW served at root with 200", st == 200, f"got {st}")
check("SW content-type is javascript", "javascript" in hd.get("Content-Type", ""), hd.get("Content-Type"))
check("Service-Worker-Allowed: / header", hd.get("Service-Worker-Allowed") == "/", hd.get("Service-Worker-Allowed"))
check("SW revalidated on every load (no-cache)", "no-cache" in hd.get("Cache-Control", ""), hd.get("Cache-Control"))
check("SW has cache VERSION constant", "fightskill-pwa-v2" in sw)
check("SW precaches offline.html", "'/static/offline.html'" in sw)
check("SW never caches /admin", "'/admin'" in sw)
check("SW never caches /api", "'/api'" in sw)
check("SW never caches /auth", "'/auth'" in sw)
check("SW navigation = network-first + offline fallback", "isNavigation" in sw and "offline.html" in sw)
check("SW static assets = stale-while-revalidate", "startsWith('/static/')" in sw)
check("SW refuses Set-Cookie responses", "Set-Cookie" in sw)
check("SW wipes old caches on activate", "caches.delete" in sw)
check("SW skipWaiting message channel", "SKIP_WAITING" in sw)

print("── 2) Manifest route (/manifest.webmanifest) ──")
st, hd, mf = get("/manifest.webmanifest")
check("Manifest 200 at root", st == 200, f"got {st}")
check("Manifest content-type", "manifest" in hd.get("Content-Type", ""), hd.get("Content-Type"))
m = json.loads(mf)
check("Manifest name/short_name", m.get("name") and m.get("short_name") == "FIGHTSKILL")
check("Manifest display standalone", m.get("display") == "standalone")
check("Manifest scope /", m.get("scope") == "/")
check("Manifest start_url /home?source=pwa", m.get("start_url") == "/home?source=pwa", m.get("start_url"))
check("Manifest theme/background colors", m.get("theme_color") == "#131019" and m.get("background_color") == "#131019")
check("Manifest has 4 icons (any+maskable)", len(m.get("icons", [])) == 4 and
      any(i.get("purpose") == "maskable" for i in m.get("icons", [])))
check("Manifest has 4 shortcuts", len(m.get("shortcuts", [])) == 4)
missing_icons = [i["src"] for i in m.get("icons", [])
                 if not os.path.exists(STATIC + i["src"][len("/static"):])]
check("All manifest icon files exist", not missing_icons, str(missing_icons))
missing_sc = [s["url"].split("?")[0] for s in m.get("shortcuts", []) if not s["url"].startswith("/")]
check("Shortcut URLs are same-origin root paths", not missing_sc, str(missing_sc))

print("── 3) Icon assets ──")
from PIL import Image
EXPECT = {"icon-512.png": 512, "icon-192.png": 192, "maskable-512.png": 512,
          "maskable-192.png": 192, "apple-touch-icon.png": 180,
          "favicon-32.png": 32, "favicon-16.png": 16}
for name, size in EXPECT.items():
    p = os.path.join(STATIC, "icons", name)
    ok = os.path.exists(p) and Image.open(p).size == (size, size)
    check(f"icon {name} is {size}x{size}", ok)

print("── 4) Offline page ──")
st, hd, off = get("/static/offline.html")
check("offline.html served", st == 200)
check("offline page is self-contained (no external src/href)",
      not re.search(r'(?:src|href)\s*=\s*["\']https?://', off))
check("offline page has 4 languages", all(x in off for x in ['fa: {', 'en: {', 'ar: {', 'tr: {']))
check("offline page auto-reloads on reconnect", "addEventListener('online'" in off)
check("offline page is RTL-aware", "dir = 'rtl'" in off.replace('documentElement.dir', 'dir'))
check("offline page respects reduced motion", "prefers-reduced-motion" in off)

print("── 5) Login as admin → webapp pages include PWA bits ──")
post("/auth/login", {"email": "admin@vapolyx.gg", "password": "Admin@123"})
cookies = "; ".join(f"{c.name}={c.value}" for c in JAR)
check("login created a session", "session" in cookies, cookies)
st, hd, home = get("/home")
check("admin /home renders (after login)", st == 200, f"got {st}")
check("<link rel=manifest>", 'rel="manifest"' in home and '/manifest.webmanifest' in home)
check("theme-color meta", 'name="theme-color"' in home and '#131019' in home)
check("apple-touch-icon link", 'rel="apple-touch-icon"' in home)
check("apple-mobile-web-app-capable", 'apple-mobile-web-app-capable' in home)
check("install banner markup present", 'id="pwaInstall"' in home)
check("install button present", 'id="pwaInstallBtn"' in home)
check("SW registration JS present", "navigator.serviceWorker.register('/sw.js'" in home)
check("banner is hidden by default (no flash)", re.search(r'class="pwa-install"\s+hidden', home) is not None)
check("EN title translated", "Install FIGHTSKILL" in home)
check("iOS guide strings present", "pwa_ios_title" in home or "Install on iOS" in home)
check("dismiss stored in localStorage", "fightskill_pwa_dismissed_at" in home)
check("standalone check (no banner inside installed app)", "display-mode: standalone" in home)

print("── 6) Legacy + standalone template families ──")
st, hd, login = get("/auth/login")
check("auth/login (standalone) has manifest link", 'rel="manifest"' in login)
check("auth/login (standalone) registers SW", "navigator.serviceWorker.register('/sw.js'" in login)
check("auth/login has install banner", 'id="pwaInstall"' in login)
st, hd, nf = get("/definitely-not-a-page-404")
check("legacy base (404 page) has manifest link", st == 404 and 'rel="manifest"' in nf, f"got {st}")
check("legacy base (404 page) registers SW", "navigator.serviceWorker.register('/sw.js'" in nf)
st, hd, land = get("/landing")
check("landing page has install banner", st == 200 and 'id="pwaInstall"' in land, f"got {st}")

print("── 7) i18n files ──")
for lang, needle in [("en", "Install FIGHTSKILL"), ("fa", "نصب FIGHTSKILL"),
                     ("ar", "ثبّت FIGHTSKILL"), ("tr", "FIGHTSKILL'i yükle")]:
    with open(os.path.join(ROOT, "app", "i18n", "locales", f"{lang}.json"), encoding="utf-8") as f:
        d = json.load(f)
    check(f"{lang}.json pwa_install_title + text + ios", d.get("pwa_install_title") == needle and
          bool(d.get("pwa_install_text")) and bool(d.get("pwa_ios_text")))

print("── 8) CSS layout rules ──")
with open(os.path.join(STATIC, "css", "design.css"), encoding="utf-8") as f:
    css = f.read()
check(".pwa-install styled", ".pwa-install {" in css)
check("banner anchors opposite the FAB (logical inset)", "inset-inline-end: 1.25rem" in css)
check("banner hidden attr respected", ".pwa-install[hidden]" in css)
check("mobile: banner stacked above FAB (no overlap)", "calc(7.8rem + env(safe-area-inset-bottom, 0px))" in css)
check("mobile: FAB raised above bottom-nav", "bottom: calc(4.9rem + env(safe-area-inset-bottom, 0px))" in css)
check("banner respects reduced motion", ".pwa-install { animation: none; }" in css)

print("── 9) Page regressions ──")
for path in ["/tournaments", "/results", "/leaderboard", "/wallet", "/how-it-works"]:
    st, hd, body = get(path)
    check(f"{path} renders 200 with PWA bits", st == 200 and 'id="pwaInstall"' in body, f"got {st}")
st, hd, adm = get("/admin/")
check("admin panel unaffected (renders)", st == 200, f"got {st}")

print(f"\n══ TASK 16 PWA: {PASS} passed, {FAIL} failed ══")
sys.exit(1 if FAIL else 0)
