import Foundation
import Observation

enum RecordListMode {
    case disappeared(typeIdentifier: String)
    case disappearedDiff(typeIdentifier: String, fromObservationID: Int64, toObservationID: Int64)
    case added(typeIdentifier: String, afterDate: Date, beforeDate: Date)
    case addedDiff(typeIdentifier: String, afterDate: Date, beforeDate: Date, fromObservationID: Int64, toObservationID: Int64)

    var typeIdentifier: String {
        switch self {
        case .disappeared(let typeID):
            return typeID
        case .disappearedDiff(let typeID, _, _):
            return typeID
        case .added(let typeID, _, _):
            return typeID
        case .addedDiff(let typeID, _, _, _, _):
            return typeID
        }
    }

    var archiveDiff: (fromObservationID: Int64, toObservationID: Int64, kind: HealthArchiveDiffKind)? {
        switch self {
        case .disappeared:
            return nil
        case .disappearedDiff(_, let fromObservationID, let toObservationID):
            return (fromObservationID, toObservationID, .disappeared)
        case .added:
            return nil
        case .addedDiff(_, _, _, let fromObservationID, let toObservationID):
            return (fromObservationID, toObservationID, .appeared)
        }
    }
}

enum ExportState: Equatable {
    case idle
    case loading
    case ready(URL)
    case failed(String)
}

@MainActor
@Observable
final class DataTypeRecordListViewModel {
    let typeIdentifier: String
    let displayName: String
    let totalCount: Int
    let mode: RecordListMode
    let previewRecords: [HealthRecordValue]

    var displayRecords: [HealthRecordValue] = []
    var isLoadingMore = false
    var exportPreview: HealthArchiveExportPreview?
    var exportPreviewError: String?
    var hasMore: Bool {
        if mode.archiveDiff != nil {
            return archiveHasMore && displayRecords.count < totalCount
        }
        return displayRecords.count < min(totalCount, previewRecords.count)
    }
    var exportState: ExportState = .idle

    private var pageIndex = 0
    private let pageSize = 50
    private let archiveStore: HealthArchiveStore
    private let reportID = UUID()
    private var archiveCursor: RecordCursor?
    private var archiveHasMore = true

    init(
        typeIdentifier: String,
        displayName: String,
        totalCount: Int,
        mode: RecordListMode,
        previewRecords: [HealthRecordValue] = [],
        archiveStore: HealthArchiveStore
    ) {
        self.typeIdentifier = typeIdentifier
        self.displayName = displayName
        self.totalCount = totalCount
        self.mode = mode
        self.previewRecords = previewRecords
        self.archiveStore = archiveStore
    }

    func loadFirstPage() async {
        guard displayRecords.isEmpty else { return }
        await loadExportPreview()
        pageIndex = 0
        await loadNextPage()
    }

    func loadExportPreview() async {
        do {
            exportPreview = try await archiveStore.exportPreview(buildExportRequest())
            exportPreviewError = nil
        } catch {
            exportPreview = nil
            exportPreviewError = error.localizedDescription
        }
    }

    func loadNextPage() async {
        guard !isLoadingMore, hasMore else { return }
        isLoadingMore = true
        defer { isLoadingMore = false }

        if let archiveDiff = mode.archiveDiff {
            await loadNextArchiveDiffPage(archiveDiff)
            return
        }

        let batchStart = pageIndex
        let batchEnd = min(pageIndex + pageSize, previewRecords.count)

        guard batchStart < previewRecords.count else {
            return
        }

        let batch = Array(previewRecords[batchStart..<batchEnd])

        // Enrich batch with actual values from archive store
        var enrichedBatch = batch
        do {
            let fingerprints = Set(batch.map { $0.recordFingerprint })
            let request = HealthArchiveRecordRequest(
                sampleTypeIdentifier: typeIdentifier,
                fingerprints: fingerprints,
                limit: nil
            )
            let archivedRecords = try await archiveStore.records(for: request)

            let archiveByFingerprint = Dictionary(grouping: archivedRecords, by: { $0.strictFingerprint })
            enrichedBatch = batch.map { cached in
                guard let archived = archiveByFingerprint[cached.recordFingerprint]?.first,
                      cached.displayValue == nil else { return cached }
                return HealthRecordValue(
                    typeIdentifier: cached.typeIdentifier,
                    sampleUUIDHash: cached.sampleUUIDHash,
                    recordFingerprint: cached.recordFingerprint,
                    startDate: cached.startDate,
                    endDate: cached.endDate,
                    displayValue: archived.displayValue
                )
            }
        } catch {
            // Cached preview records remain usable when the archive cannot enrich display values.
        }

        displayRecords.append(contentsOf: enrichedBatch)
        pageIndex = batchEnd
    }

