"""
Mandatum Python SDK
Verifiable identity and scoped permissions for AI agents.

Install: pip install mandatum
Usage:
    from mandatum import Mandatum
    m = Mandatum(api_key="mdt_live_...")
    result = m.check(token, "payments:execute", {"amount": 350})
"""

import requests
import time


class MandatumError(Exception):
    def __init__(self, message, code=None, status=None):
        super().__init__(message)
        self.code = code
        self.status = status


class Mandatum:
    """
    Mandatum client — the main entry point.

    Args:
        api_key: Your Mandatum API key (mdt_live_...)
        base_url: API base URL (default: http://localhost:3001)
        timeout: Request timeout in seconds (default: 10)
    """

    def __init__(self, api_key: str, base_url: str = "http://localhost:3001", timeout: int = 10):
        if not api_key:
            raise MandatumError("API key is required", code="MISSING_API_KEY")
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.agents = AgentsAPI(self)
        self.tokens = TokensAPI(self)
        self.policies = PoliciesAPI(self)
        self.audit = AuditAPI(self)

    def check(self, token: str, action: str, context: dict = None) -> dict:
        """
        The 1-line permission gate.

        Call this before every agent action.
        Returns dict with 'allowed', 'reason', 'receipt', etc.

        Example:
            result = m.check(token, "payments:execute", {"amount": 350, "currency": "INR"})
            if result["allowed"]:
                process_payment()
            else:
                print("Blocked:", result["reason"])
        """
        data = self._request("POST", "/tokens/verify", {
            "token": token,
            "action": action,
            "context": context or {},
        }, authenticated=False)
        return {
            "allowed": data.get("allowed", False),
            "valid": data.get("valid", False),
            "agent": data.get("agent"),
            "delegator": data.get("delegator"),
            "reason": data.get("reason"),
            "receipt": data.get("receipt"),
            "checks": data.get("checks", []),
            "scopes": data.get("scopes", []),
            "constraints": data.get("constraints", {}),
            "error": data.get("error"),
        }

    def is_allowed(self, token: str, action: str, context: dict = None) -> bool:
        """Quick boolean check — returns True/False."""
        return self.check(token, action, context)["allowed"]

    def health(self) -> dict:
        """Get server health status."""
        return self._request("GET", "/health", authenticated=False)

    def get_public_key(self) -> dict:
        """Get server Ed25519 public key for local verification."""
        return self._request("GET", "/.well-known/public-key", authenticated=False)

    def _request(self, method: str, path: str, body: dict = None, authenticated: bool = True) -> dict:
        url = f"{self.base_url}/v1{path}"
        headers = {"Content-Type": "application/json"}
        if authenticated:
            headers["Authorization"] = f"Bearer {self.api_key}"

        try:
            resp = requests.request(
                method, url, json=body, headers=headers, timeout=self.timeout
            )
            data = resp.json()
            if not resp.ok:
                raise MandatumError(
                    data.get("error", f"HTTP {resp.status_code}"),
                    code=data.get("code", "API_ERROR"),
                    status=resp.status_code,
                )
            return data
        except requests.exceptions.ConnectionError:
            raise MandatumError("Cannot connect to Mandatum server", code="CONNECTION_ERROR")
        except requests.exceptions.Timeout:
            raise MandatumError("Request timed out", code="TIMEOUT")


class AgentsAPI:
    def __init__(self, client: Mandatum):
        self._c = client

    def create(self, name: str, type: str = "general", description: str = "", delegator: str = "default") -> dict:
        """Register a new agent. Generates Ed25519 key pair on server."""
        return self._c._request("POST", "/agents", {
            "name": name, "type": type, "description": description, "delegator": delegator,
        })

    def list(self, **params) -> list:
        """List all agents."""
        qs = "&".join(f"{k}={v}" for k, v in params.items()) if params else ""
        return self._c._request("GET", f"/agents{'?' + qs if qs else ''}")

    def get(self, agent_id: str) -> dict:
        """Get agent details."""
        return self._c._request("GET", f"/agents/{agent_id}")

    def suspend(self, agent_id: str) -> dict:
        """Suspend an agent."""
        return self._c._request("PATCH", f"/agents/{agent_id}", {"status": "suspended"})

    def revoke(self, agent_id: str) -> dict:
        """Revoke an agent permanently."""
        return self._c._request("PATCH", f"/agents/{agent_id}", {"status": "revoked"})

    def spend(self, agent_id: str, params: dict) -> dict:
        """
        Record a spending event — checks against policy limits.

        Args:
            agent_id: The agent making the payment
            params: dict with 'amount', 'currency', 'vendor', 'category', 'description'

        Returns:
            dict with 'allowed', 'eventId', 'amount', 'spending' or 'reason' if blocked

        Example:
            result = m.agents.spend("agt_abc123", {
                "amount": 350, "currency": "INR", "vendor": "DigitalOcean"
            })
            if result["allowed"]:
                process_payment()
        """
        return self._c._request("POST", f"/agents/{agent_id}/spend", params)

    def spending(self, agent_id: str) -> dict:
        """Get spending summary: today/total spent, remaining budget, recent events."""
        return self._c._request("GET", f"/agents/{agent_id}/spending")


class TokensAPI:
    def __init__(self, client: Mandatum):
        self._c = client

    def issue(self, agent_id: str, scopes: list, constraints: dict = None, expires_in: str = "30d") -> dict:
        """Issue a signed JWT token for an agent."""
        return self._c._request("POST", "/tokens/issue", {
            "agentId": agent_id, "scopes": scopes,
            "constraints": constraints or {}, "expiresIn": expires_in,
        })

    def verify(self, token: str, action: str = None, context: dict = None) -> dict:
        """Verify a token (signature + constraints + revocation)."""
        return self._c._request("POST", "/tokens/verify", {
            "token": token, "action": action, "context": context or {},
        }, authenticated=False)

    def revoke(self, jti: str) -> dict:
        """Revoke a token immediately."""
        return self._c._request("POST", "/tokens/revoke", {"jti": jti})

    def list(self, **params) -> list:
        """List all tokens."""
        qs = "&".join(f"{k}={v}" for k, v in params.items()) if params else ""
        return self._c._request("GET", f"/tokens{'?' + qs if qs else ''}")


class PoliciesAPI:
    def __init__(self, client: Mandatum):
        self._c = client

    def create(self, name: str, resource: str, actions: list, constraints: dict = None) -> dict:
        """Create a policy."""
        return self._c._request("POST", "/policies", {
            "name": name, "resource": resource, "actions": actions,
            "constraints": constraints or {},
        })

    def list(self) -> list:
        return self._c._request("GET", "/policies")

    def assign(self, agent_id: str, policy_id: str) -> dict:
        """Assign a policy to an agent."""
        return self._c._request("POST", "/policies/assign", {
            "agentId": agent_id, "policyId": policy_id,
        })


class AuditAPI:
    def __init__(self, client: Mandatum):
        self._c = client

    def list(self, **params) -> dict:
        qs = "&".join(f"{k}={v}" for k, v in params.items()) if params else ""
        return self._c._request("GET", f"/audit{'?' + qs if qs else ''}")

    def verify_chain(self) -> dict:
        """Verify audit trail hash-chain integrity."""
        return self._c._request("GET", "/audit/verify-chain")
