/**
 * Synor Compiler SDK for Swift
 *
 * Smart contract compilation, optimization, and ABI generation.
 */

import Foundation

// MARK: - Enumerations

public enum OptimizationLevel: String, Codable {
    case none
    case basic
    case size
    case aggressive
}

// MARK: - Configuration

public struct StripOptions: Codable {
    public var stripDebug: Bool = true
    public var stripProducers: Bool = true
    public var stripNames: Bool = true
    public var stripCustom: Bool = true
    public var preserveSections: [String] = []
    public var stripUnused: Bool = true

    public init() {}

    private enum CodingKeys: String, CodingKey {
        case stripDebug = "strip_debug"
        case stripProducers = "strip_producers"
        case stripNames = "strip_names"
        case stripCustom = "strip_custom"
        case preserveSections = "preserve_sections"
        case stripUnused = "strip_unused"
    }
}

public struct CompilerConfig {
    public let apiKey: String
    public var endpoint: String = "https://compiler.synor.io/v1"
    public var timeout: TimeInterval = 60
    public var retries: Int = 3
    public var debug: Bool = false
    public var optimizationLevel: OptimizationLevel = .size
    public var stripOptions: StripOptions?
    public var maxContractSize: Int = 256 * 1024
    public var useWasmOpt: Bool = true
    public var validate: Bool = true
    public var extractMetadata: Bool = true
    public var generateAbi: Bool = true

    public init(apiKey: String) {
        self.apiKey = apiKey
    }
}

// MARK: - Types

public struct ValidationError: Codable {
    public let code: String
    public let message: String
    public let location: String?
}

public struct ValidationResult: Codable {
    public let valid: Bool
    public let errors: [ValidationError]
    public let warnings: [String]
    public let exportCount: Int
    public let importCount: Int
    public let functionCount: Int
    public let memoryPages: [Int?]

    private enum CodingKeys: String, CodingKey {
        case valid, errors, warnings
        case exportCount = "export_count"
        case importCount = "import_count"
        case functionCount = "function_count"
        case memoryPages = "memory_pages"
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        valid = try container.decode(Bool.self, forKey: .valid)
        errors = try container.decodeIfPresent([ValidationError].self, forKey: .errors) ?? []
        warnings = try container.decodeIfPresent([String].self, forKey: .warnings) ?? []
        exportCount = try container.decodeIfPresent(Int.self, forKey: .exportCount) ?? 0
        importCount = try container.decodeIfPresent(Int.self, forKey: .importCount) ?? 0
        functionCount = try container.decodeIfPresent(Int.self, forKey: .functionCount) ?? 0
        memoryPages = try container.decodeIfPresent([Int?].self, forKey: .memoryPages) ?? [0, nil]
    }
}

public struct ContractMetadata: Codable {
    public let name: String?
    public let version: String?
    public let authors: [String]
    public let description: String?
    public let license: String?
    public let repository: String?
    public let buildTimestamp: Int64?
    public let rustVersion: String?
    public let sdkVersion: String?
    public let custom: [String: String]

    private enum CodingKeys: String, CodingKey {
        case name, version, authors, description, license, repository, custom
        case buildTimestamp = "build_timestamp"
        case rustVersion = "rust_version"
        case sdkVersion = "sdk_version"
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decodeIfPresent(String.self, forKey: .name)
        version = try container.decodeIfPresent(String.self, forKey: .version)
        authors = try container.decodeIfPresent([String].self, forKey: .authors) ?? []
        description = try container.decodeIfPresent(String.self, forKey: .description)
        license = try container.decodeIfPresent(String.self, forKey: .license)
        repository = try container.decodeIfPresent(String.self, forKey: .repository)
        buildTimestamp = try container.decodeIfPresent(Int64.self, forKey: .buildTimestamp)
        rustVersion = try container.decodeIfPresent(String.self, forKey: .rustVersion)
        sdkVersion = try container.decodeIfPresent(String.self, forKey: .sdkVersion)
        custom = try container.decodeIfPresent([String: String].self, forKey: .custom) ?? [:]
    }
}

public struct TypeInfo: Codable {
    public let kind: String
    public let size: Int?
    public let element: TypeInfo?
    public let inner: TypeInfo?
    public let elements: [TypeInfo]?
    public let name: String?

