#!/usr/bin/env python3
"""Round 11 API Contract Test - with rate-limit awareness"""
import urllib.request
import urllib.error
import json
import time
import sys
import os

BASE = "http://localhost:8080/api/v1"
PASS = 0
FAIL = 0
TOKEN = ""
ADMIN_TOKEN = ""
SESS_ID = ""

def req(method, path, data=None, auth=None, ct="application/json"):
    url = f"{BASE}{path}"
    headers = {"Content-Type": ct}
    if auth:
        headers["Authorization"] = f"Bearer {auth}"
    body = None
    if data is not None:
        body = json.dumps(data).encode()
    try:
        r = urllib.request.Request(url, data=body, headers=headers, method=method)
        resp = urllib.request.urlopen(r, timeout=10)
        return resp.status, resp.read().decode()
    except urllib.error.HTTPError as e:
        return e.code, e.read().decode()
    except Exception as e:
        return 0, str(e)

def test(name, method, path, expected, data=None, auth=None, skip_msg=None):
    global PASS, FAIL
    if skip_msg:
        print(f"  SKIP | {name}: {skip_msg}")
        return None, None
    code, body = req(method, path, data, auth)
    status = "PASS" if code == expected else "FAIL"
    if status == "PASS":
        PASS += 1
    else:
        FAIL += 1
    body_preview = body[:100].replace('\n',' ') if body else ""
    print(f"  {status} | {name} | expected={expected} got={code} | {body_preview}")
    return code, body

