import Foundation

@Observable
final class DashboardViewModel {
    var isRequestingAuth = false
    var isCreatingSnapshot = false
    var authError: String?
    var snapshotError: String?
    var snapshotProgress: SnapshotProgress = .idle
    var snapshotProgressMessage: String = ""
    var snapshotProgressDetail: String = ""
    var showProgressSheet = false
    var canRetryWithPermissions = false
    var permissionsAlreadyRequested = false
    var fetchProgress: SnapshotFetchProgress?
    var fetchDurationSeconds: TimeInterval? = nil
    var fetchStartDate: Date? = nil
    var operationID: UUID? = nil
    var completedSnapshotID: UUID? = nil
    var completedSnapshotTimestamp: Date? = nil
    var completedSnapshotDeviceID: String? = nil
    var completedSnapshotTriggerReason: String? = nil
    var completedSnapshotRetryOfSnapshotID: UUID? = nil
    var ambiguousDisappearedMetrics: [AmbiguousDisappearedMetric] = []
    var latestArchiveObservation: CachedArchiveObservationRow?
    var archiveObservationRows: [CachedArchiveObservationRow] = []
    var archiveHealthStatus: CachedArchiveHealthStatus?
    var archiveCacheError: String?

    private let healthKit = HealthKitService.shared
    private var pendingPartialSnapshotID: UUID?
    private var pendingAmbiguousSnapshotID: UUID?

    private struct ArchiveCacheRefreshResult: Sendable {
        let observationRows: [CachedArchiveObservationRow]
        let healthStatus: CachedArchiveHealthStatus?
    }

    func requestAuthorization() async {
        isRequestingAuth = true
        authError = nil
        defer { isRequestingAuth = false }
        do {
            try await healthKit.requestAuthorization()
        } catch {
            authError = error.localizedDescription
        }
    }