    public var typeName: String {
        switch kind {
        case "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128",
             "bool", "string", "bytes", "address", "hash256":
            return kind
        case "fixedBytes":
            return "bytes\(size ?? 0)"
        case "array":
            return "\(element?.typeName ?? "")[]"
        case "fixedArray":
            return "\(element?.typeName ?? "")[\(size ?? 0)]"
        case "option":
            return "\(inner?.typeName ?? "")?"
        case "tuple":
            return "(\(elements?.map { $0.typeName }.joined(separator: ", ") ?? ""))"
        case "struct":
            return name ?? "struct"
        default:
            return name ?? "unknown"
        }
    }
}

public struct ParamAbi: Codable {
    public let name: String
    public let type: TypeInfo
    public let indexed: Bool

    private enum CodingKeys: String, CodingKey {
        case name, type, indexed
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        type = try container.decode(TypeInfo.self, forKey: .type)
        indexed = try container.decodeIfPresent(Bool.self, forKey: .indexed) ?? false
    }
}

public struct FunctionAbi: Codable {
    public let name: String
    public let selector: String
    public let inputs: [ParamAbi]
    public let outputs: [ParamAbi]
    public let view: Bool
    public let payable: Bool
    public let doc: String?

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        selector = try container.decode(String.self, forKey: .selector)
        inputs = try container.decodeIfPresent([ParamAbi].self, forKey: .inputs) ?? []
        outputs = try container.decodeIfPresent([ParamAbi].self, forKey: .outputs) ?? []
        view = try container.decodeIfPresent(Bool.self, forKey: .view) ?? false
        payable = try container.decodeIfPresent(Bool.self, forKey: .payable) ?? false
        doc = try container.decodeIfPresent(String.self, forKey: .doc)
    }
}

public struct EventAbi: Codable {
    public let name: String
    public let topic: String
    public let params: [ParamAbi]
    public let doc: String?

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        topic = try container.decode(String.self, forKey: .topic)
        params = try container.decodeIfPresent([ParamAbi].self, forKey: .params) ?? []
        doc = try container.decodeIfPresent(String.self, forKey: .doc)
    }
}

public struct ErrorAbi: Codable {
    public let name: String
    public let selector: String
    public let params: [ParamAbi]
    public let doc: String?

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        selector = try container.decode(String.self, forKey: .selector)
        params = try container.decodeIfPresent([ParamAbi].self, forKey: .params) ?? []
        doc = try container.decodeIfPresent(String.self, forKey: .doc)
    }
}

public struct ContractAbi: Codable {
    public let name: String
    public let version: String
    public let functions: [FunctionAbi]
    public let events: [EventAbi]
    public let errors: [ErrorAbi]

    public func findFunction(name: String) -> FunctionAbi? {
        functions.first { $0.name == name }
    }

    public func findBySelector(_ selector: String) -> FunctionAbi? {
        functions.first { $0.selector == selector }
    }
}

public struct CompilationResult: Codable {
    public let contractId: String
    public let code: String
    public let codeHash: String
    public let originalSize: Int
    public let optimizedSize: Int
    public let sizeReduction: Double
    public let metadata: ContractMetadata?
    public let abi: ContractAbi?
    public let estimatedDeployGas: Int64
    public let validation: ValidationResult?
    public let warnings: [String]

    private enum CodingKeys: String, CodingKey {
        case contractId = "contract_id"
        case code
        case codeHash = "code_hash"
        case originalSize = "original_size"
        case optimizedSize = "optimized_size"
        case sizeReduction = "size_reduction"
        case metadata, abi
        case estimatedDeployGas = "estimated_deploy_gas"
        case validation, warnings
    }

    public var sizeStats: String {
        String(format: "Original: %d bytes, Optimized: %d bytes, Reduction: %.1f%%",
               originalSize, optimizedSize, sizeReduction)
    }

    public func decodeCode() -> Data? {
        Data(base64Encoded: code)
    }
}

public struct SizeBreakdown: Codable {
    public let code: Int
    public let data: Int
    public let types: Int
    public let functions: Int
    public let memory: Int
    public let table: Int
    public let exports: Int
    public let imports: Int
    public let custom: Int
    public let total: Int
}

public struct FunctionAnalysis: Codable {
    public let name: String
    public let size: Int
    public let instructionCount: Int
    public let localCount: Int
    public let exported: Bool
    public let estimatedGas: Int64

