import Foundation

// MARK: - ABI Types

public struct AbiParameter: Codable {
    public let name: String?
    public let type: String
    public let indexed: Bool?
    public let components: [AbiParameter]?

    public init(name: String? = nil, type: String, indexed: Bool? = nil, components: [AbiParameter]? = nil) {
        self.name = name
        self.type = type
        self.indexed = indexed
        self.components = components
    }

    func toDictionary() -> [String: Any] {
        var dict: [String: Any] = ["type": type]
        if let name = name { dict["name"] = name }
        if let indexed = indexed { dict["indexed"] = indexed }
        if let components = components { dict["components"] = components.map { $0.toDictionary() } }
        return dict
    }
}

public struct AbiEntry: Codable {
    public let type: String
    public let name: String?
    public let inputs: [AbiParameter]?
    public let outputs: [AbiParameter]?
    public let stateMutability: String?
    public let anonymous: Bool?

    public init(type: String, name: String? = nil, inputs: [AbiParameter]? = nil, outputs: [AbiParameter]? = nil, stateMutability: String? = nil, anonymous: Bool? = nil) {
        self.type = type
        self.name = name
        self.inputs = inputs
        self.outputs = outputs
        self.stateMutability = stateMutability
        self.anonymous = anonymous
    }

    func toDictionary() -> [String: Any] {
        var dict: [String: Any] = ["type": type]
        if let name = name { dict["name"] = name }
        if let inputs = inputs { dict["inputs"] = inputs.map { $0.toDictionary() } }
        if let outputs = outputs { dict["outputs"] = outputs.map { $0.toDictionary() } }
        if let stateMutability = stateMutability { dict["stateMutability"] = stateMutability }
        if let anonymous = anonymous { dict["anonymous"] = anonymous }
        return dict
    }
}

// MARK: - Deployment

public struct DeployContractOptions {
    public let bytecode: String
    public let abi: [AbiEntry]?
    public let constructorArgs: [Any]?
    public let value: String?
    public let gasLimit: Int64?
    public let gasPrice: String?
    public let nonce: Int64?

    public init(bytecode: String, abi: [AbiEntry]? = nil, constructorArgs: [Any]? = nil, value: String? = nil, gasLimit: Int64? = nil, gasPrice: String? = nil, nonce: Int64? = nil) {
        self.bytecode = bytecode
        self.abi = abi
        self.constructorArgs = constructorArgs
        self.value = value
        self.gasLimit = gasLimit
        self.gasPrice = gasPrice
        self.nonce = nonce
    }
}

public struct DeploymentResult: Codable {
    public let contractAddress: String
    public let transactionHash: String
    public let deployer: String?
    public let gasUsed: Int64?
    public let blockNumber: Int64?
    public let blockHash: String?

    enum CodingKeys: String, CodingKey {
        case contractAddress = "contract_address"
        case transactionHash = "transaction_hash"
        case deployer
        case gasUsed = "gas_used"
        case blockNumber = "block_number"
        case blockHash = "block_hash"
    }
}

// MARK: - Contract Interaction

public struct CallContractOptions {
    public let contract: String
    public let method: String
    public let args: [Any]
    public let abi: [AbiEntry]

    public init(contract: String, method: String, args: [Any] = [], abi: [AbiEntry]) {
        self.contract = contract
        self.method = method
        self.args = args
        self.abi = abi
    }
}

public struct SendContractOptions {
    public let contract: String
    public let method: String
    public let args: [Any]
    public let abi: [AbiEntry]
    public let value: String?
    public let gasLimit: Int64?
    public let gasPrice: String?
    public let nonce: Int64?

    public init(contract: String, method: String, args: [Any] = [], abi: [AbiEntry], value: String? = nil, gasLimit: Int64? = nil, gasPrice: String? = nil, nonce: Int64? = nil) {
        self.contract = contract
        self.method = method
        self.args = args
        self.abi = abi
        self.value = value
        self.gasLimit = gasLimit
        self.gasPrice = gasPrice
        self.nonce = nonce
    }
}

public struct TransactionResult: Codable {
    public let transactionHash: String
    public let blockNumber: Int64?
    public let blockHash: String?
    public let gasUsed: Int64?
    public let effectiveGasPrice: String?
    public let status: String?
    public let logs: [EventLog]?

    enum CodingKeys: String, CodingKey {
        case transactionHash = "transaction_hash"
        case blockNumber = "block_number"
        case blockHash = "block_hash"
        case gasUsed = "gas_used"
        case effectiveGasPrice = "effective_gas_price"
        case status
        case logs
    }
}

// MARK: - Events

public struct EventLog: Codable {
    public let address: String
    public let topics: [String]
    public let data: String
    public let blockNumber: Int64?
    public let transactionHash: String?
    public let logIndex: Int?
    public let blockHash: String?
    public let removed: Bool?

    enum CodingKeys: String, CodingKey {
        case address, topics, data
        case blockNumber = "block_number"
        case transactionHash = "transaction_hash"
        case logIndex = "log_index"
        case blockHash = "block_hash"
        case removed
    }

    func toDictionary() -> [String: Any] {
        var dict: [String: Any] = [
            "address": address,
            "topics": topics,
            "data": data
        ]
        if let blockNumber = blockNumber { dict["block_number"] = blockNumber }
        if let transactionHash = transactionHash { dict["transaction_hash"] = transactionHash }
        if let logIndex = logIndex { dict["log_index"] = logIndex }
        if let blockHash = blockHash { dict["block_hash"] = blockHash }
        if let removed = removed { dict["removed"] = removed }
        return dict
    }
}

public struct DecodedEvent: Codable {
    public let name: String
    public let signature: String?
    public let args: [String: AnyCodable]?
    public let log: EventLog?
}

