"""
Synor Crypto SDK Client for Python

Quantum-resistant cryptographic primitives for the Synor blockchain.
"""

import base64
from typing import Optional, List, Dict, Any, Union
import httpx

from .types import (
    CryptoConfig, Network, Mnemonic, MnemonicValidation,
    HybridKeypair, HybridPublicKey, HybridSignature, SecretKey,
    FalconVariant, FalconKeypair, FalconSignature,
    SphincsVariant, SphincsKeypair, SphincsSignature,
    DerivationPath, DerivationConfig, PasswordDerivationConfig,
    AlgorithmCapabilities, NegotiationPolicy, NegotiationResult, SessionParams,
    Hash256, Address, CryptoError
)


class MnemonicClient:
    """Mnemonic operations."""

    def __init__(self, crypto: 'SynorCrypto'):
        self._crypto = crypto

    async def generate(self, word_count: int = 24) -> Mnemonic:
        """Generates a new random mnemonic."""
        result = await self._crypto._post('/mnemonic/generate', {'word_count': word_count})
        return Mnemonic.from_dict(result)

    async def from_phrase(self, phrase: str) -> Mnemonic:
        """Creates a mnemonic from a phrase string."""
        result = await self._crypto._post('/mnemonic/from-phrase', {'phrase': phrase})
        return Mnemonic.from_dict(result)

    async def from_entropy(self, entropy: bytes) -> Mnemonic:
        """Creates a mnemonic from entropy bytes."""
        result = await self._crypto._post('/mnemonic/from-entropy', {
            'entropy': base64.b64encode(entropy).decode()
        })
        return Mnemonic.from_dict(result)

    async def validate(self, phrase: str) -> MnemonicValidation:
        """Validates a mnemonic phrase."""
        result = await self._crypto._post('/mnemonic/validate', {'phrase': phrase})
        return MnemonicValidation.from_dict(result)

    async def to_seed(self, mnemonic: Union[Mnemonic, str], passphrase: str = '') -> bytes:
        """Derives a 64-byte seed from a mnemonic."""
        phrase = mnemonic.phrase if isinstance(mnemonic, Mnemonic) else mnemonic
        result = await self._crypto._post('/mnemonic/to-seed', {
            'phrase': phrase,
            'passphrase': passphrase
        })
        return base64.b64decode(result['seed'])

    async def suggest_words(self, partial: str, limit: int = 10) -> List[str]:
        """Suggests word completions from the BIP-39 wordlist."""
        result = await self._crypto._post('/mnemonic/suggest', {
            'partial': partial,
            'limit': limit
        })
        return result['suggestions']


class KeypairClient:
    """Keypair operations."""

    def __init__(self, crypto: 'SynorCrypto'):
        self._crypto = crypto

    async def generate(self) -> HybridKeypair:
        """Generates a new random hybrid keypair."""
        result = await self._crypto._post('/keypair/generate')
        return HybridKeypair.from_dict(result)

    async def from_mnemonic(
        self,
        mnemonic: Union[Mnemonic, str],
        passphrase: str = ''
    ) -> HybridKeypair:
        """Creates a keypair from a mnemonic."""
        phrase = mnemonic.phrase if isinstance(mnemonic, Mnemonic) else mnemonic
        result = await self._crypto._post('/keypair/from-mnemonic', {
            'phrase': phrase,
            'passphrase': passphrase
        })
        return HybridKeypair.from_dict(result)

    async def from_seed(self, seed: bytes) -> HybridKeypair:
        """Creates a keypair from a 64-byte seed."""
        result = await self._crypto._post('/keypair/from-seed', {
            'seed': base64.b64encode(seed).decode()
        })
        return HybridKeypair.from_dict(result)

    async def derive(
        self,
        parent_keypair: HybridKeypair,
        path: DerivationPath
    ) -> HybridKeypair:
        """Derives a child keypair using BIP-44 path."""
        result = await self._crypto._post('/keypair/derive', {
            'public_key': parent_keypair.public_key.to_dict(),
            'path': {'account': path.account, 'change': path.change, 'index': path.index}
        })
        return HybridKeypair.from_dict(result)

    async def get_address(
        self,
        public_key: HybridPublicKey,
        network: Network
    ) -> Address:
        """Gets the address for a public key."""
        result = await self._crypto._post('/keypair/address', {
            'public_key': public_key.to_dict(),
            'network': network.value
        })
        return Address.from_dict(result)


