//
//  APIv2_13.swift
//  iKeyMon
//
//  Created by tracer on 13.11.25.
//


import Foundation

extension APIv2_13 {
    struct Load: Codable {
        let current: LoadMetrics
        let historical: [LoadMetrics]
        
        struct LoadMetrics: Codable {
            let oneMinute: Double
            let fiveMinute: Double
            let fifteenMinute: Double
            let timestamp: Date
            
            enum CodingKeys: String, CodingKey {
                case oneMinute = "load_1"
                case fiveMinute = "load_5"
                case fifteenMinute = "load_15"
                case timestamp
            }
        }
    }
    
    struct Memory: Codable {
        let system: SystemMemory
        let swap: SwapMemory?
        
        struct SystemMemory: Codable {
            let total: Int64
            let used: Int64
            let free: Int64
            let available: Int64
            let buffers: Int64?
            let cached: Int64?
        }
        
        struct SwapMemory: Codable {
            let total: Int64
            let used: Int64
            let free: Int64
        }
    }
    
    struct Utilization: Codable {
        let cpu: CPUUtilization
        let memory: MemoryUtilization
        let disk: [DiskUtilization]
        let network: [NetworkUtilization]?
        
        struct CPUUtilization: Codable {
            let overall: Double
            let cores: [Double]
            let processes: [ProcessInfo]?
            
            struct ProcessInfo: Codable {
                let pid: Int
                let name: String
                let cpuPercent: Double
                
                enum CodingKeys: String, CodingKey {
                    case pid
                    case name
                    case cpuPercent = "cpu_percent"
                }
            }
        }
        
        struct MemoryUtilization: Codable {
            let percent: Double
            let topProcesses: [ProcessMemoryInfo]?
            
            struct ProcessMemoryInfo: Codable {
                let pid: Int
                let name: String
                let memoryMB: Double
                
                enum CodingKeys: String, CodingKey {
                    case pid
                    case name
                    case memoryMB = "memory_mb"
                }
            }
        }
        
        struct DiskUtilization: Codable {
            let device: String
            let mountpoint: String
            let usedPercent: Double
            let totalBytes: Int64
            let usedBytes: Int64
            let freeBytes: Int64
            
            enum CodingKeys: String, CodingKey {
                case device
                case mountpoint
                case usedPercent = "used_percent"
                case totalBytes = "total_bytes"
                case usedBytes = "used_bytes"
                case freeBytes = "free_bytes"
            }
        }
        
        struct NetworkUtilization: Codable {
            let interface: String
            let bytesIn: Int64
            let bytesOut: Int64
            let packetsIn: Int64
            let packetsOut: Int64
            
            enum CodingKeys: String, CodingKey {
                case interface
                case bytesIn = "bytes_in"
                case bytesOut = "bytes_out"
                case packetsIn = "packets_in"
                case packetsOut = "packets_out"
            }
        }
    }
}

class APIv2_13: BaseAPIClient, ServerAPIProtocol {
    typealias LoadType = APIv2_13.Load
    typealias MemoryType = APIv2_13.Memory
    typealias UtilizationType = APIv2_13.Utilization
    
    private enum Endpoint: String {
        case systemInfo = "/api/v2/system/info"
        case load = "/api/v2/metrics/load"
        case memory = "/api/v2/metrics/memory"
        case utilization = "/api/v2/metrics/utilization"
        
        func url(baseURL: URL) -> URL {
            return baseURL.appendingPathComponent(self.rawValue)
        }
    }
    
    func fetchSystemInfo() async throws -> SystemInfo {
        let url = Endpoint.systemInfo.url(baseURL: baseURL)
        let request = URLRequest(url: url)
        return try await performRequest(request, responseType: SystemInfo.self)
    }
    
    func fetchLoad() async throws -> LoadType {
        let url = Endpoint.load.url(baseURL: baseURL)
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        return try await performRequest(request, responseType: LoadType.self)
    }
    
    func fetchMemory() async throws -> MemoryType {
        let url = Endpoint.memory.url(baseURL: baseURL)
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        return try await performRequest(request, responseType: MemoryType.self)
    }
    
    func fetchUtilization() async throws -> UtilizationType {
        let url = Endpoint.utilization.url(baseURL: baseURL)
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        return try await performRequest(request, responseType: UtilizationType.self)
    }

    func fetchServerSummary(apiKey: String) async throws -> ServerInfo {
        let summaryURL = baseURL.appendingPathComponent("api/v2/server")
        var request = URLRequest(url: summaryURL)
        request.httpMethod = "GET"
        request.setValue(apiKey, forHTTPHeaderField: "X-API-KEY")
        request.timeoutInterval = 30

        let (data, response) = try await session.data(for: request)
        guard let httpResponse = response as? HTTPURLResponse else {
            throw APIError.invalidResponse
        }

        guard httpResponse.statusCode == 200 else {
            throw APIError.httpError(httpResponse.statusCode)
        }

        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let envelope = try decoder.decode(ServerSummaryEnvelope.self, from: data)
        return envelope.toDomain()
    }
}

// MARK: - Server Summary Mapping

private extension APIv2_13 {
    struct ServerSummaryEnvelope: Decodable {
        let meta: Meta
        let operatingSystem: OperatingSystem?
        let utilization: Utilization
        let components: Components
        let ports: [Port]?
        let additionalPhpInterpreters: [AdditionalInterpreter]?

        struct Meta: Decodable {
            struct Uptime: Decodable {
                let days: Int
                let hours: Int
                let minutes: Int
                let seconds: Int