print("=" * 60)
print(" Round 11 API Contract Test Suite")
print(f" Started at {time.strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)

# === Part 1: Health ===
print("\n--- Part 1: Health ---")
test("GET /health", "GET", "/health", 200)
test("HEAD /health", "HEAD", "/health", 200)

# === Part 2: Auth Register ===
print("\n--- Part 2: Auth Register ---")
UN = f"testapi_{int(time.time())}"
PW = "TestPass123!"

test("Register missing username", "POST", "/auth/register", 400,
     {"password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"})
time.sleep(1)
test("Register missing password", "POST", "/auth/register", 400,
     {"username":"testuser","email":"test@test.com","nickname":"Test","verify_code":"000000"})
time.sleep(1)
test("Register username too short", "POST", "/auth/register", 400,
     {"username":"ab","password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"})
time.sleep(1)
test("Register username too long", "POST", "/auth/register", 400,
     {"username":"a"*33,"password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"})
time.sleep(1)
test("Register username special chars", "POST", "/auth/register", 400,
     {"username":"user@name!","password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"})
time.sleep(1)
test("Register password too short", "POST", "/auth/register", 400,
     {"username":"test_abc","password":"Ab1!","email":"test@test.com","nickname":"Test","verify_code":"000000"})
time.sleep(1)

# Normal registration
code, body = test("Register normal", "POST", "/auth/register", 201,
     {"username":UN,"password":PW,"email":"test@test.com","nickname":"TestUser","verify_code":"000000"})

time.sleep(1)
# Duplicate
test("Register duplicate", "POST", "/auth/register", 409,
     {"username":UN,"password":PW,"email":"test@test.com","nickname":"TestUser","verify_code":"000000"})

# === Part 3: Auth Login ===
print("\n--- Part 3: Auth Login ---")
time.sleep(1)
test("Login wrong username", "POST", "/auth/login", 401,
     {"username":"nonexistent_user_99","password":"TestPass123!"})
time.sleep(1)
test("Login wrong password", "POST", "/auth/login", 401,
     {"username":UN,"password":"WrongPass999!"})
time.sleep(1)

# Correct login
code, body = req("POST", "/auth/login", {"username":UN,"password":PW})
if code == 200:
    data = json.loads(body)
    TOKEN = data.get("token","")
    print(f"  PASS | Login correct | 200 | user_id={data.get('user_id')} token={'OK' if TOKEN else 'MISSING'}")
    PASS += 1
else:
    print(f"  FAIL | Login correct | expected=200 got={code} | {body[:100]}")
    FAIL += 1

time.sleep(1)
# Admin login
code, body = req("POST", "/auth/login", {"username":"admin","password":"admin123"})
if code == 200:
    data = json.loads(body)
    ADMIN_TOKEN = data.get("token","")
    print(f"  PASS | Admin login | 200 | token={'OK' if ADMIN_TOKEN else 'MISSING'}")
    PASS += 1
else:
    print(f"  FAIL | Admin login | expected=200 got={code} | {body[:100]}")
    FAIL += 1

# JWT validation
if TOKEN:
    time.sleep(1)
    test("JWT valid (refresh)", "POST", "/auth/refresh", 200, auth=TOKEN)

time.sleep(1)
test("Login missing password", "POST", "/auth/login", 400, {"username":"testuser"})
time.sleep(1)
test("Login empty body", "POST", "/auth/login", 400, {})
time.sleep(1)
test("Login username format invalid", "POST", "/auth/login", 400, {"username":"ab","password":"Test123!"})

# === Part 4: Session API ===
print("\n--- Part 4: Session API ---")
test("Sessions list no auth", "GET", "/sessions", 401)
test("Sessions create no auth", "POST", "/sessions", 401, {"title":"Test"})

if TOKEN:
    time.sleep(1)
    test("Sessions list with auth", "GET", "/sessions", 200, auth=TOKEN)
    
    time.sleep(1)
    code, body = test("Sessions create", "POST", "/sessions", 201, {"title":"Round 11 Test"}, auth=TOKEN)
    if code == 201:
        try:
            SESS_ID = json.loads(body).get("id","")
        except: pass
    
    if SESS_ID:
        time.sleep(1)
        test("Sessions get existing", "GET", f"/sessions/{SESS_ID}", 200, auth=TOKEN)
    
    time.sleep(1)
    test("Sessions get non-existent", "GET", "/sessions/session_nonexistent123", 404, auth=TOKEN)
    
    time.sleep(1)
    test("Sessions create empty title", "POST", "/sessions", 201, {}, auth=TOKEN)
    
    if SESS_ID:
        time.sleep(1)
        test("Sessions delete existing", "DELETE", f"/sessions/{SESS_ID}", 200, auth=TOKEN)
    
    time.sleep(1)
    test("Sessions delete non-existent", "DELETE", "/sessions/session_nonexistent", 200, auth=TOKEN)
    
    time.sleep(1)
    test("Messages get non-existent session", "GET", "/sessions/session_nonexistent/messages", 200, auth=TOKEN)
else:
    print("  SKIP: No token available for session tests")

# === Part 5: Files ===
print("\n--- Part 5: Files API ---")
test("Files list no auth", "GET", "/files", 401)
if TOKEN:
    time.sleep(1)
    test("Files list with auth", "GET", "/files", 200, auth=TOKEN)
    time.sleep(1)
    test("Files get non-existent", "GET", "/files/file_nonexistent", 404, auth=TOKEN)

# === Part 6: Knowledge ===
print("\n--- Part 6: Knowledge API ---")
test("Knowledge bases no auth", "GET", "/knowledge/bases", 401)
if TOKEN:
    time.sleep(1)
    test("Knowledge bases with auth", "GET", "/knowledge/bases", 200, auth=TOKEN)
    time.sleep(1)
    test("Knowledge get non-existent", "GET", "/knowledge/bases/kb_nonexistent", 404, auth=TOKEN)

# === Part 7: Automation ===
print("\n--- Part 7: Automation API ---")
test("Automation rules no auth", "GET", "/automation/rules", 401)
if TOKEN:
    time.sleep(1)
    test("Automation rules with auth", "GET", "/automation/rules", 200, auth=TOKEN)
    time.sleep(1)
    test("Automation scenes with auth", "GET", "/automation/scenes", 200, auth=TOKEN)
    time.sleep(1)
    test("Automation get non-existent rule", "GET", "/automation/rules/rule_nonexistent", 404, auth=TOKEN)

# === Part 8: Reminders ===
print("\n--- Part 8: Reminders API ---")
test("Reminders list no auth", "GET", "/reminders", 401)
if TOKEN:
    time.sleep(1)
    test("Reminders list with auth (needs user_id)", "GET", "/reminders?user_id=test_user", 200, auth=TOKEN)
    time.sleep(1)
    test("Reminders create missing fields", "POST", "/reminders", 400, {}, auth=TOKEN)

# === Part 9: Briefings ===
print("\n--- Part 9: Briefings API ---")
test("Briefings get no auth", "GET", "/briefings", 401)
if TOKEN:
    time.sleep(1)
    test("Briefings get with auth", "GET", "/briefings?user_id=test_user", 200, auth=TOKEN)
    time.sleep(1)
    test("Briefings latest with auth", "GET", "/briefings/latest?user_id=test_user", 200, auth=TOKEN)

# === Part 10: Notifications ===
print("\n--- Part 10: Notifications API ---")
test("Notifications push no auth", "POST", "/notifications/push", 401, {"message":"test"})
if TOKEN:
    time.sleep(1)
    test("Notifications push empty", "POST", "/notifications/push", 400, {}, auth=TOKEN)

# === Part 11: Memories ===
print("\n--- Part 11: Memories API ---")
test("Memories search no auth", "GET", "/memory/search", 401)
if TOKEN:
    time.sleep(1)
    test("Memories search with auth", "GET", "/memory/search?q=test", 200, auth=TOKEN)
    time.sleep(1)
    test("Memories list with auth", "GET", "/memory", 200, auth=TOKEN)
    time.sleep(1)
    test("Memories add missing fields", "POST", "/memory", 400, {}, auth=TOKEN)

# === Part 12: Voice ===
print("\n--- Part 12: Voice API ---")
if TOKEN:
    time.sleep(1)
    test("Voice status with auth", "GET", "/voice/status", 200, auth=TOKEN)

# === Part 13: Admin ===
print("\n--- Part 13: Admin endpoints ---")
if ADMIN_TOKEN:
    time.sleep(1)
    test("Admin sessions with admin", "GET", "/admin/sessions", 200, auth=ADMIN_TOKEN)
    time.sleep(1)
    test("Admin sessions/active", "GET", "/admin/sessions/active", 200, auth=ADMIN_TOKEN)
if TOKEN:
    time.sleep(1)
    test("Admin sessions without admin", "GET", "/admin/sessions", 403, auth=TOKEN)

# === Part 14: Invalid token ===
print("\n--- Part 14: Invalid token ---")
test("Sessions invalid token", "GET", "/sessions", 401, auth="invalidtoken12345")
test("Sessions expired/malformed token", "GET", "/sessions", 401, auth="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8k")

# === Part 15: Token refresh ===
print("\n--- Part 15: Token refresh ---")
test("Refresh no auth", "POST", "/auth/refresh", 401)
test("Refresh invalid token", "POST", "/auth/refresh", 401, auth="invalidtoken")

# === Summary ===
print("\n" + "=" * 60)
print(" TEST SUMMARY")
print("=" * 60)
TOTAL = PASS + FAIL
print(f"Total: {TOTAL} | Passed: {PASS} | Failed: {FAIL}")
print(f"Success rate: {PASS/TOTAL*100:.1f}%" if TOTAL > 0 else "No tests run")
print(f"\nTest user: {UN}")
print(f"Token: {'available' if TOKEN else 'MISSING'}")
print(f"Admin token: {'available' if ADMIN_TOKEN else 'MISSING'}")

# Save env
if TOKEN:
    with open("/tmp/round11_testenv", "w") as f:
        f.write(f"TOKEN={TOKEN}\n")
        f.write(f"ADMIN_TOKEN={ADMIN_TOKEN}\n")
        f.write(f"TEST_USER={UN}\n")
