"""BOB Passport — verifiable agent identity credential.

A passport proves an agent's BOB Score, tier, and identity signals.
Businesses verify passports to decide whether to trust an agent.

Usage (3 lines)::

    from bob import verify_passport

    result = verify_passport(passport, min_score=400)
    if not result["valid"]:
        raise PermissionError(result["reason"])
"""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from typing import Any, Optional

# ---------------------------------------------------------------------------
# Types
# ---------------------------------------------------------------------------

@dataclass
class PassportSubjectKey:
    kid: str = ""
    alg: str = ""
    public_key_jwk: dict = field(default_factory=dict)

@dataclass
class PassportSubject:
    agent_id: str = ""
    key: PassportSubjectKey = field(default_factory=PassportSubjectKey)

@dataclass
class PassportTrust:
    score: int = 0
    tier: str = ""
    tier_color: str = ""
    proven_rails: list[str] = field(default_factory=list)
    payment_history_sources: list[str] = field(default_factory=list)
    signals: dict = field(default_factory=dict)

@dataclass
class PassportProof:
    type: str = ""
    created: str = ""
    verification_method: str = ""
    signature: str = ""

@dataclass
class PassportBacking:
    operator_id: str = ""
    kyc_verified: bool = False
    linked_identity_rails: list[str] = field(default_factory=list)

@dataclass
class PassportCredentialSubject:
    """W3C VC 2.0 credentialSubject."""
    id: str = ""  # did:bob:agent:{agentId}
    version: str = ""
    subject: PassportSubject = field(default_factory=PassportSubject)
    backing: PassportBacking = field(default_factory=PassportBacking)
    trust: PassportTrust = field(default_factory=PassportTrust)

@dataclass
class Passport:
    """BOB Agent Passport — W3C Verifiable Credential Data Model 2.0."""
    context: list[str] = field(default_factory=list)
    id: str = ""
    type: list[str] = field(default_factory=list)
    issuer: str = ""
    valid_from: str = ""
    valid_until: str = ""
    credential_subject: PassportCredentialSubject = field(default_factory=PassportCredentialSubject)
    proof: PassportProof = field(default_factory=PassportProof)

@dataclass
class PassportStatus:
    credential_id: str = ""
    valid: bool = False
    revoked: bool = False
    superseded_by: Optional[str] = None
    error: Optional[str] = None

# ---------------------------------------------------------------------------
# Tier thresholds (must match pkg/score/engine.go)
# ---------------------------------------------------------------------------

TIER_THRESHOLDS = {
    "Legendary": 925,
    "Elite": 800,
    "Trusted": 650,
    "Established": 500,
    "Verified": 400,
    "New": 300,
    "Unverified": 150,
}

# ---------------------------------------------------------------------------
# Verify
# ---------------------------------------------------------------------------

_DEFAULT_ALLOWED_ISSUERS = ["did:web:bobscore.ai"]

def verify_passport(
    passport: dict | Passport,
    *,
    min_score: int | None = None,
    min_tier: str | None = None,
    require_rails: list[str] | None = None,
    max_age_seconds: int | None = None,
    api_url: str | None = None,
    allowed_issuers: list[str] | None = None,
) -> dict:
    """Verify a BOB Passport's claims against your policy requirements.

    NOTE: This is a policy check, not a cryptographic signature verification.
    It validates structure, expiry, issuer, score, tier, and rails — but does
    not verify the secp256k1 issuer signature. For full cryptographic
    verification, use the online status check (api_url) or implement signature
    recovery using the DID document.

    Args:
        passport: Passport dict or Passport dataclass.
        min_score: Minimum BOB Score required.
        min_tier: Minimum tier (checks score threshold).
        require_rails: Require specific proven rails.
        max_age_seconds: Maximum passport age in seconds.
        api_url: BOB API URL for online revocation check.
        allowed_issuers: Trusted issuer DIDs. Default: ["did:web:bobscore.ai"].

    Returns:
        dict with keys: valid, reason, score, tier, agent_id
    """
    if isinstance(passport, Passport):
        p = passport
    elif isinstance(passport, dict):
        p = _dict_to_passport(passport)
    else:
        return {"valid": False, "reason": "invalid passport type"}

    # Structure
    cs = p.credential_subject
    if not p.id or not cs.subject.agent_id or not cs.trust:
        return {"valid": False, "reason": "invalid passport structure"}

    # Issuer — locked to known BOB issuers by default
    issuers = allowed_issuers or _DEFAULT_ALLOWED_ISSUERS
    if p.issuer not in issuers:
        return {"valid": False, "reason": f"untrusted issuer: {p.issuer}"}

    # Expiry
    try:
        from datetime import datetime, timezone
        expiry = datetime.fromisoformat(p.valid_until.replace("Z", "+00:00"))
        if expiry < datetime.now(timezone.utc):
            return {"valid": False, "reason": "passport expired"}
    except (ValueError, AttributeError):
        return {"valid": False, "reason": "invalid expiration date"}

    # Max age
    if max_age_seconds is not None:
        try:
            issued = datetime.fromisoformat(p.valid_from.replace("Z", "+00:00"))
            age = (datetime.now(timezone.utc) - issued).total_seconds()
            if age > max_age_seconds:
                return {"valid": False, "reason": f"passport too old ({int(age)}s > {max_age_seconds}s)"}
        except (ValueError, AttributeError):
            pass

    # Score
    score = cs.trust.score
    if min_score is not None and score < min_score:
        return {"valid": False, "reason": f"score {score} below minimum {min_score}"}

    # Tier
    if min_tier is not None:
        threshold = TIER_THRESHOLDS.get(min_tier)
        if threshold is not None and score < threshold:
            return {"valid": False, "reason": f"score {score} below {min_tier} threshold ({threshold})"}

    # Rails
    if require_rails:
        proven = set(cs.trust.proven_rails or [])
        for rail in require_rails:
            if rail not in proven:
                return {"valid": False, "reason": f"missing required rail: {rail}"}

    # Online revocation check
    if api_url:
        try:
            import urllib.request
            import json
            url = f"{api_url}/api/v1/credentials/{p.id}/status"
            with urllib.request.urlopen(url, timeout=5) as resp:
                status = json.loads(resp.read())
                if not status.get("valid"):
                    return {"valid": False, "reason": status.get("error", "revoked or invalid")}
        except Exception:
            pass  # Network error — fall through to offline only

    return {
        "valid": True,
        "score": score,
        "tier": cs.trust.tier,
        "agent_id": cs.subject.agent_id,
    }


