import Foundation

/// Real-Check ob ein iPhone (per UDID) tatsächlich beim NanoMDM enrolled ist
/// und wann es zuletzt Commands acked hat. Implementiert via SSH+psql gegen
/// den rebreak-mdm-Server (Phase 1 / lokal-only).
///
/// Production-Pfad wäre ein dedizierter `mdm.rebreak.org/status/<udid>` REST-Endpoint —
/// solange den NanoMDM nicht hat, ist SSH der pragmatische Weg.
struct EnrollmentStatus: Equatable {
    /// True wenn UDID in NanoMDM-`devices`-Tabelle vorhanden.
    var isEnrolled: Bool
    /// Wann das iPhone zuletzt sein Auth-Token erneuert hat (≈ enrollment-time
    /// oder letzter MDM-handshake).
    var tokenUpdateAt: Date?
    /// Wann iPhone zuletzt einen Command Acknowledged hat. nil = nie.
    var lastAckAt: Date?
    /// Wieviele Commands aktuell in der Queue stecken + active sind.
    var pendingCommandCount: Int

    /// Heuristik: "frisch" wenn MDM-Channel kürzlich aktiv war.
    /// Entweder iPhone hat kürzlich Command acked, ODER hat kürzlich
    /// sein Auth-Token erneuert (= frisch enrolled). Letzteres deckt
    /// den Fall ab dass nach Re-Enroll noch keine Commands gepusht wurden.
    var isFresh: Bool {
        let now = Date()
        if let ack = lastAckAt, now.timeIntervalSince(ack) < 30 * 60 { return true }
        if let tok = tokenUpdateAt, now.timeIntervalSince(tok) < 30 * 60 { return true }
        return false
    }
}

enum MDMStatusError: Error, LocalizedError {
    case sshFailed(String)
    case parseError(String)

    var errorDescription: String? {
        switch self {
        case .sshFailed(let msg): return "SSH zu rebreak-mdm fehlgeschlagen: \(msg)"
        case .parseError(let msg): return "DB-Output-Parse-Error: \(msg)"
        }
    }
}

enum MDMStatus {
    /// SSH-Host-Alias aus ~/.ssh/config. Vorbedingung: `ssh rebreak-mdm`
    /// funktioniert ohne Passwort-Prompt.
    static let sshHost = "rebreak-mdm"

    /// Spawnt `ssh rebreak-mdm 'psql ... -c "<query>"'` und parsed die Antwort.
    /// Output-Format: tab-separated, eine Zeile pro Row.
    static func query(udid: String) async throws -> EnrollmentStatus {
        // ⚠️ udid ist user-controlled (kommt aus libimobiledevice-Output).
        // Apple-UDIDs sind aber strikt hex+dash → wir validieren defensiv.
        guard udid.range(of: "^[A-Fa-f0-9-]{20,50}$", options: .regularExpression) != nil else {
            throw MDMStatusError.parseError("UDID hat unerwartetes Format: \(udid)")
        }

        // Date-Format mit space-separator (kein 'T'), weil 'T' in einfachen
        // Anführungszeichen über doppelte-Anführungszeichen-shell quoting bricht.
        let sql = """
        SELECT \
          (SELECT count(*) FROM devices WHERE id='\(udid)') AS enrolled, \
          (SELECT to_char(token_update_at, 'YYYY-MM-DD HH24:MI:SS') FROM devices WHERE id='\(udid)') AS token_update_at, \
          (SELECT to_char(max(updated_at), 'YYYY-MM-DD HH24:MI:SS') FROM command_results WHERE id='\(udid)') AS last_ack, \
          (SELECT count(*) FROM enrollment_queue WHERE id='\(udid)' AND active=true) AS pending
        """
        let remoteCmd = #"source /opt/nanomdm/.env; PGPASSWORD=$NANOMDM_DB_PASS psql -h 127.0.0.1 -U nanomdm -d nanomdm -A -F$'\t' -t -c "\#(sql)""#

        let result = try await ProcessRunner.run(
            "/usr/bin/ssh",
            arguments: [
                "-o", "BatchMode=yes",
                "-o", "ConnectTimeout=5",
                sshHost,
                remoteCmd,
            ]
        )
        if result.exitCode != 0 {
            throw MDMStatusError.sshFailed(result.stderr.isEmpty ? result.stdout : result.stderr)
        }

        let line = result.stdout.split(separator: "\n").first.map(String.init) ?? ""
        let fields = line.split(separator: "\t", omittingEmptySubsequences: false).map(String.init)
        guard fields.count >= 4 else {
            throw MDMStatusError.parseError("Erwartete 4 Felder, bekam: \(fields.count) — Output: \(result.stdout)")
        }
        let isEnrolled = (Int(fields[0]) ?? 0) > 0
        let tokenUpdateAt = parseTimestamp(fields[1])
        let lastAckAt = parseTimestamp(fields[2])
        let pending = Int(fields[3].trimmingCharacters(in: .whitespaces)) ?? 0

        return EnrollmentStatus(
            isEnrolled: isEnrolled,
            tokenUpdateAt: tokenUpdateAt,
            lastAckAt: lastAckAt,
            pendingCommandCount: pending
        )
    }

    /// Liest das `result`-Field eines spezifischen Commands aus command_results.
    /// Returnt nil wenn iPhone noch nicht ge-acked hat oder command-uuid nicht
    /// existiert. Result ist Apple-Plist-XML (response payload des iPhones).
    static func readCommandResult(udid: String, commandUUID: String) async throws -> String? {
        guard udid.range(of: "^[A-Fa-f0-9-]{20,50}$", options: .regularExpression) != nil else {
            throw MDMStatusError.parseError("UDID-Format: \(udid)")
        }
        guard commandUUID.range(of: "^[A-Fa-f0-9-]{20,50}$", options: .regularExpression) != nil else {
            throw MDMStatusError.parseError("CommandUUID-Format: \(commandUUID)")
        }
        let sql = "SELECT result FROM command_results WHERE id='\(udid)' AND command_uuid='\(commandUUID)' AND status='Acknowledged' LIMIT 1"
        let remoteCmd = #"source /opt/nanomdm/.env; PGPASSWORD=$NANOMDM_DB_PASS psql -h 127.0.0.1 -U nanomdm -d nanomdm -A -t -c "\#(sql)""#
        let result = try await ProcessRunner.run(
            "/usr/bin/ssh",
            arguments: ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", sshHost, remoteCmd]
        )
        if result.exitCode != 0 {
            throw MDMStatusError.sshFailed(result.stderr)
        }
        let trimmed = result.stdout.trimmingCharacters(in: .whitespacesAndNewlines)
        return trimmed.isEmpty ? nil : trimmed
    }

    private static func parseTimestamp(_ s: String) -> Date? {
        let trimmed = s.trimmingCharacters(in: .whitespaces)
        if trimmed.isEmpty { return nil }
        let fb = DateFormatter()
        // psql to_char(... 'YYYY-MM-DD HH24:MI:SS') liefert "2026-05-27 05:52:57"
        // (UTC, ohne offset suffix — wir wissen aber dass postgres UTC liefert)
        fb.dateFormat = "yyyy-MM-dd HH:mm:ss"
        fb.timeZone = TimeZone(secondsFromGMT: 0)
        fb.locale = Locale(identifier: "en_US_POSIX")
        return fb.date(from: trimmed)
    }
}
