import Foundation

// MARK: - Server Info Domain Model

struct ServerInfo: Codable, Hashable, Equatable {
    struct Load: Codable, Hashable, Equatable {
        let minute1: Double
        let minute5: Double
        let minute15: Double
        let percent: Double
        let cpuCount: Int
        let level: String

        init(minute1: Double, minute5: Double, minute15: Double, percent: Double, cpuCount: Int, level: String) {
            self.minute1 = minute1
            self.minute5 = minute5
            self.minute15 = minute15
            self.percent = percent
            self.cpuCount = cpuCount
            self.level = level
        }

        var displayPercent: Double {
            let clampedPercent = min(max(percent, 0), 100)
            guard clampedPercent != percent else {
                return clampedPercent
            }

            let normalized = (minute1 / Double(max(cpuCount, 1))) * 100
            return min(max(normalized, 0), 100)
        }
    }

    struct Memory: Codable, Hashable, Equatable {
        let free: Int
        let used: Int
        let total: Int
        let percent: Double

        init(free: Int, used: Int, total: Int, percent: Double) {
            self.free = free
            self.used = used
            self.total = total
            self.percent = percent
        }
    }

    struct DiskSpace: Codable, Hashable, Equatable {
        let free: Int
        let used: Int
        let total: Int
        let percent: Double

        init(free: Int, used: Int, total: Int, percent: Double) {
            self.free = free
            self.used = used
            self.total = total
            self.percent = percent
        }
    }

    struct ServicePort: Codable, Hashable, Identifiable, Equatable {
        var id: String { "\(service)-\(port)-\(proto)" }
        let service: String
        let status: String
        let port: Int
        let proto: String

        init(service: String, status: String, port: Int, proto: String) {
            self.service = service
            self.status = status
            self.port = port
            self.proto = proto
        }
    }

    struct PHPInterpreter: Codable, Hashable, Identifiable, Equatable {
        var id: String { [fullVersion, path ?? ""].joined(separator: "|") }
        let version: String
        let fullVersion: String
        let path: String?
        let configFile: String?
        let extensions: [String]
        let memoryLimit: String?
        let maxExecutionTime: String?

        init(
            version: String,
            fullVersion: String? = nil,
            path: String? = nil,
            configFile: String? = nil,
            extensions: [String] = [],
            memoryLimit: String? = nil,
            maxExecutionTime: String? = nil
        ) {
            self.version = version
            self.fullVersion = fullVersion ?? version
            self.path = path
            self.configFile = configFile
            self.extensions = extensions
            self.memoryLimit = memoryLimit
            self.maxExecutionTime = maxExecutionTime
        }

        var versionWithPath: String {
            var components = [fullVersion]
            if let path, !path.isEmpty {
                components.append(path)
            }
            return components.joined(separator: " – ")
        }
    }

    struct OperatingSystem: Codable, Hashable, Equatable {
        struct UpdateStatus: Codable, Hashable, Equatable {
            let updateCount: Int
            let securityUpdateCount: Int
            let rebootRequired: Bool

            init(updateCount: Int, securityUpdateCount: Int, rebootRequired: Bool) {
                self.updateCount = updateCount
                self.securityUpdateCount = securityUpdateCount
                self.rebootRequired = rebootRequired
            }
        }

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

        init(
            label: String,
            distribution: String,
            version: String,
            architecture: String,
            endOfLife: Bool,
            updates: UpdateStatus?
        ) {
            self.label = label
            self.distribution = distribution
            self.version = version
            self.architecture = architecture
            self.endOfLife = endOfLife
            self.updates = updates
        }
    }

    var hostname: String
    var ipAddresses: [String]
    var cpuCores: Int
    var serverTime: String
    var uptime: String
    var processCount: Int
    var apacheVersion: String
    var phpVersion: String
    var mysqlVersion: String?
    var mariadbVersion: String?
    var emailsInQueue: Int?
    var operatingSystem: OperatingSystem?
    var ports: [ServicePort]?
    var load: Load
    var memory: Memory
    var swap: Memory
    var diskSpace: DiskSpace
    var cpuUtilizationPercent: Double?
    var panelVersion: String
    var panelBuild: String
    var apiVersion: String
    var additionalPHPInterpreters: [PHPInterpreter]?

    var formattedVersion: String {
        "KeyHelp \(panelVersion) • Build \(panelBuild) • API \(apiVersion)"
    }

    var formattedServerTime: String {
        let normalizedServerTime = ServerInfo.normalizedServerTime(serverTime)

        if let date = ServerInfo.serverTimeParsers
            .lazy
            .compactMap({ $0.date(from: normalizedServerTime) })
            .first {
            return ServerInfo.displayFormatter.string(from: date)
        }

        return serverTime
    }