    private enum CodingKeys: String, CodingKey {
        case name, size, exported
        case instructionCount = "instruction_count"
        case localCount = "local_count"
        case estimatedGas = "estimated_gas"
    }
}

public struct ImportAnalysis: Codable {
    public let module: String
    public let name: String
    public let kind: String
    public let signature: String?
}

public struct SecurityIssue: Codable {
    public let severity: String
    public let type: String
    public let description: String
    public let location: String?
}

public struct SecurityAnalysis: Codable {
    public let score: Int
    public let issues: [SecurityIssue]
    public let recommendations: [String]
}

public struct GasAnalysis: Codable {
    public let deploymentGas: Int64
    public let functionGas: [String: Int64]
    public let memoryInitGas: Int64
    public let dataSectionGas: Int64

    private enum CodingKeys: String, CodingKey {
        case deploymentGas = "deployment_gas"
        case functionGas = "function_gas"
        case memoryInitGas = "memory_init_gas"
        case dataSectionGas = "data_section_gas"
    }
}

public struct ContractAnalysis: Codable {
    public let sizeBreakdown: SizeBreakdown
    public let functions: [FunctionAnalysis]
    public let imports: [ImportAnalysis]
    public let security: SecurityAnalysis?
    public let gasAnalysis: GasAnalysis?

    private enum CodingKeys: String, CodingKey {
        case sizeBreakdown = "size_breakdown"
        case functions, imports, security
        case gasAnalysis = "gas_analysis"
    }
}

public struct CompilerError: Error {
    public let message: String
    public let code: String?
    public let httpStatus: Int?

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

// MARK: - Client

public actor SynorCompiler {
    private let config: CompilerConfig
    private let session: URLSession
    private let decoder: JSONDecoder
    private let encoder: JSONEncoder
    private var closed = false

    public let contracts: ContractsClient
    public let abi: AbiClient
    public let analysis: AnalysisClient
    public let validation: ValidationClient

    public init(config: CompilerConfig) {
        self.config = config
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = config.timeout
        self.session = URLSession(configuration: configuration)
        self.decoder = JSONDecoder()
        self.encoder = JSONEncoder()
        self.encoder.keyEncodingStrategy = .convertToSnakeCase

        self.contracts = ContractsClient()
        self.abi = AbiClient()
        self.analysis = AnalysisClient()
        self.validation = ValidationClient()

        Task { await self.contracts.setCompiler(self) }
        Task { await self.abi.setCompiler(self) }
        Task { await self.analysis.setCompiler(self) }
        Task { await self.validation.setCompiler(self) }
    }

    public var defaultOptimizationLevel: OptimizationLevel { config.optimizationLevel }

    public func healthCheck() async -> Bool {
        do {
            let result: [String: String] = try await get("/health")
            return result["status"] == "healthy"
        } catch {
            return false
        }
    }

    public func getInfo() async throws -> [String: Any] {
        try await get("/info")
    }

    public func close() {
        closed = true
    }

    public var isClosed: Bool { closed }

    public func compile(
        wasm: Data,
        optimizationLevel: OptimizationLevel? = nil,
        stripOptions: StripOptions? = nil,
        useWasmOpt: Bool? = nil,
        validate: Bool? = nil,
        extractMetadata: Bool? = nil,
        generateAbi: Bool? = nil
    ) async throws -> CompilationResult {
        let wasmBase64 = wasm.base64EncodedString()
        var body: [String: Any] = [
            "wasm": wasmBase64,
            "optimization_level": (optimizationLevel ?? config.optimizationLevel).rawValue,
            "use_wasm_opt": useWasmOpt ?? config.useWasmOpt,
            "validate": validate ?? config.validate,
            "extract_metadata": extractMetadata ?? config.extractMetadata,
            "generate_abi": generateAbi ?? config.generateAbi
        ]
        if let options = stripOptions {
            body["strip_options"] = try encoder.encode(options)
        }
        return try await post("/compile", body: body)
    }

    func get<T: Decodable>(_ path: String) async throws -> T {
        guard !closed else {
            throw CompilerError("Client has been closed", code: "CLIENT_CLOSED")
        }

        var request = URLRequest(url: URL(string: config.endpoint + path)!)
        request.httpMethod = "GET"
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")

        let (data, response) = try await session.data(for: request)
        return try handleResponse(data: data, response: response)
    }

    func post<T: Decodable>(_ path: String, body: [String: Any]) async throws -> T {
        guard !closed else {
            throw CompilerError("Client has been closed", code: "CLIENT_CLOSED")
        }

        var request = URLRequest(url: URL(string: config.endpoint + path)!)
        request.httpMethod = "POST"
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
        request.httpBody = try JSONSerialization.data(withJSONObject: body)

        let (data, response) = try await session.data(for: request)
        return try handleResponse(data: data, response: response)
    }

    private func handleResponse<T: Decodable>(data: Data, response: URLResponse) throws -> T {
        guard let httpResponse = response as? HTTPURLResponse else {
            throw CompilerError("Invalid response")
        }

        if httpResponse.statusCode >= 400 {
            if let error = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
                throw CompilerError(
                    error["message"] as? String ?? "HTTP \(httpResponse.statusCode)",
                    code: error["code"] as? String,
                    httpStatus: httpResponse.statusCode
                )
            }
            throw CompilerError("HTTP \(httpResponse.statusCode)", httpStatus: httpResponse.statusCode)
        }

        return try decoder.decode(T.self, from: data)
    }
}

