"""Comprehensive SDK verification — validates all modules, interop, and edge cases."""

import sys
import os
import json
import base64
import subprocess

# Ensure the SDK source is on the path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))

import zephr
from zephr.crypto import generate_key, encrypt, decrypt
from zephr.memory import zero_bytes
from zephr.link import generate_link
from zephr.limits import SECRET_MAX_LENGTH, SECRET_MAX_BYTES
from zephr.exceptions import (
    ZephrError,
    EncryptionError,
    ValidationError,
    ApiError,
    NetworkError,
)

passed = 0
failed = 0


def check(name, condition):
    global passed, failed
    if condition:
        passed += 1
        print(f"  [OK] {name}")
    else:
        failed += 1
        print(f"  [FAIL] {name}")


# ── 1. Version ──────────────────────────────────────────────────────
print("\n1. Version")
check("version is 0.3.0", zephr.__version__ == "0.3.0")

# ── 2. Public API surface ──────────────────────────────────────────
print("\n2. Public API surface (__all__)")
expected_all = {
    "create_secret",
    "retrieve_secret",
    "ZephrError",
    "EncryptionError",
    "ValidationError",
    "ApiError",
    "NetworkError",
}
check("__all__ matches expected", set(zephr.__all__) == expected_all)
check("create_secret callable", callable(zephr.create_secret))
check("retrieve_secret callable", callable(zephr.retrieve_secret))

# ── 3. Exception hierarchy ─────────────────────────────────────────
print("\n3. Exception hierarchy")
check("ZephrError is base", issubclass(ZephrError, Exception))
check("EncryptionError extends ZephrError", issubclass(EncryptionError, ZephrError))
check("ValidationError extends ZephrError", issubclass(ValidationError, ZephrError))
check("ApiError extends ZephrError", issubclass(ApiError, ZephrError))
check("NetworkError extends ZephrError", issubclass(NetworkError, ZephrError))
check("ApiError has status_code", ApiError("test", status_code=429).status_code == 429)
check("ApiError status_code defaults None", ApiError("test").status_code is None)
check("ApiError has code", ApiError("test", code="RATE_LIMIT_EXCEEDED").code == "RATE_LIMIT_EXCEEDED")
check("ApiError code defaults None", ApiError("test").code is None)
check("ApiError accepts status_code + code", ApiError("test", status_code=429, code="RATE_LIMIT_EXCEEDED").code == "RATE_LIMIT_EXCEEDED")

# ── 4. Limits ───────────────────────────────────────────────────────
print("\n4. Limits")
check("SECRET_MAX_LENGTH is 2048", SECRET_MAX_LENGTH == 2048)
check("SECRET_MAX_BYTES is 2048", SECRET_MAX_BYTES == 2048)

# ── 5. Validation (via create_secret) ──────────────────────────────
print("\n5. Validation")

def expect_validation_error(name, fn):
    try:
        fn()
        check(name, False)
    except ValidationError:
        check(name, True)
    except Exception as e:
        print(f"  [FAIL] {name} — got {type(e).__name__}: {e}")
        global failed
        failed += 1

expect_validation_error("empty string", lambda: zephr.create_secret(""))
expect_validation_error("whitespace only", lambda: zephr.create_secret("   "))
expect_validation_error("newline only", lambda: zephr.create_secret("\n"))
expect_validation_error("over limit (ASCII)", lambda: zephr.create_secret("x" * 2049))
expect_validation_error("non-string (int)", lambda: zephr.create_secret(123))
expect_validation_error("non-string (None)", lambda: zephr.create_secret(None))
expect_validation_error("non-string (bytes)", lambda: zephr.create_secret(b"hello"))
expect_validation_error("invalid expiry (2)", lambda: zephr.create_secret("ok", expiry_hours=2))
expect_validation_error("invalid expiry (0)", lambda: zephr.create_secret("ok", expiry_hours=0))
expect_validation_error("invalid expiry (str)", lambda: zephr.create_secret("ok", expiry_hours="24"))
expect_validation_error("non-bool split", lambda: zephr.create_secret("ok", split="yes"))