class SigningClient:
    """Signing and verification operations."""

    def __init__(self, crypto: 'SynorCrypto'):
        self._crypto = crypto

    async def sign(self, keypair: HybridKeypair, message: bytes) -> HybridSignature:
        """Signs a message with a hybrid keypair."""
        result = await self._crypto._post('/sign/hybrid', {
            'secret_key': {
                'ed25519_seed': base64.b64encode(keypair.secret_key.ed25519_seed).decode(),
                'master_seed': base64.b64encode(keypair.secret_key.master_seed).decode()
            },
            'message': base64.b64encode(message).decode()
        })
        return HybridSignature.from_dict(result)

    async def verify(
        self,
        public_key: HybridPublicKey,
        message: bytes,
        signature: HybridSignature
    ) -> bool:
        """Verifies a hybrid signature."""
        result = await self._crypto._post('/sign/verify', {
            'public_key': public_key.to_dict(),
            'message': base64.b64encode(message).decode(),
            'signature': {
                'ed25519': base64.b64encode(signature.ed25519).decode(),
                'dilithium': base64.b64encode(signature.dilithium).decode()
            }
        })
        return result['valid']

    async def sign_ed25519(self, secret_key: bytes, message: bytes) -> bytes:
        """Signs a message using Ed25519 only."""
        result = await self._crypto._post('/sign/ed25519', {
            'secret_key': base64.b64encode(secret_key).decode(),
            'message': base64.b64encode(message).decode()
        })
        return base64.b64decode(result['signature'])

    async def verify_ed25519(
        self,
        public_key: bytes,
        message: bytes,
        signature: bytes
    ) -> bool:
        """Verifies an Ed25519 signature."""
        result = await self._crypto._post('/sign/verify-ed25519', {
            'public_key': base64.b64encode(public_key).decode(),
            'message': base64.b64encode(message).decode(),
            'signature': base64.b64encode(signature).decode()
        })
        return result['valid']


class FalconClient:
    """Falcon (FIPS 206) compact signature operations."""

    def __init__(self, crypto: 'SynorCrypto'):
        self._crypto = crypto

    async def generate(self, variant: FalconVariant = FalconVariant.FALCON512) -> FalconKeypair:
        """Generates a Falcon keypair."""
        result = await self._crypto._post('/falcon/generate', {'variant': variant.value})
        return FalconKeypair.from_dict(result)

    async def sign(self, keypair: FalconKeypair, message: bytes) -> FalconSignature:
        """Signs a message with Falcon."""
        result = await self._crypto._post('/falcon/sign', {
            'variant': keypair.variant.value,
            'secret_key': base64.b64encode(keypair.secret_key.bytes).decode(),
            'message': base64.b64encode(message).decode()
        })
        return FalconSignature.from_dict(result)

    async def verify(
        self,
        public_key: bytes,
        message: bytes,
        signature: FalconSignature
    ) -> bool:
        """Verifies a Falcon signature."""
        result = await self._crypto._post('/falcon/verify', {
            'variant': signature.variant.value,
            'public_key': base64.b64encode(public_key).decode(),
            'message': base64.b64encode(message).decode(),
            'signature': base64.b64encode(signature.bytes).decode()
        })
        return result['valid']

    def signature_size(self, variant: FalconVariant) -> int:
        """Returns the signature size for a variant."""
        return 690 if variant == FalconVariant.FALCON512 else 1330

    def public_key_size(self, variant: FalconVariant) -> int:
        """Returns the public key size for a variant."""
        return 897 if variant == FalconVariant.FALCON512 else 1793

    def security_level(self, variant: FalconVariant) -> int:
        """Returns the security level in bits."""
        return 128 if variant == FalconVariant.FALCON512 else 256


class SphincsClient:
    """SPHINCS+ (FIPS 205) hash-based signature operations."""

    def __init__(self, crypto: 'SynorCrypto'):
        self._crypto = crypto

    async def generate(self, variant: SphincsVariant = SphincsVariant.SHAKE128S) -> SphincsKeypair:
        """Generates a SPHINCS+ keypair."""
        result = await self._crypto._post('/sphincs/generate', {'variant': variant.value})
        return SphincsKeypair.from_dict(result)

    async def sign(self, keypair: SphincsKeypair, message: bytes) -> SphincsSignature:
        """Signs a message with SPHINCS+."""
        result = await self._crypto._post('/sphincs/sign', {
            'variant': keypair.variant.value,
            'secret_key': base64.b64encode(keypair.secret_key.bytes).decode(),
            'message': base64.b64encode(message).decode()
        })
        return SphincsSignature.from_dict(result)

    async def verify(
        self,
        public_key: bytes,
        message: bytes,
        signature: SphincsSignature
    ) -> bool:
        """Verifies a SPHINCS+ signature."""
        result = await self._crypto._post('/sphincs/verify', {
            'variant': signature.variant.value,
            'public_key': base64.b64encode(public_key).decode(),
            'message': base64.b64encode(message).decode(),
            'signature': base64.b64encode(signature.bytes).decode()
        })
        return result['valid']

    def signature_size(self, variant: SphincsVariant) -> int:
        """Returns the signature size for a variant."""
        sizes = {
            SphincsVariant.SHAKE128S: 7856,
            SphincsVariant.SHAKE192S: 16224,
            SphincsVariant.SHAKE256S: 29792,
        }
        return sizes[variant]

    def security_level(self, variant: SphincsVariant) -> int:
        """Returns the security level in bits."""
        levels = {
            SphincsVariant.SHAKE128S: 128,
            SphincsVariant.SHAKE192S: 192,
            SphincsVariant.SHAKE256S: 256,
        }
        return levels[variant]