    var operatingSystemSummary: String? {
        guard let operatingSystem else { return nil }
        let components = [
            operatingSystem.label,
            operatingSystem.architecture
        ].filter { !$0.isEmpty }
        return components.isEmpty ? nil : components.joined(separator: " • ")
    }

    var supportsRestartCommand: Bool {
        ServerInfo.version(apiVersion, isAtLeast: "2.14")
    }

    var rebootRequired: Bool {
        operatingSystem?.updates?.rebootRequired ?? false
    }

    var statusTooltip: String {
        if rebootRequired {
            return "This host is online, but a reboot is required to complete pending updates."
        }
        return "This host is online."
    }

    var summaryCPUPercent: Double {
        if let cpuUtilizationPercent {
            return min(max(cpuUtilizationPercent, 0), 100)
        }
        return load.displayPercent
    }
}

// MARK: - Helpers & Sample Data

extension ServerInfo {
    private static let serverTimeParsers: [ISO8601DateFormatter] = {
        let withFractional = ISO8601DateFormatter()
        withFractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

        let withoutFractional = ISO8601DateFormatter()
        withoutFractional.formatOptions = [.withInternetDateTime]

        let noColonTimeZone = ISO8601DateFormatter()
        noColonTimeZone.formatOptions = [.withFullDate, .withTime, .withDashSeparatorInDate, .withColonSeparatorInTime, .withTimeZone]

        return [withFractional, withoutFractional, noColonTimeZone]
    }()

    private static let displayFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = .autoupdatingCurrent
        formatter.dateStyle = .medium
        formatter.timeStyle = .medium
        return formatter
    }()

    private static func normalizedServerTime(_ value: String) -> String {
        if value.range(of: #"[+-]\d{2}:\d{2}$"#, options: .regularExpression) != nil {
            return value
        }

        guard value.range(of: #"[+-]\d{4}$"#, options: .regularExpression) != nil else {
            return value
        }

        var normalized = value
        let insertionIndex = normalized.index(normalized.endIndex, offsetBy: -2)
        normalized.insert(":", at: insertionIndex)
        return normalized
    }

    private static func version(_ value: String, isAtLeast minimum: String) -> Bool {
        let lhs = value
            .split(separator: ".")
            .compactMap { Int($0) }
        let rhs = minimum
            .split(separator: ".")
            .compactMap { Int($0) }

        let count = max(lhs.count, rhs.count)
        for index in 0..<count {
            let left = index < lhs.count ? lhs[index] : 0
            let right = index < rhs.count ? rhs[index] : 0

            if left != right {
                return left > right
            }
        }

        return true
    }

    static let placeholder = ServerInfo(
        hostname: "preview.example.com",
        ipAddresses: ["192.168.1.1", "fe80::1"],
        cpuCores: 4,
        serverTime: "2025-04-04T18:00:00+0200",
        uptime: "3 Days / 12 Hours / 30 Minutes",
        processCount: 123,
        apacheVersion: "2.4.58",
        phpVersion: "8.2.12",
        mysqlVersion: "8.0.33",
        mariadbVersion: nil,
        emailsInQueue: 0,
        operatingSystem: OperatingSystem(
            label: "Debian 12.12 (64-bit)",
            distribution: "Debian",
            version: "12.12",
            architecture: "x86_64",
            endOfLife: false,
            updates: OperatingSystem.UpdateStatus(
                updateCount: 12,
                securityUpdateCount: 8,
                rebootRequired: true
            )
        ),
        ports: [
            ServicePort(service: "HTTP", status: "online", port: 80, proto: "tcp"),
            ServicePort(service: "HTTPS", status: "online", port: 443, proto: "tcp"),
            ServicePort(service: "SSH", status: "offline", port: 22, proto: "tcp")
        ],
        load: Load(minute1: 0.5, minute5: 0.3, minute15: 0.2, percent: 10.0, cpuCount: 4, level: "low"),
        memory: Memory(free: 8_000_000_000, used: 4_000_000_000, total: 12_000_000_000, percent: 33.3),
        swap: Memory(free: 4_000_000_000, used: 1_000_000_000, total: 5_000_000_000, percent: 20.0),
        diskSpace: DiskSpace(free: 100_000_000_000, used: 50_000_000_000, total: 150_000_000_000, percent: 33.3),
        cpuUtilizationPercent: 12.5,
        panelVersion: "25.0",
        panelBuild: "3394",
        apiVersion: "2",
        additionalPHPInterpreters: [
            PHPInterpreter(version: "8.3", path: "/usr/bin/php8.3"),
            PHPInterpreter(version: "8.2", path: "/usr/bin/php8.2")
        ]
    )
}