# Byte-based limit: 512 emoji × 4 bytes = 2,048 bytes — exactly at limit, should pass
# May succeed end-to-end (if server is reachable) or fail at the network layer — either
# outcome proves validation passed. Only a ValidationError is a failure.
try:
    zephr.create_secret("\U0001f512" * 512)
    check("512 emoji at byte limit passes validation", True)
except (NetworkError, ApiError):
    check("512 emoji at byte limit passes validation (fails at network)", True)
except ValidationError:
    check("512 emoji at byte limit passes validation", False)

# 513 emoji × 4 bytes = 2,052 bytes — over limit, should fail validation
expect_validation_error(
    "513 emoji over byte limit (2052 bytes)",
    lambda: zephr.create_secret("\U0001f512" * 513),
)

# Boundary: exactly at ASCII limit should NOT raise validation error
try:
    zephr.create_secret("x" * 2048)
    check("at-limit passes validation", True)
except (NetworkError, ApiError):
    check("at-limit passes validation (fails at network, not validation)", True)
except ValidationError:
    check("at-limit passes validation", False)

# ── 6. retrieve_secret input validation ────────────────────────────
print("\n6. retrieve_secret validation")

expect_validation_error(
    "plain integer rejected",
    lambda: zephr.retrieve_secret(42),
)
expect_validation_error(
    "None rejected",
    lambda: zephr.retrieve_secret(None),
)
expect_validation_error(
    "URL without fragment rejected",
    lambda: zephr.retrieve_secret("https://zephr.io/secret/abc1234567890123456789"),
)
expect_validation_error(
    "URL with wrong path rejected",
    lambda: zephr.retrieve_secret("https://zephr.io/notasecret/abc1234567890123456789#v1.key"),
)
expect_validation_error(
    "URL with short ID rejected",
    lambda: zephr.retrieve_secret("https://zephr.io/secret/tooshort#v1.key"),
)
expect_validation_error(
    "dict missing url rejected",
    lambda: zephr.retrieve_secret({"key": "v1.abc"}),
)
expect_validation_error(
    "dict missing key rejected",
    lambda: zephr.retrieve_secret({"url": "https://zephr.io/secret/abc1234567890123456789"}),
)
expect_validation_error(
    "dict url not string rejected",
    lambda: zephr.retrieve_secret({"url": 123, "key": "v1.abc"}),
)
expect_validation_error(
    "dict key not string rejected",
    lambda: zephr.retrieve_secret({"url": "https://zephr.io/secret/abc1234567890123456789", "key": 123}),
)

# Valid URL format should reach the network (fail with ApiError/NetworkError, not ValidationError)
try:
    zephr.retrieve_secret("https://zephr.io/secret/abc1234567890123456789#v1.testkey")
    check("valid URL format reaches network", False)
except (NetworkError, ApiError, EncryptionError):
    check("valid URL format reaches network (fails at transport, not validation)", True)
except ValidationError as e:
    check(f"valid URL format reaches network — unexpected ValidationError: {e}", False)

# Valid split dict format should reach the network
try:
    zephr.retrieve_secret({
        "url": "https://zephr.io/secret/abc1234567890123456789",
        "key": "v1.testkey",
    })
    check("valid split dict reaches network", False)
except (NetworkError, ApiError, EncryptionError):
    check("valid split dict reaches network (fails at transport, not validation)", True)
except ValidationError as e:
    check(f"valid split dict reaches network — unexpected ValidationError: {e}", False)

# ── 7. Crypto pipeline ─────────────────────────────────────────────
print("\n7. Crypto pipeline")

# Key generation
key_bytes, key_string = generate_key()
check("key is 32 bytes", len(key_bytes) == 32)
check("key string starts with v1.", key_string.startswith("v1."))
check("key string has base64url part", len(key_string.split(".")) == 2 and len(key_string.split(".")[1]) > 0)

# No padding in key
check("key has no base64 padding", "=" not in key_string)

# Encryption
plaintext = "Hello, Zephr!"
plaintext_bytes = plaintext.encode("utf-8")
blob = encrypt(plaintext_bytes, key_bytes)
check("blob is a string", isinstance(blob, str))