    func createSnapshot(
        selectedTypeIDs: Set<String>,
        adaptiveTimeoutsEnabled: Bool,
        triggerReason: String = "manual",
        retryOfSnapshotID: UUID? = nil,
        timeoutMultiplier: Double = 1
    ) async {
        guard !selectedTypeIDs.isEmpty else {
            snapshotError = "No health data types selected. Please select types to monitor in Settings."
            return
        }

        [pendingPartialSnapshotID, pendingAmbiguousSnapshotID, completedSnapshotID]
            .compactMap { $0 }
            .forEach(LegacySwiftDataBridge.discardPendingSnapshot(id:))

        isCreatingSnapshot = true
        snapshotError = nil
        snapshotProgress = .fetching
        showProgressSheet = true
        fetchStartDate = Date()
        fetchDurationSeconds = nil
        operationID = UUID()
        completedSnapshotID = nil
        completedSnapshotTimestamp = nil
        completedSnapshotDeviceID = nil
        completedSnapshotTriggerReason = nil
        completedSnapshotRetryOfSnapshotID = nil
        pendingPartialSnapshotID = nil
        pendingAmbiguousSnapshotID = nil
        ambiguousDisappearedMetrics = []
        snapshotProgressMessage = ""
        snapshotProgressDetail = ""
        canRetryWithPermissions = false
        permissionsAlreadyRequested = false

        let monitoredTypes = HealthKitService.allTypes
            .filter { selectedTypeIDs.contains($0.id) }
            .sortedByFetchDisplayNameDescending()
            .map { (id: $0.id, displayName: $0.displayName) }
        fetchProgress = SnapshotFetchProgress(monitoredTypes: monitoredTypes)
        fetchProgress?.updateConfiguration(
            perTypeTimeoutSeconds: HealthKitService.defaultInitialTimeoutSeconds,
            maxConcurrentTypeFetches: HealthKitService.maxConcurrentTypeFetches,
            adaptiveTimeoutsEnabled: adaptiveTimeoutsEnabled
        )

        defer { isCreatingSnapshot = false }

        do {
            let learnedMetricTimeout = Double(selectedTypeIDs.count) * HealthKitService.maximumTimeoutSeconds + 30
            let operationTimeout = max(
                HealthKitService.captureOperationWatchdogTimeoutSeconds,
                learnedMetricTimeout
            )
            let capture = try await withTimeout(seconds: operationTimeout) {
                try await LegacySwiftDataBridge.createSnapshot(
                    using: self.healthKit,
                    selectedTypeIDs: selectedTypeIDs,
                    adaptiveTimeoutsEnabled: adaptiveTimeoutsEnabled,
                    triggerReason: triggerReason,
                    retryOfSnapshotID: retryOfSnapshotID,
                    timeoutMultiplier: timeoutMultiplier,
                    reviewAmbiguousCompleteDisappearedTypes: triggerReason == "manual",
                    progress: self.fetchProgress
                )
            }

            fetchDurationSeconds = fetchStartDate.map { Date().timeIntervalSince($0) }
            applyOutcome(capture.outcome)
            fetchProgress?.updateChainContext(
                previousSnapshotID: capture.outcome.previousSnapshotID,
                isChainStart: capture.outcome.isChainStart,
                snapshotChecksum: capture.outcome.snapshotChecksum,
                monitoredTypeSetHash: capture.outcome.monitoredTypeSetHash,
                monitoredRegistryVersion: capture.outcome.monitoredRegistryVersion
            )

            reflectUnavailableMetricsInProgress(typeSummaries: capture.typeSummaries)

            if capture.snapshotQuality != .complete {
                pendingPartialSnapshotID = capture.outcome.snapshotID

                let unauthorizedCount = capture.typeSummaries.filter { $0.quality == .unauthorized }.count
                let failedCount = capture.typeSummaries.filter { $0.quality == .failed }.count

                let failedTypes = capture.typeSummaries.filter { $0.quality == .failed }
                let unauthorizedTypes = capture.typeSummaries.filter { $0.quality == .unauthorized }

                snapshotProgress = .incomplete
                snapshotProgressMessage = "Incomplete snapshot"
                permissionsAlreadyRequested = healthKit.hasRequestedPermissionsBefore

                if capture.shouldAutoSaveKnownUnauthorizedPartial {
                    snapshotProgressMessage = "Known unavailable metrics"
                    snapshotProgressDetail = "Snapshot was auto-saved as partial because unavailable metrics were already confirmed in the previous snapshot."
                    await savePartialSnapshot(keepSheetOpenForReview: true)
                    return
                }

                if unauthorizedCount > 0 {
                    snapshotProgressMessage = "Missing permissions (\(unauthorizedCount) type\(unauthorizedCount == 1 ? "" : "s"))"
                    let typesList = unauthorizedTypes.map { $0.displayName }.joined(separator: ", ")

                    if permissionsAlreadyRequested {
                        snapshotProgressDetail = """
                        Not authorized: \(typesList)

                        You need to grant access to health data. Open Settings and enable HealthKit access for the types you want to monitor.
                        """
                        canRetryWithPermissions = false
                    } else {
                        snapshotProgressDetail = "Some health data types are not authorized. You can request permissions and try again."
                        canRetryWithPermissions = true
                    }
                } else if failedCount > 0 {
                    snapshotProgressMessage = "Data load failed (\(failedCount) type\(failedCount == 1 ? "" : "s"))"
                    let typesList = failedTypes.map { $0.displayName }.joined(separator: ", ")
                    snapshotProgressDetail = """
                    Failed to load: \(typesList)

                    This may indicate a temporary issue. Try again or check HealthKit availability.
                    """
                    canRetryWithPermissions = false
                } else {
                    snapshotProgressDetail = "Snapshot created with quality: \(capture.snapshotQuality.rawValue). Some data may be incomplete."
                    canRetryWithPermissions = false
                }

                showProgressSheet = true
                return
            }

            if !capture.ambiguousDisappearedMetrics.isEmpty {
                pendingAmbiguousSnapshotID = capture.outcome.snapshotID
                ambiguousDisappearedMetrics = capture.ambiguousDisappearedMetrics
                snapshotProgress = .requiresResolution
                snapshotProgressMessage = "Metric access needs review"
                snapshotProgressDetail = "One or more metrics returned zero samples after previously having data. Choose how HealthProbe should classify this before saving the snapshot."
                showProgressSheet = true
                return
            }

            snapshotProgress = .complete
            await refreshArchiveCache()
        } catch is CancellationError {
            snapshotError = "Snapshot creation exceeded the operation timeout. Individual metric timeouts are adaptive; retry failed metrics with an extended timeout when available."
            snapshotProgress = .idle
            showProgressSheet = true
        } catch {
            snapshotError = "Failed to create snapshot: \(error.localizedDescription)"
            snapshotProgress = .idle
            showProgressSheet = true
        }
    }

