from __future__ import annotations

import hashlib
import json
import os
from typing import Any, Optional, Dict

import httpx

from .client import AttestResult, _get_api_url


class AsyncTreeship:
    """
    Async Treeship client for creating cryptographic attestations.
    
    Example:
        >>> from treeship import AsyncTreeship
        >>> async with AsyncTreeship() as ts:
        ...     result = await ts.attest(
        ...         action="Approved loan application #12345",
        ...         inputs_hash=ts.hash({"customer_id": "cust_123"})
        ...     )
        ...     print(result.url)
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        agent: Optional[str] = None,
        api_url: Optional[str] = None,
    ):
        """
        Initialize the async Treeship client.
        
        Args:
            api_key: API key for authentication. Defaults to TREESHIP_API_KEY env var.
            agent: Default agent slug. Defaults to TREESHIP_AGENT env var.
            api_url: API base URL. Defaults to TREESHIP_API_URL env var or https://api.treeship.dev.
        """
        self.api_key = api_key or os.environ.get("TREESHIP_API_KEY", "")
        self.agent = agent or os.getenv("TREESHIP_AGENT", "")
        self._api_url = api_url or _get_api_url()
        self._client = httpx.AsyncClient(
            base_url=self._api_url,
            headers={
                "Authorization": f"Bearer {self.api_key}",
                "User-Agent": "treeship-sdk/0.1.0",
            },
            timeout=30.0,
        )

    @staticmethod
    def hash(data: Any) -> str:
        """
        Create a SHA256 hash of data for use as inputs_hash.
        
        Args:
            data: Any JSON-serializable data, string, or bytes.
            
        Returns:
            Hex-encoded SHA256 hash.
        """
        if isinstance(data, (dict, list)):
            data = json.dumps(data, sort_keys=True, separators=(",", ":"))
        if isinstance(data, str):
            data = data.encode()
        return hashlib.sha256(data).hexdigest()

    async def attest(
        self,
        action: str,
        inputs_hash: Optional[str] = None,
        agent: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        is_public: Optional[bool] = None,
        enable_zk: Optional[bool] = None,
    ) -> AttestResult:
        """
        Create a cryptographic attestation.
        
        Args:
            action: Human-readable description of the action.
            inputs_hash: SHA256 hash of inputs. If not provided, hashes the action string.
            agent: Agent slug. Uses default agent if not provided.
            metadata: Optional key-value metadata.
            is_public: Override org default visibility. True=public, False=private.
            enable_zk: Enable ZK proof for privacy-preserving public verification.
            
        Returns:
            AttestResult with attestation details and verification URL.
        """
        slug = agent or self.agent
        if not slug:
            raise ValueError(
                "Agent slug required: pass agent= or set TREESHIP_AGENT environment variable"
            )

        if not self.api_key:
            raise ValueError(
                "API key required: pass api_key= or set TREESHIP_API_KEY environment variable"
            )

        payload: Dict[str, Any] = {
            "agent_slug": slug,
            "action": action,
            "inputs_hash": inputs_hash or self.hash(action),
            "metadata": metadata or {},
        }
        
        if is_public is not None:
            payload["is_public"] = is_public
        if enable_zk is not None:
            payload["enable_zk"] = enable_zk

        response = await self._client.post("/v1/attest", json=payload)
        response.raise_for_status()
        return AttestResult.from_dict(response.json())

    async def verify(self, attestation_id: str) -> Dict[str, Any]:
        """
        Verify an attestation by ID.
        
        Args:
            attestation_id: The attestation UUID.
            
        Returns:
            Dict with verification result including 'valid' boolean.
        """
        response = await self._client.get(f"/v1/verify/{attestation_id}")
        response.raise_for_status()
        return response.json()

    async def get_agent(self, slug: Optional[str] = None) -> Dict[str, Any]:
        """
        Get agent feed with recent attestations.
        
        Args:
            slug: Agent slug. Uses default agent if not provided.
            
        Returns:
            Dict with agent info and attestations list.
        """
        agent_slug = slug or self.agent
        if not agent_slug:
            raise ValueError("Agent slug required")
        response = await self._client.get(f"/v1/agent/{agent_slug}")
        response.raise_for_status()
        return response.json()

    async def get_pubkey(self) -> Dict[str, Any]:
        """
        Get the public signing key for independent verification.
        
        Returns:
            Dict with key_id, algorithm, and public_key_pem.
        """
        response = await self._client.get("/v1/pubkey")
        response.raise_for_status()
        return response.json()

    async def update_settings(
        self,
        default_public: Optional[bool] = None,
        default_zk_enabled: Optional[bool] = None,
        org_name: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Update organization-level settings for your API key.
        
        Args:
            default_public: If True, new attestations are public by default.
                           If False, new attestations are private (owner-only) by default.
            default_zk_enabled: If True, enable ZK proofs by default.
            org_name: Organization name for display.
            
        Returns:
            Dict with updated settings.
        """
        payload: Dict[str, Any] = {}
        if default_public is not None:
            payload["default_public"] = default_public
        if default_zk_enabled is not None:
            payload["default_zk_enabled"] = default_zk_enabled
        if org_name is not None:
            payload["org_name"] = org_name
            
        response = await self._client.patch(
            "/v1/keys/settings",
            params={"api_key": self.api_key},
            json=payload,
        )
        response.raise_for_status()
        return response.json()

    async def close(self) -> None:
        """Close the HTTP client."""
        await self._client.aclose()

    async def __aenter__(self) -> AsyncTreeship:
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()