# Blob structure: base64url(JSON with iv + ciphertext).
# Restore padding before decoding — urlsafe_b64decode requires a multiple of 4.
blob_data = json.loads(base64.urlsafe_b64decode(blob + '=' * (-len(blob) % 4)))
check("blob has 'iv' key", "iv" in blob_data)
check("blob has 'ciphertext' key", "ciphertext" in blob_data)
check("blob has exactly 2 keys", len(blob_data) == 2)

# IV and ciphertext are base64url (no padding, no + or /)
check("iv has no padding", "=" not in blob_data["iv"])
check("iv has no + or /", "+" not in blob_data["iv"] and "/" not in blob_data["iv"])
check("ciphertext has no padding", "=" not in blob_data["ciphertext"])

# Encrypt requires bytes, not str
try:
    encrypt("not bytes", key_bytes)
    check("encrypt rejects str input", False)
except TypeError:
    check("encrypt rejects str input", True)

# Round-trip: encrypt then decrypt
rt_key_bytes, rt_key_string = generate_key()
rt_plaintext = "Round-trip test: Hello from Python SDK!"
rt_blob = encrypt(rt_plaintext.encode("utf-8"), rt_key_bytes)
rt_decrypted = decrypt(rt_blob, rt_key_string)
check("encrypt -> decrypt round-trip", rt_decrypted.decode("utf-8") == rt_plaintext)

# Decrypt with wrong key should raise EncryptionError
_, wrong_key_string = generate_key()
try:
    decrypt(rt_blob, wrong_key_string)
    check("decrypt with wrong key raises EncryptionError", False)
except EncryptionError:
    check("decrypt with wrong key raises EncryptionError", True)

# Decrypt with invalid key format should raise EncryptionError
try:
    decrypt(rt_blob, "notavalidkey")
    check("decrypt with malformed key raises EncryptionError", False)
except EncryptionError:
    check("decrypt with malformed key raises EncryptionError", True)

# Decrypt with unknown version should raise EncryptionError
try:
    decrypt(rt_blob, "v99." + "A" * 43)
    check("decrypt with unsupported version raises EncryptionError", False)
except EncryptionError:
    check("decrypt with unsupported version raises EncryptionError", True)

# Unicode round-trip
uni_rt_plaintext = "Emoji: \U0001f512 CJK: \u4f60\u597d Accent: caf\u00e9"
uni_rt_key_bytes, uni_rt_key_string = generate_key()
uni_rt_blob = encrypt(uni_rt_plaintext.encode("utf-8"), uni_rt_key_bytes)
uni_rt_decrypted = decrypt(uni_rt_blob, uni_rt_key_string)
check("Unicode encrypt -> decrypt round-trip", uni_rt_decrypted.decode("utf-8") == uni_rt_plaintext)

# ── 8. Memory cleanup ──────────────────────────────────────────────
print("\n8. Memory cleanup")
buf = bytearray(b"\xDE\xAD\xBE\xEF" * 4)
zero_bytes(buf)
check("buffer zeroed after cleanup", all(b == 0 for b in buf))

# Verify 3-pass pattern by checking intermediate states
buf2 = bytearray(8)
# After zero_bytes, final state should be all 0x00 (last pass)
zero_bytes(buf2)
check("final pass is 0x00", all(b == 0x00 for b in buf2))

# ── 9. Link generation ─────────────────────────────────────────────
print("\n9. Link generation")

# Standard mode
link = generate_link("abc123", "v1.testkey", split=False)
check("standard mode", link["mode"] == "standard")
check("standard has full_link", "full_link" in link)
check("standard link format", link["full_link"] == "https://zephr.io/secret/abc123#v1.testkey")
check("standard has no url key", "url" not in link)
check("standard has no key key", "key" not in link)

# Split mode
link_split = generate_link("abc123", "v1.testkey", split=True)
check("split mode", link_split["mode"] == "split")
check("split has url", link_split["url"] == "https://zephr.io/secret/abc123")
check("split has key", link_split["key"] == "v1.testkey")
check("split has no full_link", "full_link" not in link_split)

