"""DID-based agent identity for Skytale channels.

Provides DID generation (``did:key`` from Ed25519 public keys) and
resolution. Agents use DIDs as persistent identities that bind to
MLS credentials in encrypted channels.

Two DID methods are supported:

- **did:key** --- Self-contained, no external resolution needed.
  ``did:key:z6Mk...`` = Multibase-encoded Ed25519 public key.
- **did:web** --- Web-hosted at ``https://domain/.well-known/did.json``.
  Good for enterprise agents with persistent identity.

Usage::

    from skytale_sdk.identity import AgentIdentity

    identity = AgentIdentity.generate()
    print(identity.did)        # did:key:z6Mk...
    print(identity.public_key) # bytes

    # Or from an existing Ed25519 private key
    identity = AgentIdentity.from_private_key(private_key_bytes)
"""

from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass
from typing import Optional


# Multicodec prefix for Ed25519 public key (0xed01)
_ED25519_MULTICODEC = b"\xed\x01"


@dataclass(frozen=True)
class AgentIdentity:
    """A DID-based agent identity backed by an Ed25519 key pair.

    Attributes:
        did: The DID URI (e.g. ``did:key:z6Mk...``).
        public_key: Ed25519 public key bytes (32 bytes).
        private_key: Ed25519 private key bytes (32 bytes, if available).
    """

    did: str
    public_key: bytes
    private_key: Optional[bytes] = None

    @classmethod
    def generate(cls) -> AgentIdentity:
        """Generate a new Ed25519 key pair and derive a ``did:key`` DID.

        Returns:
            New :class:`AgentIdentity` with a fresh key pair.

        Example::

            identity = AgentIdentity.generate()
            print(identity.did)  # did:key:z6Mk...
        """
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

        private = Ed25519PrivateKey.generate()
        private_bytes = private.private_bytes_raw()
        public_bytes = private.public_key().public_bytes_raw()

        did = _encode_did_key(public_bytes)
        return cls(did=did, public_key=public_bytes, private_key=private_bytes)

    @classmethod
    def from_private_key(cls, private_key: bytes) -> AgentIdentity:
        """Create an identity from an existing Ed25519 private key.

        Args:
            private_key: 32-byte Ed25519 private key.

        Returns:
            :class:`AgentIdentity` with the derived public key and DID.
        """
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

        private = Ed25519PrivateKey.from_private_bytes(private_key)
        public_bytes = private.public_key().public_bytes_raw()
        did = _encode_did_key(public_bytes)
        return cls(did=did, public_key=public_bytes, private_key=private_key)

    @classmethod
    def from_did(cls, did: str) -> AgentIdentity:
        """Parse a ``did:key`` DID and extract the public key.

        Args:
            did: A ``did:key:z6Mk...`` URI.

        Returns:
            :class:`AgentIdentity` with only the public key (no private key).

        Raises:
            ValueError: If the DID is not a valid ``did:key``.
        """
        public_bytes = _decode_did_key(did)
        return cls(did=did, public_key=public_bytes, private_key=None)

    def sign(self, message: bytes) -> bytes:
        """Sign a message with the private key.

        Args:
            message: Bytes to sign.

        Returns:
            64-byte Ed25519 signature.

        Raises:
            ValueError: If no private key is available.
        """
        if self.private_key is None:
            raise ValueError("Cannot sign: no private key available")

        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

        private = Ed25519PrivateKey.from_private_bytes(self.private_key)
        return private.sign(message)

    def verify(self, message: bytes, signature: bytes) -> bool:
        """Verify a signature against this identity's public key.

        Args:
            message: Original message bytes.
            signature: 64-byte Ed25519 signature to verify.

        Returns:
            ``True`` if the signature is valid.
        """
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

        public = Ed25519PublicKey.from_public_bytes(self.public_key)
        try:
            public.verify(signature, message)
            return True
        except Exception:
            return False

    def to_did_document(self) -> dict:
        """Generate a DID Document for this identity.

        Returns:
            DID Document dict (JSON-LD compatible).
        """
        import base64

        pub_b64url = base64.urlsafe_b64encode(self.public_key).rstrip(b"=").decode()

        return {
            "@context": [
                "https://www.w3.org/ns/did/v1",
                "https://w3id.org/security/suites/ed25519-2020/v1",
            ],
            "id": self.did,
            "verificationMethod": [
                {
                    "id": f"{self.did}#key-1",
                    "type": "Ed25519VerificationKey2020",
                    "controller": self.did,
                    "publicKeyMultibase": _multibase_encode(self.public_key),
                }
            ],
            "authentication": [f"{self.did}#key-1"],
            "assertionMethod": [f"{self.did}#key-1"],
        }


def _encode_did_key(public_key: bytes) -> str:
    """Encode an Ed25519 public key as a did:key DID."""
    multicodec = _ED25519_MULTICODEC + public_key
    return f"did:key:{_multibase_encode_raw(multicodec)}"


def _decode_did_key(did: str) -> bytes:
    """Decode a did:key DID to extract the Ed25519 public key."""
    if not did.startswith("did:key:z"):
        raise ValueError(f"Not a valid did:key: {did}")

    multibase_value = did[len("did:key:"):]
    decoded = _multibase_decode(multibase_value)

    if not decoded.startswith(_ED25519_MULTICODEC):
        raise ValueError("DID key is not Ed25519")

    return decoded[len(_ED25519_MULTICODEC):]


def _multibase_encode(public_key: bytes) -> str:
    """Multibase-encode a public key (base58btc with z prefix)."""
    return _multibase_encode_raw(_ED25519_MULTICODEC + public_key)


def _multibase_encode_raw(data: bytes) -> str:
    """Encode bytes as multibase base58btc (z prefix)."""
    import base58

    return "z" + base58.b58encode(data).decode()


def _multibase_decode(value: str) -> bytes:
    """Decode a multibase-encoded string."""
    if not value.startswith("z"):
        raise ValueError(f"Unsupported multibase encoding: {value[0]}")

    import base58

    return base58.b58decode(value[1:])


def resolve_did_web(did: str) -> dict:
    """Resolve a ``did:web`` DID by fetching the DID Document.

    Args:
        did: A ``did:web:domain`` URI.

    Returns:
        DID Document dict fetched from ``https://domain/.well-known/did.json``.

    Raises:
        ValueError: If the DID is not a valid ``did:web``.
    """
    if not did.startswith("did:web:"):
        raise ValueError(f"Not a valid did:web: {did}")

    domain = did[len("did:web:"):]
    # did:web:example.com:path -> https://example.com/path/did.json
    parts = domain.split(":")
    host = parts[0]
    path = "/".join(parts[1:]) if len(parts) > 1 else ".well-known"
    url = f"https://{host}/{path}/did.json"

    import urllib.request

    with urllib.request.urlopen(url) as resp:
        return json.loads(resp.read())
