import Foundation

/// Synor IBC SDK Types for Swift
///
/// Inter-Blockchain Communication (IBC) protocol types.

/// IBC Height representation
public struct Height: Codable, Equatable {
    public let revisionNumber: UInt64
    public let revisionHeight: UInt64

    public init(revisionNumber: UInt64 = 0, revisionHeight: UInt64 = 1) {
        self.revisionNumber = revisionNumber
        self.revisionHeight = revisionHeight
    }

    public var isZero: Bool {
        revisionNumber == 0 && revisionHeight == 0
    }

    public func increment() -> Height {
        Height(revisionNumber: revisionNumber, revisionHeight: revisionHeight + 1)
    }

    enum CodingKeys: String, CodingKey {
        case revisionNumber = "revision_number"
        case revisionHeight = "revision_height"
    }
}

/// Chain identifier
public struct ChainId: Codable, Equatable {
    public let id: String

    public init(_ id: String) {
        self.id = id
    }
}

/// IBC Version with features
public struct Version: Codable, Equatable {
    public let identifier: String
    public let features: [String]

    public init(identifier: String, features: [String]) {
        self.identifier = identifier
        self.features = features
    }

    public static func defaultConnection() -> Version {
        Version(identifier: "1", features: ["ORDER_ORDERED", "ORDER_UNORDERED"])
    }
}

/// Light client types
public enum ClientType: String, Codable {
    case tendermint
    case soloMachine = "solo_machine"
    case localhost
    case wasm
}

/// Light client identifier
public struct ClientId: Codable, Equatable {
    public let id: String

    public init(_ id: String) {
        self.id = id
    }
}

/// Trust level configuration
public struct TrustLevel: Codable, Equatable {
    public let numerator: Int
    public let denominator: Int

    public init(numerator: Int = 1, denominator: Int = 3) {
        self.numerator = numerator
        self.denominator = denominator
    }
}

/// Light client state
public struct ClientState: Codable {
    public let chainId: String
    public let trustLevel: TrustLevel
    public let trustingPeriod: UInt64
    public let unbondingPeriod: UInt64
    public let maxClockDrift: UInt64
    public let latestHeight: Height
    public let frozenHeight: Height?

    public init(
        chainId: String,
        trustLevel: TrustLevel,
        trustingPeriod: UInt64,
        unbondingPeriod: UInt64,
        maxClockDrift: UInt64,
        latestHeight: Height,
        frozenHeight: Height? = nil
    ) {
        self.chainId = chainId
        self.trustLevel = trustLevel
        self.trustingPeriod = trustingPeriod
        self.unbondingPeriod = unbondingPeriod
        self.maxClockDrift = maxClockDrift
        self.latestHeight = latestHeight
        self.frozenHeight = frozenHeight
    }

    enum CodingKeys: String, CodingKey {
        case chainId = "chain_id"
        case trustLevel = "trust_level"
        case trustingPeriod = "trusting_period"
        case unbondingPeriod = "unbonding_period"
        case maxClockDrift = "max_clock_drift"
        case latestHeight = "latest_height"
        case frozenHeight = "frozen_height"
    }
}

/// Connection state
public enum ConnectionState: String, Codable {
    case uninitialized
    case `init`
    case tryopen
    case open
}

/// Connection identifier
public struct ConnectionId: Codable, Equatable {
    public let id: String

    public init(_ id: String) {
        self.id = id
    }

    public static func newId(_ sequence: Int) -> ConnectionId {
        ConnectionId("connection-\(sequence)")
    }
}

/// Port identifier
public struct PortId: Codable, Equatable {
    public let id: String

    public init(_ id: String) {
        self.id = id
    }

    public static func transfer() -> PortId {
        PortId("transfer")
    }
}

/// Channel identifier
public struct ChannelId: Codable, Equatable {
    public let id: String

    public init(_ id: String) {
        self.id = id
    }

    public static func newId(_ sequence: Int) -> ChannelId {
        ChannelId("channel-\(sequence)")
    }
}

/// Channel ordering
public enum ChannelOrder: String, Codable {
    case unordered
    case ordered
}

/// Channel state
public enum ChannelState: String, Codable {
    case uninitialized
    case `init`
    case tryopen
    case open
    case closed
}

/// Timeout information
public struct Timeout {
    public let height: Height
    public let timestamp: UInt64

    public init(height: Height, timestamp: UInt64 = 0) {
        self.height = height
        self.timestamp = timestamp
    }

