import Foundation
import SwiftData
import os.log

private let logger = Logger(subsystem: "ro.xdev.healthprobe", category: "SnapshotLifecycleService")

enum SnapshotLifecycleService {
    struct DeletionPreview {
        let target: HealthSnapshot
        let affectedDeltas: [SnapshotDelta]
        let mergedDelta: SnapshotDelta?
        let willBreakChain: Bool
        let description: String
    }

    static func previewDeletion(of snapshot: HealthSnapshot, context: ModelContext) throws -> DeletionPreview {
        let allDeltas = try fetchDeltas(context: context)
        let incomingDelta = allDeltas.first { $0.toSnapshotID == snapshot.id }
        let outgoingDelta = allDeltas.first { $0.fromSnapshotID == snapshot.id }

        var willBreakChain = false
        var description = ""

        let integrityResult = IntegrityService.validate(snapshot: snapshot, delta: incomingDelta)
        switch integrityResult {
        case .valid, .pendingSync:
            break
        case .checksumMismatch(_, let expected, let actual):
            willBreakChain = true
            description = "Checksum mismatch: expected \(expected.prefix(8))…, got \(actual.prefix(8))…"
        case .missingDelta(let fromID, _):
            willBreakChain = true
            description = "Missing delta from \(fromID)"
        case .corrupted(_, let reason):
            willBreakChain = true
            description = reason
        }

        var affectedDeltas: [SnapshotDelta] = []
        if let d = incomingDelta { affectedDeltas.append(d) }
        if let d = outgoingDelta { affectedDeltas.append(d) }

        // For intermediate deletion, compute the merged delta preview
        var mergedDelta: SnapshotDelta? = nil
        if let d1 = incomingDelta, let d2 = outgoingDelta,
           let prevSnap = try fetchSnapshot(id: d1.fromSnapshotID, context: context),
           let nextSnap = try fetchSnapshot(id: d2.toSnapshotID, context: context) {
            mergedDelta = DeltaService.mergeDeltas(
                d1: d1, d2: d2,
                snapshotBefore: prevSnap,
                snapshotAfter: nextSnap
            )
        }

        return DeletionPreview(
            target: snapshot,
            affectedDeltas: affectedDeltas,
            mergedDelta: mergedDelta,
            willBreakChain: willBreakChain,
            description: description
        )
    }

    static func delete(_ snapshot: HealthSnapshot, context: ModelContext) throws {
        let allDeltas = try fetchDeltas(context: context)
        let incomingDelta = allDeltas.first { $0.toSnapshotID == snapshot.id }
        let outgoingDelta = allDeltas.first { $0.fromSnapshotID == snapshot.id }

        let deviceID = snapshot.deviceID
        let version = Bundle.main.appBuildVersion

        // Build operation log before making changes
        let log = OperationLog(
            operationType: "delete",
            summary: buildSummary(snapshot: snapshot, incoming: incomingDelta, outgoing: outgoingDelta),
            deviceID: deviceID,
            appBuildVersion: version
        )
        log.affectedSnapshotIDs = [snapshot.id.uuidString]
        let logID = log.id
        context.insert(log)

        if incomingDelta == nil && outgoingDelta == nil {
            // Standalone snapshot — just delete
            context.delete(snapshot)
        } else if incomingDelta == nil, let outgoing = outgoingDelta {
            // Oldest snapshot: delete it and outgoing delta, set next as chain start
            if let nextSnap = try fetchSnapshot(id: outgoing.toSnapshotID, context: context) {
                nextSnap.previousSnapshotID = nil
                nextSnap.isChainStart = true
            }
            context.delete(outgoing)
            context.delete(snapshot)
        } else if outgoingDelta == nil, let incoming = incomingDelta {
            // Latest snapshot: delete it and incoming delta
            context.delete(incoming)
            context.delete(snapshot)
        } else if let d1 = incomingDelta, let d2 = outgoingDelta {
            // Intermediate snapshot: merge deltas and delete
            guard let prevSnap = try fetchSnapshot(id: d1.fromSnapshotID, context: context),
                  let nextSnap = try fetchSnapshot(id: d2.toSnapshotID, context: context) else {
                logger.error("SnapshotLifecycleService: failed to find surrounding snapshots for merge")
                throw LifecycleError.missingNeighbor
            }

            let merged = DeltaService.mergeDeltas(
                d1: d1, d2: d2,
                snapshotBefore: prevSnap,
                snapshotAfter: nextSnap
            )
            merged.deviceID = deviceID
            context.insert(merged)
            for td in merged.typeDeltas ?? [] { context.insert(td) }

            nextSnap.previousSnapshotID = prevSnap.id
            context.delete(d1)
            context.delete(d2)
            context.delete(snapshot)
        }

        // Atomic save: log + destructive changes in same save call
        try context.save()

        // Post-save OperationLog verification
        let verifyDescriptor = FetchDescriptor<OperationLog>(
            predicate: #Predicate<OperationLog> { $0.id == logID }
        )
        if (try? context.fetch(verifyDescriptor).first) == nil {
            logger.critical("OperationLog not found after save — attempting recovery re-insert")
            let recovery = OperationLog(
                operationType: log.operationType,
                summary: log.summary,
                deviceID: log.operationDeviceID,
                appBuildVersion: log.operationAppBuildVersion
            )
            recovery.affectedSnapshotIDsJSON = log.affectedSnapshotIDsJSON
            context.insert(recovery)
            try? context.save()
        }
    }

    // MARK: - Fetch helpers

    private static func fetchDeltas(context: ModelContext) throws -> [SnapshotDelta] {
        try context.fetch(FetchDescriptor<SnapshotDelta>())
    }

    private static func fetchSnapshot(id: UUID, context: ModelContext) throws -> HealthSnapshot? {
        let descriptor = FetchDescriptor<HealthSnapshot>(
            predicate: #Predicate<HealthSnapshot> { $0.id == id }
        )
        return try context.fetch(descriptor).first
    }

    private static func buildSummary(snapshot: HealthSnapshot, incoming: SnapshotDelta?, outgoing: SnapshotDelta?) -> String {
        let position: String
        if incoming == nil && outgoing == nil { position = "standalone" }
        else if incoming == nil { position = "oldest" }
        else if outgoing == nil { position = "latest" }
        else { position = "intermediate" }
        return "Deleted \(position) snapshot \(snapshot.id) at \(snapshot.timestamp)"
    }

    enum LifecycleError: Error {
        case missingNeighbor
    }
}

private extension Bundle {
    var appBuildVersion: String {
        let version = infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
        let build = infoDictionary?["CFBundleVersion"] as? String ?? ""
        return "\(version) (\(build))"
    }
}