# ---------------------------------------------------------------------------
# VC-JWT decode (no crypto verification)
# ---------------------------------------------------------------------------

def decode_passport_jwt(token: str) -> dict:
    """Decode a VC-JWT without verifying the signature.

    Returns dict with keys: header, payload, passport.

    NOTE: This does NOT verify the ES256K signature. For full verification,
    use the BOB API status endpoint or a secp256k1 library.
    """
    import base64
    import json

    parts = token.split(".")
    if len(parts) != 3:
        raise ValueError("Invalid JWT: expected 3 dot-separated parts")

    def b64url_decode(s: str) -> bytes:
        s += "=" * (4 - len(s) % 4)
        return base64.urlsafe_b64decode(s)

    header = json.loads(b64url_decode(parts[0]))
    if header.get("alg") != "ES256K":
        raise ValueError(f"Unsupported JWT algorithm: {header.get('alg')}")

    payload = json.loads(b64url_decode(parts[1]))
    passport_data = payload.get("vc")
    if not passport_data:
        raise ValueError("JWT does not contain a vc claim")

    return {"header": header, "payload": payload, "passport": passport_data}


def verify_passport_jwt(
    token: str,
    **kwargs,
) -> dict:
    """Verify a passport from a JWT string.

    Decodes the JWT, extracts the passport, and runs the same policy checks
    as verify_passport().

    NOTE: Does NOT verify the ES256K signature.
    """
    try:
        result = decode_passport_jwt(token)
        return verify_passport(result["passport"], **kwargs)
    except (ValueError, KeyError) as e:
        return {"valid": False, "reason": str(e)}


def _dict_to_passport(d: dict) -> Passport:
    """Convert a raw dict to a Passport dataclass (W3C VC 2.0 shape)."""
    cs_raw = d.get("credentialSubject", {})
    subject_raw = cs_raw.get("subject", {})
    key_raw = subject_raw.get("key", {})
    backing_raw = cs_raw.get("backing", {})
    trust_raw = cs_raw.get("trust", {})
    proof_raw = d.get("proof", {})

    return Passport(
        context=d.get("@context", []),
        id=d.get("id", ""),
        type=d.get("type", []),
        issuer=d.get("issuer", ""),
        valid_from=d.get("validFrom", ""),
        valid_until=d.get("validUntil", ""),
        credential_subject=PassportCredentialSubject(
            id=cs_raw.get("id", ""),
            version=cs_raw.get("version", ""),
            subject=PassportSubject(
                agent_id=subject_raw.get("agent_id", ""),
                key=PassportSubjectKey(
                    kid=key_raw.get("kid", ""),
                    alg=key_raw.get("alg", ""),
                    public_key_jwk=key_raw.get("public_key_jwk", {}),
                ),
            ),
            backing=PassportBacking(
                operator_id=backing_raw.get("operator_id", ""),
                kyc_verified=backing_raw.get("kyc_verified", False),
                linked_identity_rails=backing_raw.get("linked_identity_rails", []),
            ),
            trust=PassportTrust(
                score=trust_raw.get("score", 0),
                tier=trust_raw.get("tier", ""),
                tier_color=trust_raw.get("tier_color", ""),
                proven_rails=trust_raw.get("proven_rails", []),
                payment_history_sources=trust_raw.get("payment_history_sources", []),
                signals=trust_raw.get("signals", {}),
            ),
        ),
        proof=PassportProof(
            type=proof_raw.get("type", ""),
            created=proof_raw.get("created", ""),
            verification_method=proof_raw.get("verification_method", ""),
            signature=proof_raw.get("signature", ""),
        ),
    )
