"""
QA script for Phase 14 — Enterprise Readiness (API Keys + SSO Config).
Run with: python -X utf8 scripts/qa_phase14.py
"""

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

import requests

BASE = "http://localhost:8000/api/v1"
PASS = "\033[92m✓\033[0m"
FAIL = "\033[91m✗\033[0m"

passed = 0
failed = 0


def check(label: str, condition: bool, detail: str = ""):
    global passed, failed
    if condition:
        passed += 1
        print(f"  {PASS} {label}")
    else:
        failed += 1
        msg = f"  {FAIL} {label}"
        if detail:
            msg += f" — {detail}"
        print(msg)


def get_token(username="administrator", password="admin123"):
    r = requests.post(f"{BASE}/auth/login",
                      data={"username": username, "password": password})
    if r.status_code == 200:
        return r.json().get("access_token") or r.json().get("token")
    raise RuntimeError(f"Login failed: {r.status_code} {r.text[:200]}")


def auth(token):
    return {"Authorization": f"Bearer {token}"}


# ─────────────────────────────────────────────────────────────────────────────

def main():
    print("\n====== Phase 14 QA — Enterprise Readiness ======\n")

    token = get_token()
    h = auth(token)

    # ── Block 1: Create API key ───────────────────────────────────────────────
    print("── Block 1: Create API key ──")

    r = requests.post(f"{BASE}/api-keys", headers=h, json={
        "name": "QA Test Key",
        "description": "Automated QA key",
        "scopes": ["read", "write"],
    })
    check("POST /api-keys → 201", r.status_code == 201, r.text[:200])
    created = r.json() if r.status_code == 201 else {}

    check("Created key has id",        bool(created.get("id")))
    check("Created key has raw_key",   bool(created.get("raw_key")))
    check("raw_key starts with aegis_", str(created.get("raw_key", "")).startswith("aegis_"))
    check("key_prefix present",        bool(created.get("key_prefix")))
    check("key_prefix matches raw_key start",
          str(created.get("raw_key", "")).startswith(str(created.get("key_prefix", "X"))))
    check("scopes correct",            set(created.get("scopes", [])) == {"read", "write"})
    check("is_active = True",          created.get("is_active") == True)
    check("raw_key NOT in key_prefix", created.get("raw_key") != created.get("key_prefix"))

    raw_key = created.get("raw_key", "")
    key_id  = created.get("id", "")

    # Create a read-only key
    r2 = requests.post(f"{BASE}/api-keys", headers=h, json={
        "name": "QA Read-only Key",
        "scopes": ["read"],
    })
    check("POST second key (read-only) → 201", r2.status_code == 201, r2.text[:100])
    raw_key_ro = r2.json().get("raw_key", "") if r2.status_code == 201 else ""
    key_id_ro  = r2.json().get("id", "") if r2.status_code == 201 else ""

    # Invalid scope → 422
    r3 = requests.post(f"{BASE}/api-keys", headers=h, json={
        "name": "Bad Scope Key",
        "scopes": ["superuser"],
    })
    check("POST with invalid scope → 422", r3.status_code == 422)

    print()

    # ── Block 2: List & Get API keys ─────────────────────────────────────────
    print("── Block 2: List & Get API keys ──")

    r = requests.get(f"{BASE}/api-keys", headers=h)
    check("GET /api-keys → 200", r.status_code == 200, r.text[:100])
    keys = r.json() if r.status_code == 200 else []
    check("List not empty", len(keys) > 0)
    check("List contains our key", any(k["id"] == key_id for k in keys))
    check("raw_key NOT in list response", all("raw_key" not in k for k in keys))
    check("key_hash NOT in list response", all("key_hash" not in k for k in keys))

    r = requests.get(f"{BASE}/api-keys/{key_id}", headers=h)
    check("GET /api-keys/{id} → 200", r.status_code == 200)
    k = r.json() if r.status_code == 200 else {}
    check("Get returns correct id", k.get("id") == key_id)
    check("raw_key NOT in get response", "raw_key" not in k)

    # 404 for non-existent key
    r = requests.get(
        f"{BASE}/api-keys/00000000-0000-0000-0000-000000000001", headers=h
    )
    check("GET non-existent key → 404", r.status_code == 404)

    print()

    # ── Block 3: Authenticate with API key ───────────────────────────────────
    print("── Block 3: Authenticate with API key ──")

    if raw_key:
        api_key_header = {"Authorization": f"Bearer {raw_key}"}

        r = requests.get(f"{BASE}/techniques", headers=api_key_header)
        check("GET /techniques with API key → 200", r.status_code == 200,
              r.text[:150])

        r = requests.get(f"{BASE}/risk/summary", headers=api_key_header)
        check("GET /risk/summary with API key → 200", r.status_code == 200)

        r = requests.get(f"{BASE}/dashboard/kpis", headers=api_key_header)
        check("GET /dashboard/kpis with API key → 200", r.status_code == 200)

        # Verify last_used_at was updated
        r = requests.get(f"{BASE}/api-keys/{key_id}", headers=h)
        check("last_used_at updated after API key use",
              r.status_code == 200 and r.json().get("last_used_at") is not None)

    # Invalid / garbage key → 401
    r = requests.get(f"{BASE}/techniques",
                     headers={"Authorization": "Bearer aegis_deadbeefdeadbeef"})
    check("Invalid API key → 401", r.status_code == 401)

    print()

    # ── Block 4: Update API key ───────────────────────────────────────────────
    print("── Block 4: Update API key ──")

    r = requests.patch(f"{BASE}/api-keys/{key_id}", headers=h, json={
        "name": "QA Test Key (updated)",
        "scopes": ["read"],
    })
    check("PATCH /api-keys/{id} → 200", r.status_code == 200, r.text[:150])
    upd = r.json() if r.status_code == 200 else {}
    check("Name updated", upd.get("name") == "QA Test Key (updated)")
    check("Scopes updated to [read]", upd.get("scopes") == ["read"])

    print()

    # ── Block 5: Revoke API key ───────────────────────────────────────────────
    print("── Block 5: Revoke API key ──")

    if key_id_ro:
        r = requests.post(f"{BASE}/api-keys/{key_id_ro}/revoke", headers=h)
        check("POST /api-keys/{id}/revoke → 200", r.status_code == 200,
              r.text[:100])
        rev = r.json() if r.status_code == 200 else {}
        check("is_active = False after revoke", rev.get("is_active") == False)

        # Revoked key should no longer authenticate
        if raw_key_ro:
            r = requests.get(f"{BASE}/techniques",
                             headers={"Authorization": f"Bearer {raw_key_ro}"})
            check("Revoked API key → 401", r.status_code == 401)

    print()

    # ── Block 6: SSO status (public) ─────────────────────────────────────────
    print("── Block 6: SSO status & config ──")

    r = requests.get(f"{BASE}/sso/status")
    check("GET /sso/status → 200 (no auth required)", r.status_code == 200,
          r.text[:150])
    status_data = r.json() if r.status_code == 200 else {}
    check("SSO status has 'enabled' field", "enabled" in status_data)
    check("SSO status has 'configured' field", "configured" in status_data)

    # SP metadata (may be 503 if no config yet, but endpoint exists)
    r = requests.get(f"{BASE}/sso/metadata")
    check("GET /sso/metadata returns XML or 404/503",
          r.status_code in (200, 404, 503))
    if r.status_code == 200:
        check("Metadata is XML", "EntityDescriptor" in r.text
              or "xml" in r.headers.get("content-type", ""))

    # Get config (none yet → 404 or empty)
    r = requests.get(f"{BASE}/sso/config", headers=h)
    check("GET /sso/config (admin) → 200 or 404", r.status_code in (200, 404))

    # Create/update SSO config
    sso_payload = {
        "is_enabled": False,
        "provider_name": "Test IdP",
        "sp_entity_id": "https://aegis.example.com/api/v1/sso/metadata",
        "sp_acs_url": "https://aegis.example.com/api/v1/sso/callback",
        "idp_entity_id": "https://idp.example.com",
        "idp_sso_url": "https://idp.example.com/sso/saml",
        "idp_certificate": "MIIC...fakecert",
        "attr_email": "email",
        "attr_username": "username",
        "attr_role": "role",
        "default_role": "viewer",
        "auto_provision": True,
    }
    r = requests.put(f"{BASE}/sso/config", headers=h, json=sso_payload)
    check("PUT /sso/config (admin) → 200", r.status_code == 200, r.text[:200])
    cfg = r.json() if r.status_code == 200 else {}
    check("Config has provider_name",   cfg.get("provider_name") == "Test IdP")
    check("Config has idp_entity_id",   bool(cfg.get("idp_entity_id")))
    check("sp_private_key NOT in response", "sp_private_key" not in cfg)

    # Re-fetch config
    r = requests.get(f"{BASE}/sso/config", headers=h)
    check("GET /sso/config → 200 after upsert", r.status_code == 200)

    # Status should now reflect configured=True
    r = requests.get(f"{BASE}/sso/status")
    st = r.json() if r.status_code == 200 else {}
    check("SSO configured=True after upsert", st.get("configured") == True)
    check("SSO enabled=False (we set it off)", st.get("enabled") == False)

    print()

    # ── Block 7: SSO login endpoint (disabled → 503) ──────────────────────────
    print("── Block 7: SSO login (disabled) ──")

    r = requests.get(f"{BASE}/sso/login", allow_redirects=False)
    # Should be 503 (disabled) or 307/302 (if enabled and library present)
    check("GET /sso/login returns 5xx when disabled or no lib",
          r.status_code in (503, 302, 307, 400))

    print()

    # ── Block 8: Auth protection ──────────────────────────────────────────────
    print("── Block 8: Auth protection ──")

    r = requests.get(f"{BASE}/api-keys")
    check("GET /api-keys without auth → 401", r.status_code == 401)

    r = requests.post(f"{BASE}/api-keys", json={"name": "x", "scopes": ["read"]})
    check("POST /api-keys without auth → 401", r.status_code == 401)

    r = requests.get(f"{BASE}/sso/config")
    check("GET /sso/config without auth → 401", r.status_code == 401)

    r = requests.put(f"{BASE}/sso/config", json={"is_enabled": False})
    check("PUT /sso/config without auth → 401", r.status_code == 401)

    # Non-admin cannot delete keys
    # (would need a non-admin user setup — skip for now)

    print()

    # ── Block 9: Cleanup ─────────────────────────────────────────────────────
    print("── Block 9: Cleanup ──")

    # Hard-delete the QA keys (admin)
    for kid in [key_id, key_id_ro]:
        if kid:
            r = requests.delete(f"{BASE}/api-keys/{kid}", headers=h)
            check(f"DELETE /api-keys/{kid[:8]}... → 204", r.status_code == 204,
                  r.text[:100])

    print()

    # ── Block 10: Regression ─────────────────────────────────────────────────
    print("── Block 10: Regression ──")

    r = requests.get(f"{BASE}/dashboard/kpis", headers=h)
    check("GET /dashboard/kpis still works", r.status_code == 200)

    r = requests.get(f"{BASE}/risk/summary", headers=h)
    check("GET /risk/summary still works", r.status_code == 200)

    r = requests.get(f"{BASE}/knowledge/playbooks", headers=h)
    check("GET /knowledge/playbooks still works", r.status_code == 200)

    r = requests.get(f"{BASE}/techniques", headers=h)
    check("GET /techniques still works", r.status_code == 200)

    print()

    # ── Summary ───────────────────────────────────────────────────────────────
    total = passed + failed
    print(f"====== Results: {passed}/{total} passed", end="")
    if failed:
        print(f" — \033[91m{failed} FAILED\033[0m ======\n")
        sys.exit(1)
    else:
        print(" ✓ ALL PASSED ======\n")


if __name__ == "__main__":
    main()