class KdfClient:
    """Key derivation operations."""

    def __init__(self, crypto: 'SynorCrypto'):
        self._crypto = crypto

    async def derive_key(
        self,
        seed: bytes,
        config: Optional[DerivationConfig] = None
    ) -> bytes:
        """Derives a key using HKDF-SHA3-256."""
        cfg = config or DerivationConfig()
        body: Dict[str, Any] = {
            'seed': base64.b64encode(seed).decode(),
            'output_length': cfg.output_length
        }
        if cfg.salt:
            body['salt'] = base64.b64encode(cfg.salt).decode()
        if cfg.info:
            body['info'] = base64.b64encode(cfg.info).decode()

        result = await self._crypto._post('/kdf/hkdf', body)
        return base64.b64decode(result['key'])

    async def derive_from_password(
        self,
        password: Union[str, bytes],
        config: PasswordDerivationConfig
    ) -> bytes:
        """Derives a key from a password using PBKDF2."""
        password_bytes = password.encode() if isinstance(password, str) else password
        result = await self._crypto._post('/kdf/pbkdf2', {
            'password': base64.b64encode(password_bytes).decode(),
            'salt': base64.b64encode(config.salt).decode(),
            'iterations': config.iterations,
            'output_length': config.output_length
        })
        return base64.b64decode(result['key'])

    async def derive_ed25519_key(self, master_seed: bytes, account: int) -> bytes:
        """Derives an Ed25519 key from a master seed."""
        result = await self._crypto._post('/kdf/ed25519', {
            'master_seed': base64.b64encode(master_seed).decode(),
            'account': account
        })
        return base64.b64decode(result['key'])

    async def derive_child_key(
        self,
        parent_key: bytes,
        chain_code: bytes,
        index: int
    ) -> tuple[bytes, bytes]:
        """Derives a child key using BIP-32 style derivation."""
        result = await self._crypto._post('/kdf/child', {
            'parent_key': base64.b64encode(parent_key).decode(),
            'chain_code': base64.b64encode(chain_code).decode(),
            'index': index
        })
        return (
            base64.b64decode(result['key']),
            base64.b64decode(result['chain_code'])
        )

    def create_path(
        self,
        account: int,
        change: int = 0,
        index: int = 0
    ) -> DerivationPath:
        """Creates a BIP-44 derivation path."""
        return DerivationPath(account=account, change=change, index=index)


class HashClient:
    """Hashing operations."""

    def __init__(self, crypto: 'SynorCrypto'):
        self._crypto = crypto

    async def sha3_256(self, data: bytes) -> Hash256:
        """Computes SHA3-256 hash."""
        result = await self._crypto._post('/hash/sha3-256', {
            'data': base64.b64encode(data).decode()
        })
        return Hash256.from_dict(result)

    async def blake3(self, data: bytes) -> Hash256:
        """Computes BLAKE3 hash."""
        result = await self._crypto._post('/hash/blake3', {
            'data': base64.b64encode(data).decode()
        })
        return Hash256.from_dict(result)

    async def keccak256(self, data: bytes) -> Hash256:
        """Computes Keccak-256 hash."""
        result = await self._crypto._post('/hash/keccak256', {
            'data': base64.b64encode(data).decode()
        })
        return Hash256.from_dict(result)

    async def combine(self, hashes: List[bytes]) -> Hash256:
        """Combines multiple hashes."""
        result = await self._crypto._post('/hash/combine', {
            'hashes': [base64.b64encode(h).decode() for h in hashes]
        })
        return Hash256.from_dict(result)