                var formatted: String {
                    "\(days) Days / \(hours) Hours / \(minutes) Minutes / \(seconds) Seconds"
                }
            }

            let hostname: String
            let ipAddresses: [String]
            let serverTime: String
            let uptime: Uptime
            let panelVersion: String
            let panelBuild: Int
            let apiVersion: String
        }

        struct Utilization: Decodable {
            struct Load: Decodable {
                let minute1: Double
                let minute5: Double
                let minute15: Double
                let cpuCount: Int
                let percent: Double
                let level: String
            }

            struct Memory: Decodable {
                let free: Int
                let used: Int
                let total: Int
                let percent: Double
            }

            struct Disk: Decodable {
                let free: Int
                let used: Int
                let total: Int
                let percent: Double
            }

            let processCount: Int
            let emailsInQueue: Int?
            let load: Load
            let diskSpace: Disk
            let memory: Memory
            let swap: Memory
        }

        struct Components: Decodable {
            let apache: String
            let php: String
            let mysql: String?
            let mariadb: String?
        }

        struct Port: Decodable {
            let service: String
            let status: String
            let port: Int
            let proto: String

            enum CodingKeys: String, CodingKey {
                case service
                case status
                case port
                case proto = "protocol"
            }
        }

        struct AdditionalInterpreter: Decodable {
            let version: String
            let path: String?
            let configFile: String?
        }
        
        struct OperatingSystem: Decodable {
            struct Updates: Decodable {
                let updateCount: Int
                let securityUpdateCount: Int
                let rebootRequired: Bool

                enum CodingKeys: String, CodingKey {
                    case updateCount = "update_count"
                    case securityUpdateCount = "security_update_count"
                    case rebootRequired = "reboot_required"
                }

                init(from decoder: Decoder) throws {
                    let container = try decoder.container(keyedBy: CodingKeys.self)
                    updateCount = try container.decodeIfPresent(Int.self, forKey: .updateCount) ?? 0
                    securityUpdateCount = try container.decodeIfPresent(Int.self, forKey: .securityUpdateCount) ?? 0
                    rebootRequired = try container.decodeIfPresent(Bool.self, forKey: .rebootRequired) ?? false
                }
            }

            let label: String
            let distribution: String
            let version: String
            let architecture: String
            let endOfLife: Bool
            let updates: Updates?

            enum CodingKeys: String, CodingKey {
                case label
                case distribution
                case version
                case architecture
                case endOfLife = "end_of_life"
                case updates
            }

            init(from decoder: Decoder) throws {
                let container = try decoder.container(keyedBy: CodingKeys.self)
                label = try container.decodeIfPresent(String.self, forKey: .label) ?? ""
                distribution = try container.decodeIfPresent(String.self, forKey: .distribution) ?? ""
                version = try container.decodeIfPresent(String.self, forKey: .version) ?? ""
                architecture = try container.decodeIfPresent(String.self, forKey: .architecture) ?? ""
                endOfLife = try container.decodeIfPresent(Bool.self, forKey: .endOfLife) ?? false
                updates = try container.decodeIfPresent(Updates.self, forKey: .updates)
            }
        }

        func toDomain() -> ServerInfo {
            let load = utilization.load
            let disk = utilization.diskSpace
            let memory = utilization.memory
            let swapMemory = utilization.swap

            return ServerInfo(
                hostname: meta.hostname,
                ipAddresses: meta.ipAddresses,
                cpuCores: load.cpuCount,
                serverTime: meta.serverTime,
                uptime: meta.uptime.formatted,
                processCount: utilization.processCount,
                apacheVersion: components.apache,
                phpVersion: components.php,
                mysqlVersion: components.mysql,
                mariadbVersion: components.mariadb,
                emailsInQueue: utilization.emailsInQueue,
                operatingSystem: operatingSystem.map {
                    ServerInfo.OperatingSystem(
                        label: $0.label,
                        distribution: $0.distribution,
                        version: $0.version,
                        architecture: $0.architecture,
                        endOfLife: $0.endOfLife,
                        updates: $0.updates.map {
                            ServerInfo.OperatingSystem.UpdateStatus(
                                updateCount: $0.updateCount,
                                securityUpdateCount: $0.securityUpdateCount,
                                rebootRequired: $0.rebootRequired
                            )
                        }
                    )
                },
                ports: ports?.map {
                    ServerInfo.ServicePort(service: $0.service, status: $0.status, port: $0.port, proto: $0.proto)
                },
                load: ServerInfo.Load(
                    minute1: load.minute1,
                    minute5: load.minute5,
                    minute15: load.minute15,
                    percent: load.percent,
                    cpuCount: load.cpuCount,
                    level: load.level
                ),
                memory: ServerInfo.Memory(
                    free: memory.free,
                    used: memory.used,
                    total: memory.total,
                    percent: memory.percent
                ),
                swap: ServerInfo.Memory(
                    free: swapMemory.free,
                    used: swapMemory.used,
                    total: swapMemory.total,
                    percent: swapMemory.percent
                ),
                diskSpace: ServerInfo.DiskSpace(
                    free: disk.free,
                    used: disk.used,
                    total: disk.total,
                    percent: disk.percent
                ),
                panelVersion: meta.panelVersion,
                panelBuild: String(meta.panelBuild),
                apiVersion: meta.apiVersion,
                additionalPHPInterpreters: additionalPhpInterpreters?.map {
                    ServerInfo.PHPInterpreter(
                        version: $0.version,
                        path: $0.path,
                        configFile: $0.configFile,
                        extensions: [],
                        memoryLimit: nil,
                        maxExecutionTime: nil
                    )
                }
            )
        }
    }
}