# ── 10. Cross-platform interop (Python encrypt -> Node.js decrypt) ──
print("\n10. Cross-platform interop")

# Encrypt with Python
interop_secret = "Cross-platform test: Hello from Python SDK!"
interop_key_bytes, interop_key_string = generate_key()
interop_blob = encrypt(interop_secret.encode("utf-8"), interop_key_bytes)

# Decrypt with Node.js (matching web app / CLI crypto)
node_script = f"""
const crypto = require('crypto');

const keyString = '{interop_key_string}';
const blob = '{interop_blob}';

// Parse key (v1.<base64url>)
const keyB64 = keyString.split('.')[1];
const keyBuf = Buffer.from(keyB64, 'base64url');

// Parse blob: base64(JSON({{iv, ciphertext}}))
const jsonStr = Buffer.from(blob, 'base64').toString('utf-8');
const {{ iv, ciphertext }} = JSON.parse(jsonStr);

const ivBuf = Buffer.from(iv, 'base64url');
const ctBuf = Buffer.from(ciphertext, 'base64url');

// AES-GCM: last 16 bytes are auth tag
const authTag = ctBuf.subarray(ctBuf.length - 16);
const encrypted = ctBuf.subarray(0, ctBuf.length - 16);

const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuf, ivBuf);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, null, 'utf-8');
decrypted += decipher.final('utf-8');

console.log(decrypted);
"""

try:
    result = subprocess.run(
        ["node", "-e", node_script],
        capture_output=True, text=True, timeout=5
    )
    decrypted = result.stdout.strip()
    check("Python encrypt -> Node.js decrypt", decrypted == interop_secret)
    if decrypted != interop_secret:
        print(f"    Expected: {interop_secret}")
        print(f"    Got:      {decrypted}")
        if result.stderr:
            print(f"    Stderr:   {result.stderr.strip()}")
except Exception as e:
    check(f"interop test failed: {e}", False)

# ── 11. Unicode interop ────────────────────────────────────────────
print("\n11. Unicode interop")

unicode_secret = "Emoji: \U0001f512 CJK: \u4f60\u597d Accent: caf\u00e9"
uni_key_bytes, uni_key_string = generate_key()
uni_blob = encrypt(unicode_secret.encode("utf-8"), uni_key_bytes)

node_unicode = f"""
const crypto = require('crypto');
const keyBuf = Buffer.from('{uni_key_string}'.split('.')[1], 'base64url');
const json = Buffer.from('{uni_blob}', 'base64').toString('utf-8');
const {{ iv, ciphertext }} = JSON.parse(json);
const ivBuf = Buffer.from(iv, 'base64url');
const ctBuf = Buffer.from(ciphertext, 'base64url');
const authTag = ctBuf.subarray(ctBuf.length - 16);
const encrypted = ctBuf.subarray(0, ctBuf.length - 16);
const d = crypto.createDecipheriv('aes-256-gcm', keyBuf, ivBuf);
d.setAuthTag(authTag);
let out = d.update(encrypted, null, 'utf-8');
out += d.final('utf-8');
console.log(out);
"""

try:
    result = subprocess.run(
        ["node", "-e", node_unicode],
        capture_output=True, text=True, timeout=5
    )
    check("Unicode interop (emoji + CJK + accents)", result.stdout.strip() == unicode_secret)
except Exception as e:
    check(f"unicode interop failed: {e}", False)

# ── 12. Refactor verification ────────────────────────────────────────
print("\n12. Refactor verification")
crypto_path = os.path.join(os.path.dirname(__file__), "src", "zephr", "crypto.py")
with open(crypto_path) as f:
    crypto_source = f.read()
check("_zero_bytes not in crypto.py", "_zero_bytes" not in crypto_source)
check("crypto.py imports from .memory", "from .memory import zero_bytes" in crypto_source)

# ── Summary ─────────────────────────────────────────────────────────
print(f"\n{'='*50}")
print(f"Results: {passed} passed, {failed} failed, {passed + failed} total")
if failed == 0:
    print("All checks passed.")
else:
    print("Some checks failed.")
    sys.exit(1)