class NegotiationClient:
    """Algorithm negotiation operations."""

    def __init__(self, crypto: 'SynorCrypto'):
        self._crypto = crypto

    async def get_capabilities(self) -> AlgorithmCapabilities:
        """Gets the local capabilities."""
        result = await self._crypto._get('/negotiation/capabilities')
        return AlgorithmCapabilities.from_dict(result)

    async def negotiate(
        self,
        peer_capabilities: AlgorithmCapabilities,
        policy: NegotiationPolicy
    ) -> NegotiationResult:
        """Negotiates algorithm selection with a peer."""
        result = await self._crypto._post('/negotiation/negotiate', {
            'peer_capabilities': {
                'pq_algorithms': [a.value for a in peer_capabilities.pq_algorithms],
                'classical': peer_capabilities.classical,
                'hybrid': peer_capabilities.hybrid,
                'preferred': peer_capabilities.preferred.value if peer_capabilities.preferred else None
            },
            'policy': {
                'min_security_level': policy.min_security_level,
                'prefer_compact': policy.prefer_compact,
                'allow_classical': policy.allow_classical,
                'required_families': [f.value for f in policy.required_families]
            }
        })
        return NegotiationResult.from_dict(result)

    async def establish_session(
        self,
        result: NegotiationResult,
        peer_public_key: bytes
    ) -> SessionParams:
        """Establishes session parameters after negotiation."""
        resp = await self._crypto._post('/negotiation/session', {
            'negotiation_result': {
                'algorithm': result.algorithm.value,
                'security_level': result.security_level,
                'family': result.family.value,
                'hybrid': result.hybrid
            },
            'peer_public_key': base64.b64encode(peer_public_key).decode()
        })
        return SessionParams.from_dict(resp)


class SynorCrypto:
    """
    Synor Crypto SDK Client

    Provides quantum-resistant cryptographic operations for the Synor blockchain.

    Example:
        ```python
        from synor_crypto import SynorCrypto, CryptoConfig

        async def main():
            crypto = SynorCrypto(CryptoConfig(api_key='your-api-key'))

            # Generate a mnemonic
            mnemonic = await crypto.mnemonic.generate(24)
            print(f'Backup words: {mnemonic.phrase}')

            # Create keypair from mnemonic
            keypair = await crypto.keypairs.from_mnemonic(mnemonic)
            address = keypair.address(Network.MAINNET)

            # Sign a message
            signature = await crypto.signing.sign(keypair, b'Hello!')

            await crypto.close()
        ```
    """

    def __init__(self, config: CryptoConfig):
        self._config = config
        self._client = httpx.AsyncClient(
            base_url=config.endpoint,
            timeout=config.timeout / 1000,
            headers={
                'Authorization': f'Bearer {config.api_key}',
                'Content-Type': 'application/json',
                'X-SDK-Version': 'python/0.1.0'
            }
        )
        self._closed = False

        self.mnemonic = MnemonicClient(self)
        self.keypairs = KeypairClient(self)
        self.signing = SigningClient(self)
        self.falcon = FalconClient(self)
        self.sphincs = SphincsClient(self)
        self.kdf = KdfClient(self)
        self.hash = HashClient(self)
        self.negotiation = NegotiationClient(self)

    @property
    def default_network(self) -> Network:
        """Returns the default network."""
        return self._config.default_network

    async def health_check(self) -> bool:
        """Checks if the service is healthy."""
        try:
            result = await self._get('/health')
            return result.get('status') == 'healthy'
        except Exception:
            return False

    async def get_info(self) -> Dict[str, Any]:
        """Gets service information."""
        return await self._get('/info')

    async def close(self) -> None:
        """Closes the client."""
        self._closed = True
        await self._client.aclose()

    @property
    def is_closed(self) -> bool:
        """Returns True if the client is closed."""
        return self._closed

    async def __aenter__(self) -> 'SynorCrypto':
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        await self.close()

    async def _get(self, path: str) -> Dict[str, Any]:
        """Makes a GET request."""
        self._check_closed()
        response = await self._client.get(path)
        return self._handle_response(response)

    async def _post(self, path: str, body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """Makes a POST request."""
        self._check_closed()
        response = await self._client.post(path, json=body or {})
        return self._handle_response(response)

    def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
        """Handles the HTTP response."""
        data = response.json()
        if not response.is_success:
            raise CryptoError(
                data.get('message', f'HTTP {response.status_code}'),
                data.get('code')
            )
        return data

    def _check_closed(self) -> None:
        """Checks if the client is closed."""
        if self._closed:
            raise CryptoError('Client has been closed', 'CLIENT_CLOSED')
