import Foundation

// MARK: - Enums

/// Network environment.
public enum RpcNetwork: String, Codable {
    case mainnet
    case testnet
    case devnet
}

/// Transaction priority for fee estimation.
public enum RpcPriority: String, Codable {
    case low
    case medium
    case high
}

/// Transaction status.
public enum TransactionStatus: String, Codable {
    case pending
    case confirmed
    case failed
}

// MARK: - Configuration

/// Configuration for the RPC client.
public struct RpcConfig {
    public let apiKey: String
    public let endpoint: String
    public let wsEndpoint: String
    public let network: RpcNetwork
    public let timeout: TimeInterval
    public let retries: Int
    public let debug: Bool

    public init(
        apiKey: String,
        endpoint: String = "https://rpc.synor.io/v1",
        wsEndpoint: String = "wss://rpc.synor.io/v1/ws",
        network: RpcNetwork = .mainnet,
        timeout: TimeInterval = 30.0,
        retries: Int = 3,
        debug: Bool = false
    ) {
        self.apiKey = apiKey
        self.endpoint = endpoint
        self.wsEndpoint = wsEndpoint
        self.network = network
        self.timeout = timeout
        self.retries = retries
        self.debug = debug
    }
}

// MARK: - Block Models

/// Block header information.
public struct BlockHeader: Codable {
    public let hash: String
    public let height: Int64
    public let previousHash: String
    public let timestamp: Int64
    public let version: Int
    public let merkleRoot: String
    public let nonce: Int64
    public let difficulty: Double

    enum CodingKeys: String, CodingKey {
        case hash, height, timestamp, version, nonce, difficulty
        case previousHash = "previous_hash"
        case merkleRoot = "merkle_root"
    }
}

/// A blockchain block.
public struct Block: Codable {
    public let hash: String
    public let height: Int64
    public let previousHash: String
    public let timestamp: Int64
    public let version: Int
    public let merkleRoot: String
    public let nonce: Int64
    public let difficulty: Double
    public let transactions: [RpcTransaction]
    public let size: Int
    public let weight: Int

    enum CodingKeys: String, CodingKey {
        case hash, height, timestamp, version, nonce, difficulty
        case transactions, size, weight
        case previousHash = "previous_hash"
        case merkleRoot = "merkle_root"
    }
}

// MARK: - Transaction Models

/// A blockchain transaction.
public struct RpcTransaction: Codable {
    public let txid: String
    public let hash: String
    public let version: Int
    public let size: Int
    public let weight: Int
    public let lockTime: Int64
    public let inputs: [RpcTransactionInput]
    public let outputs: [RpcTransactionOutput]
    public let fee: Int64
    public let confirmations: Int
    public let blockHash: String?
    public let blockHeight: Int64?
    public let timestamp: Int64?
    public let status: TransactionStatus

    enum CodingKeys: String, CodingKey {
        case txid, hash, version, size, weight, inputs, outputs, fee, confirmations, timestamp, status
        case lockTime = "lock_time"
        case blockHash = "block_hash"
        case blockHeight = "block_height"
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        txid = try container.decode(String.self, forKey: .txid)
        hash = try container.decode(String.self, forKey: .hash)
        version = try container.decode(Int.self, forKey: .version)
        size = try container.decode(Int.self, forKey: .size)
        weight = try container.decode(Int.self, forKey: .weight)
        lockTime = try container.decode(Int64.self, forKey: .lockTime)
        inputs = try container.decode([RpcTransactionInput].self, forKey: .inputs)
        outputs = try container.decode([RpcTransactionOutput].self, forKey: .outputs)
        fee = try container.decode(Int64.self, forKey: .fee)
        confirmations = try container.decode(Int.self, forKey: .confirmations)
        blockHash = try container.decodeIfPresent(String.self, forKey: .blockHash)
        blockHeight = try container.decodeIfPresent(Int64.self, forKey: .blockHeight)
        timestamp = try container.decodeIfPresent(Int64.self, forKey: .timestamp)
        status = try container.decodeIfPresent(TransactionStatus.self, forKey: .status) ?? .pending
    }
}