public struct EventFilter {
    public let contract: String
    public let event: String?
    public let fromBlock: Int64?
    public let toBlock: Int64?
    public let topics: [String?]?
    public let abi: [AbiEntry]?

    public init(contract: String, event: String? = nil, fromBlock: Int64? = nil, toBlock: Int64? = nil, topics: [String?]? = nil, abi: [AbiEntry]? = nil) {
        self.contract = contract
        self.event = event
        self.fromBlock = fromBlock
        self.toBlock = toBlock
        self.topics = topics
        self.abi = abi
    }
}

// MARK: - ABI Utilities

public struct EncodeCallOptions {
    public let method: String
    public let args: [Any]
    public let abi: [AbiEntry]

    public init(method: String, args: [Any] = [], abi: [AbiEntry]) {
        self.method = method
        self.args = args
        self.abi = abi
    }
}

public struct DecodeResultOptions {
    public let data: String
    public let method: String
    public let abi: [AbiEntry]

    public init(data: String, method: String, abi: [AbiEntry]) {
        self.data = data
        self.method = method
        self.abi = abi
    }
}

// MARK: - Gas Estimation

public struct EstimateGasOptions {
    public let contract: String
    public let method: String
    public let args: [Any]
    public let abi: [AbiEntry]
    public let value: String?

    public init(contract: String, method: String, args: [Any] = [], abi: [AbiEntry], value: String? = nil) {
        self.contract = contract
        self.method = method
        self.args = args
        self.abi = abi
        self.value = value
    }
}

public struct GasEstimation: Codable {
    public let gasLimit: Int64
    public let gasPrice: String?
    public let maxFeePerGas: String?
    public let maxPriorityFeePerGas: String?
    public let estimatedCost: String?

    enum CodingKeys: String, CodingKey {
        case gasLimit = "gas_limit"
        case gasPrice = "gas_price"
        case maxFeePerGas = "max_fee_per_gas"
        case maxPriorityFeePerGas = "max_priority_fee_per_gas"
        case estimatedCost = "estimated_cost"
    }
}

// MARK: - Contract Information

public struct BytecodeInfo: Codable {
    public let bytecode: String
    public let deployedBytecode: String?
    public let size: Int?
    public let isContract: Bool?

    enum CodingKeys: String, CodingKey {
        case bytecode
        case deployedBytecode = "deployed_bytecode"
        case size
        case isContract = "is_contract"
    }
}

public struct VerifyContractOptions {
    public let address: String
    public let sourceCode: String
    public let compilerVersion: String
    public let constructorArgs: String?
    public let optimization: Bool?
    public let optimizationRuns: Int?
    public let license: String?

    public init(address: String, sourceCode: String, compilerVersion: String, constructorArgs: String? = nil, optimization: Bool? = nil, optimizationRuns: Int? = nil, license: String? = nil) {
        self.address = address
        self.sourceCode = sourceCode
        self.compilerVersion = compilerVersion
        self.constructorArgs = constructorArgs
        self.optimization = optimization
        self.optimizationRuns = optimizationRuns
        self.license = license
    }
}

public struct VerificationResult: Codable {
    public let verified: Bool
    public let address: String?
    public let compilerVersion: String?
    public let optimization: Bool?
    public let optimizationRuns: Int?
    public let license: String?
    public let abi: [AbiEntry]?
    public let sourceCode: String?

    enum CodingKeys: String, CodingKey {
        case verified, address
        case compilerVersion = "compiler_version"
        case optimization
        case optimizationRuns = "optimization_runs"
        case license, abi
        case sourceCode = "source_code"
    }
}

// MARK: - Multicall

public struct MulticallRequest {
    public let contract: String
    public let method: String
    public let args: [Any]
    public let abi: [AbiEntry]

    public init(contract: String, method: String, args: [Any] = [], abi: [AbiEntry]) {
        self.contract = contract
        self.method = method
        self.args = args
        self.abi = abi
    }

    func toDictionary() -> [String: Any] {
        return [
            "contract": contract,
            "method": method,
            "args": args,
            "abi": abi.map { $0.toDictionary() }
        ]
    }
}

public struct MulticallResult: Codable {
    public let success: Bool
    public let result: AnyCodable?
    public let error: String?
}

// MARK: - Storage

public struct ReadStorageOptions {
    public let contract: String
    public let slot: String
    public let blockNumber: Int64?

    public init(contract: String, slot: String, blockNumber: Int64? = nil) {
        self.contract = contract
        self.slot = slot
        self.blockNumber = blockNumber
    }
}

// MARK: - AnyCodable Helper

public struct AnyCodable: Codable {
    public let value: Any

    public init(_ value: Any) {
        self.value = value
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let intVal = try? container.decode(Int.self) {
            value = intVal
        } else if let doubleVal = try? container.decode(Double.self) {
            value = doubleVal
        } else if let boolVal = try? container.decode(Bool.self) {
            value = boolVal
        } else if let stringVal = try? container.decode(String.self) {
            value = stringVal
        } else if let arrayVal = try? container.decode([AnyCodable].self) {
            value = arrayVal.map { $0.value }
        } else if let dictVal = try? container.decode([String: AnyCodable].self) {
            value = dictVal.mapValues { $0.value }
        } else {
            value = NSNull()
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch value {
        case let intVal as Int:
            try container.encode(intVal)
        case let doubleVal as Double:
            try container.encode(doubleVal)
        case let boolVal as Bool:
            try container.encode(boolVal)
        case let stringVal as String:
            try container.encode(stringVal)
        case let arrayVal as [Any]:
            try container.encode(arrayVal.map { AnyCodable($0) })
        case let dictVal as [String: Any]:
            try container.encode(dictVal.mapValues { AnyCodable($0) })
        default:
            try container.encodeNil()
        }
    }
}