    func exportAllRecords() async {
        exportState = .loading

        do {
            let reportRequest = buildExportRequest()
            let exportURL = try await archiveStore.exportReport(reportRequest)
            exportState = .ready(exportURL)
        } catch {
            exportState = .failed(error.localizedDescription)
        }
    }

    private func loadNextArchiveDiffPage(
        _ archiveDiff: (fromObservationID: Int64, toObservationID: Int64, kind: HealthArchiveDiffKind)
    ) async {
        do {
            let records = try await archiveStore.diffRecords(HealthArchiveDiffRecordRequest(
                fromObservationID: archiveDiff.fromObservationID,
                toObservationID: archiveDiff.toObservationID,
                sampleTypeIdentifier: typeIdentifier,
                kind: archiveDiff.kind,
                afterCursor: archiveCursor,
                limit: pageSize
            ))
            let values = records.map { Self.healthRecordValue(from: $0) }
            displayRecords.append(contentsOf: values)

            if let last = records.last {
                archiveCursor = RecordCursor(
                    startDate: last.startDate,
                    strictFingerprint: last.strictFingerprint
                )
            }
            archiveHasMore = records.count == pageSize
            pageIndex += records.count
        } catch {
            archiveHasMore = false
        }
    }

    private static func healthRecordValue(from record: ArchivedHealthRecord) -> HealthRecordValue {
        HealthRecordValue(
            typeIdentifier: record.sampleTypeIdentifier,
            sampleUUIDHash: record.healthKitUUIDHash ?? record.strictFingerprint,
            recordFingerprint: record.strictFingerprint,
            startDate: record.startDate,
            endDate: record.endDate,
            displayValue: record.displayValue
        )
    }

    private func buildExportRequest() -> HealthArchiveReportRequest {
        switch mode {
        case .disappeared(let typeID):
            return HealthArchiveReportRequest(
                reportID: reportID,
                title: "Missing Records - \(displayName)",
                typeIdentifierFilter: typeID,
                disappearedOnly: true
            )
        case .disappearedDiff(let typeID, let fromObservationID, let toObservationID):
            return HealthArchiveReportRequest(
                reportID: reportID,
                title: "Missing Records - \(displayName)",
                typeIdentifierFilter: typeID,
                disappearedOnly: true,
                diffFromObservationID: fromObservationID,
                diffToObservationID: toObservationID,
                diffKind: .disappeared
            )
        case .added(let typeID, let afterDate, let beforeDate):
            return HealthArchiveReportRequest(
                reportID: reportID,
                title: "New Records - \(displayName)",
                typeIdentifierFilter: typeID,
                firstSeenAfter: afterDate,
                firstSeenBefore: beforeDate
            )
        case .addedDiff(let typeID, let afterDate, let beforeDate, let fromObservationID, let toObservationID):
            return HealthArchiveReportRequest(
                reportID: reportID,
                title: "New Records - \(displayName)",
                typeIdentifierFilter: typeID,
                firstSeenAfter: afterDate,
                firstSeenBefore: beforeDate,
                diffFromObservationID: fromObservationID,
                diffToObservationID: toObservationID,
                diffKind: .appeared
            )
        }
    }
}