/// Transaction input.
public struct RpcTransactionInput: Codable {
    public let txid: String
    public let vout: Int
    public let scriptSig: String?
    public let sequence: Int64
    public let witness: [String]?

    enum CodingKeys: String, CodingKey {
        case txid, vout, sequence, witness
        case scriptSig = "script_sig"
    }
}

/// Transaction output.
public struct RpcTransactionOutput: Codable {
    public let value: Int64
    public let n: Int
    public let scriptPubKey: ScriptPubKey

    enum CodingKeys: String, CodingKey {
        case value, n
        case scriptPubKey = "script_pubkey"
    }
}

/// Script public key.
public struct ScriptPubKey: Codable {
    public let asm: String
    public let hex: String
    public let type: String
    public let address: String?
}

// MARK: - Chain Information

/// Chain information.
public struct ChainInfo: Codable {
    public let chain: String
    public let blocks: Int64
    public let headers: Int64
    public let bestBlockHash: String
    public let difficulty: Double
    public let medianTime: Int64
    public let verificationProgress: Double
    public let chainWork: String
    public let pruned: Bool

    enum CodingKeys: String, CodingKey {
        case chain, blocks, headers, difficulty, pruned
        case bestBlockHash = "best_block_hash"
        case medianTime = "median_time"
        case verificationProgress = "verification_progress"
        case chainWork = "chain_work"
    }
}

/// Mempool information.
public struct MempoolInfo: Codable {
    public let size: Int
    public let bytes: Int64
    public let usage: Int64
    public let maxMempool: Int64
    public let mempoolMinFee: Double
    public let minRelayTxFee: Double

    enum CodingKeys: String, CodingKey {
        case size, bytes, usage
        case maxMempool = "max_mempool"
        case mempoolMinFee = "mempool_min_fee"
        case minRelayTxFee = "min_relay_tx_fee"
    }
}

/// Fee estimate for transactions.
public struct FeeEstimate: Codable {
    public let priority: RpcPriority
    public let feeRate: Int64
    public let estimatedBlocks: Int

    enum CodingKeys: String, CodingKey {
        case priority
        case feeRate = "fee_rate"
        case estimatedBlocks = "estimated_blocks"
    }
}

/// Result of transaction submission.
public struct SubmitResult: Codable {
    public let txid: String
    public let accepted: Bool
    public let reason: String?
}

// MARK: - Subscription

/// Subscription handle for WebSocket events.
public class Subscription {
    public let id: String
    public let channel: String
    private let onCancel: () -> Void

    init(id: String, channel: String, onCancel: @escaping () -> Void) {
        self.id = id
        self.channel = channel
        self.onCancel = onCancel
    }

    /// Cancel the subscription.
    public func cancel() {
        onCancel()
    }
}

// MARK: - Internal Types

struct WsMessage: Codable {
    let type: String
    let channel: String
    let data: String?
    let filter: String?
}

struct SendRawTxRequest: Codable {
    let hex: String
}

struct DecodeRawTxRequest: Codable {
    let hex: String
}

struct RawTxResponse: Codable {
    let hex: String
}

// MARK: - Errors

/// Errors thrown by RPC operations.
public enum RpcError: Error, LocalizedError {
    case invalidResponse
    case httpError(statusCode: Int, message: String)
    case networkError(Error)
    case encodingError(Error)
    case decodingError(Error)
    case webSocketError(String)

    public var errorDescription: String? {
        switch self {
        case .invalidResponse:
            return "Invalid response from server"
        case .httpError(let code, let message):
            return "HTTP error \(code): \(message)"
        case .networkError(let error):
            return "Network error: \(error.localizedDescription)"
        case .encodingError(let error):
            return "Encoding error: \(error.localizedDescription)"
        case .decodingError(let error):
            return "Decoding error: \(error.localizedDescription)"
        case .webSocketError(let message):
            return "WebSocket error: \(message)"
        }
    }
}