    func treatAmbiguousMetricsAsUnauthorized() async {
        let ambiguousIDs = Set(ambiguousDisappearedMetrics.map(\.id))
        guard let snapshotID = pendingAmbiguousSnapshotID else { return }

        do {
            let outcome = try await LegacySwiftDataBridge.markMetricsUnauthorized(
                snapshotID: snapshotID,
                typeIdentifiers: ambiguousIDs,
                using: healthKit
            )
            await finishSavedReviewedSnapshot(outcome)
        } catch {
            snapshotError = "Failed to save reviewed snapshot: \(error.localizedDescription)"
            showProgressSheet = true
        }
    }

    func treatAmbiguousMetricsAsDeleted() async {
        guard let snapshotID = pendingAmbiguousSnapshotID else { return }

        do {
            let outcome = try await LegacySwiftDataBridge.saveReviewedCompleteSnapshot(id: snapshotID, using: healthKit)
            await finishSavedReviewedSnapshot(outcome)
        } catch {
            snapshotError = "Failed to save reviewed snapshot: \(error.localizedDescription)"
            showProgressSheet = true
        }
    }

    func retryWithPermissions(selectedTypeIDs: Set<String>, adaptiveTimeoutsEnabled: Bool) async {
        isRequestingAuth = true
        defer { isRequestingAuth = false }
        do {
            try await healthKit.requestAuthorization()
            await createSnapshot(selectedTypeIDs: selectedTypeIDs, adaptiveTimeoutsEnabled: adaptiveTimeoutsEnabled)
        } catch {
            snapshotError = "Failed to request permissions: \(error.localizedDescription)"
            snapshotProgress = .idle
            showProgressSheet = false
        }
    }

    func retryFailedMetricsWithExtendedTimeout(
        selectedTypeIDs: Set<String>,
        adaptiveTimeoutsEnabled: Bool
    ) async {
        guard let progress = fetchProgress else { return }
        let hasTimeout = progress.types.contains { type in
            guard case .failed(let reason) = type.status else { return false }
            return reason == "Timeout"
        }
        guard hasTimeout else { return }

        await createSnapshot(
            selectedTypeIDs: selectedTypeIDs,
            adaptiveTimeoutsEnabled: adaptiveTimeoutsEnabled,
            triggerReason: "retryFailedMetrics",
            timeoutMultiplier: 2
        )
    }

    func savePartialSnapshot(keepSheetOpenForReview: Bool = false) async {
        guard let snapshotID = pendingPartialSnapshotID else {
            if !keepSheetOpenForReview {
                fetchProgress = nil
                showProgressSheet = false
                snapshotProgress = .idle
            }
            return
        }

        do {
            let outcome = try await LegacySwiftDataBridge.savePartialSnapshot(id: snapshotID, using: healthKit)
            applyOutcome(outcome)
            pendingPartialSnapshotID = nil
            fetchProgress?.updateChainContext(
                previousSnapshotID: outcome.previousSnapshotID,
                isChainStart: outcome.isChainStart,
                snapshotChecksum: outcome.snapshotChecksum,
                monitoredTypeSetHash: outcome.monitoredTypeSetHash,
                monitoredRegistryVersion: outcome.monitoredRegistryVersion
            )
            await refreshArchiveCache()
        } catch {
            snapshotError = "Failed to save partial snapshot: \(error.localizedDescription)"
            showProgressSheet = true
            return
        }

        if keepSheetOpenForReview {
            snapshotProgress = .incomplete
            showProgressSheet = true
            return
        }

        fetchProgress = nil
        showProgressSheet = false
        snapshotProgress = .idle
    }