// MARK: - Sub-Clients

public actor ContractsClient {
    private var compiler: SynorCompiler?

    func setCompiler(_ compiler: SynorCompiler) { self.compiler = compiler }

    public func compile(_ wasm: Data) async throws -> CompilationResult {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        return try await compiler.compile(wasm: wasm)
    }

    public func compileDev(_ wasm: Data) async throws -> CompilationResult {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        return try await compiler.compile(wasm: wasm, optimizationLevel: .none, useWasmOpt: false, validate: true)
    }

    public func compileProduction(_ wasm: Data) async throws -> CompilationResult {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        return try await compiler.compile(wasm: wasm, optimizationLevel: .aggressive, useWasmOpt: true, validate: true, extractMetadata: true, generateAbi: true)
    }

    public func get(_ contractId: String) async throws -> CompilationResult {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        return try await compiler.get("/contracts/\(contractId)")
    }
}

public actor AbiClient {
    private var compiler: SynorCompiler?

    func setCompiler(_ compiler: SynorCompiler) { self.compiler = compiler }

    public func extract(_ wasm: Data) async throws -> ContractAbi {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        let wasmBase64 = wasm.base64EncodedString()
        return try await compiler.post("/abi/extract", body: ["wasm": wasmBase64])
    }

    public func get(_ contractId: String) async throws -> ContractAbi {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        return try await compiler.get("/contracts/\(contractId)/abi")
    }
}

public actor AnalysisClient {
    private var compiler: SynorCompiler?

    func setCompiler(_ compiler: SynorCompiler) { self.compiler = compiler }

    public func analyze(_ wasm: Data) async throws -> ContractAnalysis {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        let wasmBase64 = wasm.base64EncodedString()
        return try await compiler.post("/analysis", body: ["wasm": wasmBase64])
    }

    public func get(_ contractId: String) async throws -> ContractAnalysis {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        return try await compiler.get("/contracts/\(contractId)/analysis")
    }

    public func extractMetadata(_ wasm: Data) async throws -> ContractMetadata {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        let wasmBase64 = wasm.base64EncodedString()
        return try await compiler.post("/analysis/metadata", body: ["wasm": wasmBase64])
    }
}

public actor ValidationClient {
    private var compiler: SynorCompiler?

    func setCompiler(_ compiler: SynorCompiler) { self.compiler = compiler }

    public func validate(_ wasm: Data) async throws -> ValidationResult {
        guard let compiler = compiler else { throw CompilerError("Compiler not set") }
        let wasmBase64 = wasm.base64EncodedString()
        return try await compiler.post("/validate", body: ["wasm": wasmBase64])
    }

    public func isValid(_ wasm: Data) async throws -> Bool {
        try await validate(wasm).valid
    }

    public func getErrors(_ wasm: Data) async throws -> [String] {
        try await validate(wasm).errors.map { $0.message }
    }
}

// MARK: - Constants

public let MAX_CONTRACT_SIZE = 256 * 1024
public let MAX_MEMORY_PAGES = 16
