import Foundation
import SwiftData

@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

    private let healthKit = HealthKitService.shared
    private let diffService = SnapshotDiffService.shared
    private var pendingPartialSnapshot: HealthSnapshot?

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

    func createSnapshot(
        context: ModelContext,
        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
        }

        isCreatingSnapshot = true
        snapshotError = nil
        snapshotProgress = .fetching
        showProgressSheet = true
        fetchStartDate = Date()
        fetchDurationSeconds = nil
        operationID = UUID()
        completedSnapshotID = nil
        completedSnapshotTimestamp = nil
        completedSnapshotDeviceID = nil
        completedSnapshotTriggerReason = nil
        completedSnapshotRetryOfSnapshotID = nil
        pendingPartialSnapshot = nil
        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 operationTimeout = max(120, Double(selectedTypeIDs.count) * HealthKitService.maximumTimeoutSeconds + 30)
            let snapshot = try await withTimeout(seconds: operationTimeout) {
                try await self.healthKit.createSnapshot(
                    in: context,
                    selectedTypeIDs: selectedTypeIDs,
                    adaptiveTimeoutsEnabled: adaptiveTimeoutsEnabled,
                    triggerReason: triggerReason,
                    retryOfSnapshotID: retryOfSnapshotID,
                    timeoutMultiplier: timeoutMultiplier,
                    progress: self.fetchProgress
                )
            }

            fetchDurationSeconds = fetchStartDate.map { Date().timeIntervalSince($0) }
            completedSnapshotID = snapshot.id
            completedSnapshotTimestamp = snapshot.timestamp
            completedSnapshotDeviceID = snapshot.deviceID
            completedSnapshotTriggerReason = snapshot.triggerReason
            completedSnapshotRetryOfSnapshotID = snapshot.retryOfSnapshotID
            fetchProgress?.updateChainContext(
                previousSnapshotID: snapshot.previousSnapshotID,
                isChainStart: snapshot.isChainStart,
                snapshotChecksum: HashService.snapshotChecksum(typeCounts: snapshot.typeCounts ?? []),
                monitoredTypeSetHash: snapshot.monitoredTypeSetHash,
                monitoredRegistryVersion: snapshot.monitoredRegistryVersion
            )

            if snapshot.snapshotQuality != SnapshotQuality.complete {
                pendingPartialSnapshot = snapshot

                let typeCounts = snapshot.typeCounts ?? []
                let unauthorizedCount = typeCounts.filter { $0.quality == SnapshotQuality.unauthorized }.count
                let failedCount = typeCounts.filter { $0.quality == SnapshotQuality.failed }.count

                let failedTypes = typeCounts.filter { $0.quality == SnapshotQuality.failed }
                let unauthorizedTypes = typeCounts.filter { $0.quality == SnapshotQuality.unauthorized }

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

                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: \(snapshot.snapshotQuality.rawValue). Some data may be incomplete."
                    canRetryWithPermissions = false
                }

                showProgressSheet = true
                return
            }

            let allSnapshots = try context.fetch(FetchDescriptor<HealthSnapshot>())
            let exists = allSnapshots.contains { $0.id == snapshot.id }

            if !exists {
                throw SnapshotCreationError.snapshotNotSaved
            }

            snapshotProgress = .complete
        } 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 let error as SnapshotCreationError {
            snapshotError = error.message
            snapshotProgress = .idle
            showProgressSheet = true
        } catch {
            snapshotError = "Failed to create snapshot: \(error.localizedDescription)"
            snapshotProgress = .idle
            showProgressSheet = true
        }
    }

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

    func retryFailedMetricsWithExtendedTimeout(
        context: ModelContext,
        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(
            context: context,
            selectedTypeIDs: selectedTypeIDs,
            adaptiveTimeoutsEnabled: adaptiveTimeoutsEnabled,
            triggerReason: "retryFailedMetrics",
            timeoutMultiplier: 2
        )
    }

    func savePartialSnapshot(context: ModelContext) async {
        guard let snapshot = pendingPartialSnapshot else {
            fetchProgress = nil
            showProgressSheet = false
            snapshotProgress = .idle
            return
        }

        do {
            let saved = try await healthKit.savePartialSnapshot(snapshot, in: context)
            completedSnapshotID = saved.id
            pendingPartialSnapshot = nil
            fetchProgress?.updateChainContext(
                previousSnapshotID: saved.previousSnapshotID,
                isChainStart: saved.isChainStart,
                snapshotChecksum: HashService.snapshotChecksum(typeCounts: saved.typeCounts ?? []),
                monitoredTypeSetHash: saved.monitoredTypeSetHash,
                monitoredRegistryVersion: saved.monitoredRegistryVersion
            )
        } catch {
            snapshotError = "Failed to save partial snapshot: \(error.localizedDescription)"
            showProgressSheet = true
            return
        }

        fetchProgress = nil
        showProgressSheet = false
        snapshotProgress = .idle
    }

    func discardSnapshot(context: ModelContext) async {
        pendingPartialSnapshot = nil
        if let snapshotID = completedSnapshotID {
            do {
                let allSnapshots = try context.fetch(FetchDescriptor<HealthSnapshot>())
                if let snapshot = allSnapshots.first(where: { $0.id == snapshotID }) {
                    context.delete(snapshot)
                }
            } catch { }
        }
        completedSnapshotID = nil
        fetchProgress = nil
        showProgressSheet = false
        snapshotProgress = .idle
    }

    func totalChanges(latest: HealthSnapshot, previous: HealthSnapshot) -> Int {
        diffService.totalAbsoluteChange(current: latest, baseline: previous)
    }

    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
        }
    }
}

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

    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"
        }
    }

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

enum SnapshotCreationError: Error {
    case snapshotNotSaved

    var message: String {
        switch self {
        case .snapshotNotSaved:
            return "Snapshot was not saved to database. This may indicate a HealthKit permission issue or data corruption. Try requesting health access again in the Actions section."
        }
    }
}