    func discardSnapshot() async {
        pendingPartialSnapshotID = nil
        pendingAmbiguousSnapshotID = nil
        ambiguousDisappearedMetrics = []
        if let snapshotID = completedSnapshotID {
            do {
                try LegacySwiftDataBridge.deleteSnapshot(id: snapshotID)
            } catch { }
        }
        completedSnapshotID = nil
        fetchProgress = nil
        showProgressSheet = false
        snapshotProgress = .idle
    }

    func loadArchiveCacheStatus() {
        do {
            let cache = try CoreDataArchiveCacheStore()
            archiveObservationRows = try cache.observationRows(limit: 2)
            latestArchiveObservation = archiveObservationRows.first
            archiveHealthStatus = try cache.latestArchiveHealthStatus()
            archiveCacheError = nil
        } catch {
            archiveObservationRows = []
            latestArchiveObservation = nil
            archiveHealthStatus = nil
            archiveCacheError = error.localizedDescription
        }
    }

    private func withTimeout<T>(seconds: TimeInterval, operation: @escaping () async throws -> T) async throws -> T {
        try await withThrowingTaskGroup(of: T.self) { group in
            group.addTask { try await operation() }
            group.addTask {
                try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
                throw CancellationError()
            }
            let result = try await group.next()!
            group.cancelAll()
            return result
        }
    }

    private func reflectUnavailableMetricsInProgress(typeSummaries: [LegacySnapshotTypeSummary]) {
        guard let fetchProgress else { return }
        for typeSummary in typeSummaries where typeSummary.quality == .unauthorized {
            fetchProgress.markUnavailable(typeSummary.typeIdentifier)
        }
    }

    private func finishSavedReviewedSnapshot(_ outcome: LegacySnapshotOutcome) async {
        applyOutcome(outcome)
        pendingAmbiguousSnapshotID = nil
        pendingPartialSnapshotID = nil
        ambiguousDisappearedMetrics = []
        fetchProgress?.updateChainContext(
            previousSnapshotID: outcome.previousSnapshotID,
            isChainStart: outcome.isChainStart,
            snapshotChecksum: outcome.snapshotChecksum,
            monitoredTypeSetHash: outcome.monitoredTypeSetHash,
            monitoredRegistryVersion: outcome.monitoredRegistryVersion
        )
        fetchProgress = nil
        showProgressSheet = false
        snapshotProgress = .idle
        await refreshArchiveCache()
    }

    private func applyOutcome(_ outcome: LegacySnapshotOutcome) {
        completedSnapshotID = outcome.snapshotID
        completedSnapshotTimestamp = outcome.timestamp
        completedSnapshotDeviceID = outcome.deviceID
        completedSnapshotTriggerReason = outcome.triggerReason
        completedSnapshotRetryOfSnapshotID = outcome.retryOfSnapshotID
    }

    private func refreshArchiveCache() async {
        do {
            let result = try await Task.detached(priority: .utility) {
                let cache = try CoreDataArchiveCacheStore()
                _ = try cache.rebuild(fromArchiveAt: SQLiteHealthArchiveStore.defaultDatabaseURL)
                let rows = try cache.observationRows(limit: 2)
                let healthStatus = try cache.latestArchiveHealthStatus()
                return ArchiveCacheRefreshResult(
                    observationRows: rows,
                    healthStatus: healthStatus
                )
            }.value
            archiveObservationRows = result.observationRows
            latestArchiveObservation = result.observationRows.first
            archiveHealthStatus = result.healthStatus
            archiveCacheError = nil
        } catch {
            archiveObservationRows = []
            latestArchiveObservation = nil
            archiveCacheError = error.localizedDescription
        }
    }
}

enum SnapshotProgress {
    case idle
    case fetching
    case processing
    case complete
    case incomplete
    case requiresResolution

    var message: String {
        switch self {
        case .idle: return ""
        case .fetching: return "Fetching health data..."
        case .processing: return "Processing snapshot..."
        case .complete: return "Snapshot created successfully!"
        case .incomplete: return "Snapshot created with issues"
        case .requiresResolution: return "Snapshot needs review"
        }
    }

    var isError: Bool {
        switch self {
        case .incomplete, .requiresResolution: return true
        default: return false
        }
    }
}
