"""
Synor IBC SDK Types

Type definitions for Inter-Blockchain Communication (IBC) protocol.
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List, Dict, Any, Union


# ============================================================================
# Core Types
# ============================================================================

@dataclass
class Height:
    """IBC Height representation"""
    revision_number: int = 0
    revision_height: int = 1

    def is_zero(self) -> bool:
        return self.revision_number == 0 and self.revision_height == 0

    def increment(self) -> "Height":
        return Height(self.revision_number, self.revision_height + 1)


Timestamp = int  # Nanoseconds since Unix epoch


@dataclass
class ChainId:
    """Chain identifier"""
    id: str


@dataclass
class Signer:
    """Signer address"""
    address: str


@dataclass
class Version:
    """IBC version with features"""
    identifier: str
    features: List[str]

    @classmethod
    def default_connection(cls) -> "Version":
        return cls(identifier="1", features=["ORDER_ORDERED", "ORDER_UNORDERED"])


@dataclass
class CommitmentPrefix:
    """Commitment prefix for Merkle paths"""
    key_prefix: bytes = field(default_factory=lambda: b"ibc")


# ============================================================================
# Client Types
# ============================================================================

@dataclass
class ClientId:
    """Light client identifier"""
    id: str


class ClientType(str, Enum):
    """Light client types"""
    TENDERMINT = "tendermint"
    SOLO_MACHINE = "solo-machine"
    LOCALHOST = "localhost"
    WASM = "wasm"


@dataclass
class TrustLevel:
    """Trust level configuration"""
    numerator: int = 1
    denominator: int = 3


@dataclass
class ClientState:
    """Light client state"""
    chain_id: str
    trust_level: TrustLevel
    trusting_period: int  # nanoseconds
    unbonding_period: int  # nanoseconds
    max_clock_drift: int  # nanoseconds
    latest_height: Height
    frozen_height: Optional[Height] = None


@dataclass
class ConsensusState:
    """Consensus state at a specific height"""
    timestamp: Timestamp
    root: bytes
    next_validators_hash: bytes


@dataclass
class Validator:
    """Validator info"""
    address: bytes
    pub_key: Dict[str, Any]
    voting_power: int
    proposer_priority: int = 0


@dataclass
class ValidatorSet:
    """Validator set"""
    validators: List[Validator]
    proposer: Optional[Validator] = None
    total_voting_power: int = 0


@dataclass
class Header:
    """Block header for light client updates"""
    signed_header: Dict[str, Any]
    validator_set: ValidatorSet
    trusted_height: Height
    trusted_validators: ValidatorSet


# ============================================================================
# Connection Types
# ============================================================================

@dataclass
class ConnectionId:
    """Connection identifier"""
    id: str


class ConnectionState(str, Enum):
    """Connection state"""
    UNINITIALIZED = "uninitialized"
    INIT = "init"
    TRYOPEN = "tryopen"
    OPEN = "open"


@dataclass
class ConnectionCounterparty:
    """Connection counterparty"""
    client_id: ClientId
    connection_id: Optional[ConnectionId] = None
    prefix: CommitmentPrefix = field(default_factory=CommitmentPrefix)


@dataclass
class ConnectionEnd:
    """Connection end"""
    client_id: ClientId
    versions: List[Version]
    state: ConnectionState
    counterparty: ConnectionCounterparty
    delay_period: int = 0


# ============================================================================
# Channel Types
# ============================================================================

@dataclass
class PortId:
    """Port identifier"""
    id: str

    @classmethod
    def transfer(cls) -> "PortId":
        return cls(id="transfer")

    @classmethod
    def ica_host(cls) -> "PortId":
        return cls(id="icahost")


@dataclass
class ChannelId:
    """Channel identifier"""
    id: str

    @classmethod
    def new(cls, sequence: int) -> "ChannelId":
        return cls(id=f"channel-{sequence}")


class ChannelOrder(str, Enum):
    """Channel ordering"""
    UNORDERED = "unordered"
    ORDERED = "ordered"


class ChannelState(str, Enum):
    """Channel state"""
    UNINITIALIZED = "uninitialized"
    INIT = "init"
    TRYOPEN = "tryopen"
    OPEN = "open"
    CLOSED = "closed"


@dataclass
class ChannelCounterparty:
    """Channel counterparty"""
    port_id: PortId
    channel_id: Optional[ChannelId] = None


@dataclass
class Channel:
    """Channel end"""
    state: ChannelState
    ordering: ChannelOrder
    counterparty: ChannelCounterparty
    connection_hops: List[ConnectionId]
    version: str


# ============================================================================
# Packet Types
# ============================================================================

@dataclass
class Packet:
    """IBC Packet"""
    sequence: int
    source_port: PortId
    source_channel: ChannelId
    dest_port: PortId
    dest_channel: ChannelId
    data: bytes
    timeout_height: Height
    timeout_timestamp: Timestamp = 0


@dataclass
class PacketCommitment:
    """Packet commitment hash"""
    hash: bytes


@dataclass
class AckSuccess:
    """Success acknowledgement"""
    data: bytes


@dataclass
class AckError:
    """Error acknowledgement"""
    message: str


Acknowledgement = Union[AckSuccess, AckError]


@dataclass
class Timeout:
    """Timeout information"""
    height: Height = field(default_factory=Height)
    timestamp: Timestamp = 0

    @classmethod
    def from_height(cls, height: int) -> "Timeout":
        return cls(height=Height(0, height))

    @classmethod
    def from_timestamp(cls, timestamp: int) -> "Timeout":
        return cls(timestamp=timestamp)


@dataclass
class PacketReceipt:
    """Packet receipt"""
    received_at: Height
    acknowledgement: Optional[Acknowledgement] = None


@dataclass
class FungibleTokenPacketData:
    """Transfer packet data (ICS-20)"""
    denom: str
    amount: str
    sender: str
    receiver: str
    memo: str = ""

    def is_native(self) -> bool:
        return "/" not in self.denom

    def get_denom_trace(self) -> List[str]:
        return self.denom.split("/")


# ============================================================================
# Swap Types (HTLC)
# ============================================================================

@dataclass
class SwapId:
    """Swap identifier"""
    id: str


class SwapState(str, Enum):
    """Swap state"""
    PENDING = "pending"
    LOCKED = "locked"
    COMPLETED = "completed"
    REFUNDED = "refunded"
    EXPIRED = "expired"
    CANCELLED = "cancelled"


@dataclass
class NativeAsset:
    """Native blockchain token"""
    amount: int


@dataclass
class Ics20Asset:
    """ICS-20 fungible token"""
    denom: str
    amount: int


@dataclass
class Ics721Asset:
    """ICS-721 NFT"""
    class_id: str
    token_ids: List[str]


SwapAsset = Union[NativeAsset, Ics20Asset, Ics721Asset]


@dataclass
class Hashlock:
    """Hashlock - hash of the secret"""
    hash: bytes

    @classmethod
    def from_secret(cls, secret: bytes) -> "Hashlock":
        import hashlib
        return cls(hash=hashlib.sha256(secret).digest())

    def verify(self, preimage: bytes) -> bool:
        import hashlib
        computed = hashlib.sha256(preimage).digest()
        return self.hash == computed


@dataclass
class Timelock:
    """Timelock - expiration time"""
    expiry: Timestamp

    @classmethod
    def from_duration(cls, current: Timestamp, duration_secs: int) -> "Timelock":
        return cls(expiry=current + duration_secs * 1_000_000_000)

    def is_expired(self, current: Timestamp) -> bool:
        return current >= self.expiry

    def remaining_secs(self, current: Timestamp) -> int:
        if current >= self.expiry:
            return 0
        return (self.expiry - current) // 1_000_000_000


@dataclass
class Htlc:
    """Hashed Time-Locked Contract"""
    swap_id: SwapId
    state: SwapState
    initiator: str
    responder: str
    asset: SwapAsset
    hashlock: Hashlock
    timelock: Timelock
    secret: Optional[bytes] = None
    channel_id: Optional[ChannelId] = None
    port_id: Optional[PortId] = None
    created_at: Timestamp = 0
    completed_at: Optional[Timestamp] = None


@dataclass
class AtomicSwap:
    """Atomic swap between two chains"""
    swap_id: SwapId
    initiator_htlc: Htlc
    responder_htlc: Optional[Htlc] = None
    state: SwapState = SwapState.PENDING


class SwapAction(str, Enum):
    """Swap packet actions"""
    INITIATE = "initiate"
    RESPOND = "respond"
    CLAIM = "claim"
    REFUND = "refund"


@dataclass
class SwapPacketData:
    """Swap packet data"""
    swap_id: SwapId
    action: SwapAction
    initiator: str
    responder: str
    asset: SwapAsset
    hashlock: Hashlock
    timelock_expiry: int
    secret: Optional[bytes] = None


# ============================================================================
# Proof Types
# ============================================================================

@dataclass
class ProofOp:
    """Proof operation"""
    type: str
    key: bytes
    data: bytes


@dataclass
class MerkleProof:
    """Merkle proof"""
    proofs: List[ProofOp]


@dataclass
class CommitmentProof:
    """Commitment proof"""
    proof: MerkleProof
    height: Height


# ============================================================================
# Event Types
# ============================================================================

@dataclass
class CreateClientEvent:
    type: str = "createClient"
    client_id: Optional[ClientId] = None
    client_type: Optional[ClientType] = None
    consensus_height: Optional[Height] = None


@dataclass
class UpdateClientEvent:
    type: str = "updateClient"
    client_id: Optional[ClientId] = None
    consensus_height: Optional[Height] = None


@dataclass
class OpenInitConnectionEvent:
    type: str = "openInitConnection"
    connection_id: Optional[ConnectionId] = None
    client_id: Optional[ClientId] = None
    counterparty_client_id: Optional[ClientId] = None


@dataclass
class SendPacketEvent:
    type: str = "sendPacket"
    packet: Optional[Packet] = None


@dataclass
class SwapInitiatedEvent:
    type: str = "swapInitiated"
    swap_id: Optional[SwapId] = None
    initiator: Optional[str] = None
    responder: Optional[str] = None


@dataclass
class SwapCompletedEvent:
    type: str = "swapCompleted"
    swap_id: Optional[SwapId] = None


IbcEvent = Union[
    CreateClientEvent,
    UpdateClientEvent,
    OpenInitConnectionEvent,
    SendPacketEvent,
    SwapInitiatedEvent,
    SwapCompletedEvent,
    Dict[str, Any],  # Fallback for unknown events
]


# ============================================================================
# Configuration
# ============================================================================

@dataclass
class IbcConfig:
    """IBC SDK configuration"""
    api_key: str
    endpoint: str = "https://ibc.synor.io/v1"
    ws_endpoint: str = "wss://ibc.synor.io/v1/ws"
    timeout: int = 30
    retries: int = 3
    chain_id: str = "synor-1"
    debug: bool = False


class IbcException(Exception):
    """IBC exception"""
    def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None):
        super().__init__(message)
        self.code = code
        self.status = status