    public static func fromHeight(_ height: UInt64) -> Timeout {
        Timeout(height: Height(revisionHeight: height))
    }

    public static func fromTimestamp(_ timestamp: UInt64) -> Timeout {
        Timeout(height: Height(), timestamp: timestamp)
    }

    public func toJson() -> [String: Any] {
        [
            "height": [
                "revision_number": height.revisionNumber,
                "revision_height": height.revisionHeight
            ],
            "timestamp": String(timestamp)
        ]
    }
}

/// Transfer packet data (ICS-20)
public struct FungibleTokenPacketData: Codable {
    public let denom: String
    public let amount: String
    public let sender: String
    public let receiver: String
    public let memo: String

    public init(denom: String, amount: String, sender: String, receiver: String, memo: String = "") {
        self.denom = denom
        self.amount = amount
        self.sender = sender
        self.receiver = receiver
        self.memo = memo
    }

    public var isNative: Bool {
        !denom.contains("/")
    }
}

/// Swap state
public enum SwapState: String, Codable {
    case pending
    case locked
    case completed
    case refunded
    case expired
    case cancelled
}

/// Swap identifier
public struct SwapId: Codable, Equatable {
    public let id: String

    public init(_ id: String) {
        self.id = id
    }
}

/// Native asset for swaps
public struct NativeAsset {
    public let amount: UInt64

    public init(amount: UInt64) {
        self.amount = amount
    }

    public func toJson() -> [String: Any] {
        ["native": ["amount": String(amount)]]
    }
}

/// ICS-20 asset for swaps
public struct Ics20Asset {
    public let denom: String
    public let amount: UInt64

    public init(denom: String, amount: UInt64) {
        self.denom = denom
        self.amount = amount
    }

    public func toJson() -> [String: Any] {
        ["ics20": ["denom": denom, "amount": String(amount)]]
    }
}

/// Hashlock - hash of the secret
public struct Hashlock {
    public let hash: Data

    public init(hash: Data) {
        self.hash = hash
    }
}

/// Timelock - expiration time
public struct Timelock {
    public let expiry: UInt64

    public init(expiry: UInt64) {
        self.expiry = expiry
    }

    public func isExpired(current: UInt64) -> Bool {
        current >= expiry
    }
}

/// Atomic swap
public struct AtomicSwap {
    public let swapId: SwapId
    public let state: SwapState
    public let initiatorHtlc: [String: Any]
    public let responderHtlc: [String: Any]?

    public init(
        swapId: SwapId,
        state: SwapState,
        initiatorHtlc: [String: Any],
        responderHtlc: [String: Any]? = nil
    ) {
        self.swapId = swapId
        self.state = state
        self.initiatorHtlc = initiatorHtlc
        self.responderHtlc = responderHtlc
    }

    public static func fromJson(_ json: [String: Any]) -> AtomicSwap? {
        guard let swapIdDict = json["swap_id"] as? [String: Any],
              let swapIdStr = swapIdDict["id"] as? String,
              let stateStr = json["state"] as? String,
              let state = SwapState(rawValue: stateStr),
              let initiatorHtlc = json["initiator_htlc"] as? [String: Any] else {
            return nil
        }
        return AtomicSwap(
            swapId: SwapId(swapIdStr),
            state: state,
            initiatorHtlc: initiatorHtlc,
            responderHtlc: json["responder_htlc"] as? [String: Any]
        )
    }
}

/// IBC SDK configuration
public struct IbcConfig {
    public let apiKey: String
    public let endpoint: String
    public let wsEndpoint: String
    public let timeout: Int
    public let retries: Int
    public let chainId: String
    public let debug: Bool

    public init(
        apiKey: String,
        endpoint: String = "https://ibc.synor.io/v1",
        wsEndpoint: String = "wss://ibc.synor.io/v1/ws",
        timeout: Int = 30,
        retries: Int = 3,
        chainId: String = "synor-1",
        debug: Bool = false
    ) {
        self.apiKey = apiKey
        self.endpoint = endpoint
        self.wsEndpoint = wsEndpoint
        self.timeout = timeout
        self.retries = retries
        self.chainId = chainId
        self.debug = debug
    }
}

/// IBC exception
public struct IbcError: Error, CustomStringConvertible {
    public let message: String
    public let code: String?
    public let status: Int?

    public init(_ message: String, code: String? = nil, status: Int? = nil) {
        self.message = message
        self.code = code
        self.status = status
    }

    public var description: String {
        "IbcError: \(message)\(code.map { " (\($0))" } ?? "")"
    }
}
