import Foundation
import HealthKit

// Interface updated 2026-05-24 — see AGENTS.md
protocol HealthArchiveStore {
    func beginObservation(observedAt: Date, triggerReason: String, selectedTypeSetHash: String?) async throws -> Int64
    func finishObservation(observationID: Int64, status: String, endedAt: Date) async throws
    func typeCaptureState(sampleTypeIdentifier: String) async throws -> HealthArchiveTypeCaptureState?
    func upsertTypeCaptureState(_ state: HealthArchiveTypeCaptureState, observationID: Int64) async throws
    func upsertSamples(_ samples: [HKSample], observedAt: Date) async throws -> HealthArchiveWriteSummary
    func upsertSamples(_ samples: [HKSample], observedAt: Date, observationID: Int64) async throws -> HealthArchiveWriteSummary
    func markVerification(sampleType: HKSampleType, verifiedAt: Date) async throws -> HealthArchiveFinalizationBreakdown
    func markVerification(sampleType: HKSampleType, verifiedAt: Date, observationID: Int64) async throws -> HealthArchiveFinalizationBreakdown
    func markUnchangedVerification(sampleType: HKSampleType, verifiedAt: Date, observationID: Int64) async throws -> HealthArchiveFinalizationBreakdown
    func recordDisappearance(sampleUUIDHash: String, sampleTypeIdentifier: String, observedMissingAt: Date) async throws
    func recordDisappearance(sampleUUIDHash: String, sampleTypeIdentifier: String, observedMissingAt: Date, observationID: Int64) async throws
    func recordDisappearances(sampleUUIDHashes: [String], sampleTypeIdentifier: String, observedMissingAt: Date, observationID: Int64) async throws -> Int
    func records(for request: HealthArchiveRecordRequest) async throws -> [ArchivedHealthRecord]
    func diffSummary(_ request: HealthArchiveDiffRequest) async throws -> HealthArchiveDiffSummary
    func diffRecords(_ request: HealthArchiveDiffRecordRequest) async throws -> [ArchivedHealthRecord]
    func aggregateComparison(_ request: HealthArchiveAggregateComparisonRequest) async throws -> [HealthArchiveAggregateComparisonRow]
    func sourceProvenanceBreakdown(_ request: HealthArchiveSourceProvenanceRequest) async throws -> [HealthArchiveSourceProvenanceRow]
    func consolidationEvidence(_ request: HealthArchiveConsolidationEvidenceRequest) async throws -> [HealthArchiveConsolidationEvidence]
    func exportPreview(_ request: HealthArchiveReportRequest) async throws -> HealthArchiveExportPreview
    func exportReport(_ request: HealthArchiveReportRequest) async throws -> URL
    func checkIntegrity() async throws -> HealthArchiveIntegrityReport
}

struct HealthArchiveWriteSummary: Equatable, Sendable {
    let insertedCount: Int
    let updatedCount: Int
    let unchangedCount: Int
}

struct HealthArchiveFinalizationBreakdown: Equatable, Sendable {
    var eventCountElapsedSeconds: TimeInterval = 0
    var typeSummaryElapsedSeconds: TimeInterval = 0
    var dailyAggregateElapsedSeconds: TimeInterval = 0
    var dailyAggregateBucketLookupElapsedSeconds: TimeInterval = 0
    var dailyAggregateCopyElapsedSeconds: TimeInterval = 0
    var dailyAggregateDeleteElapsedSeconds: TimeInterval = 0
    var dailyAggregateRebuildElapsedSeconds: TimeInterval = 0
    var dailyAggregateInsertElapsedSeconds: TimeInterval = 0
    var runUpdateElapsedSeconds: TimeInterval = 0
    var totalElapsedSeconds: TimeInterval = 0

    static let zero = HealthArchiveFinalizationBreakdown()

    mutating func addDailyAggregateBreakdown(_ breakdown: HealthArchiveFinalizationBreakdown) {
        dailyAggregateBucketLookupElapsedSeconds += breakdown.dailyAggregateBucketLookupElapsedSeconds
        dailyAggregateCopyElapsedSeconds += breakdown.dailyAggregateCopyElapsedSeconds
        dailyAggregateDeleteElapsedSeconds += breakdown.dailyAggregateDeleteElapsedSeconds
        dailyAggregateRebuildElapsedSeconds += breakdown.dailyAggregateRebuildElapsedSeconds
        dailyAggregateInsertElapsedSeconds += breakdown.dailyAggregateInsertElapsedSeconds
    }

    var dailyAggregateAccountedElapsedSeconds: TimeInterval {
        dailyAggregateBucketLookupElapsedSeconds
        + dailyAggregateCopyElapsedSeconds
        + dailyAggregateDeleteElapsedSeconds
        + dailyAggregateRebuildElapsedSeconds
        + dailyAggregateInsertElapsedSeconds
    }

    var dailyAggregateOtherElapsedSeconds: TimeInterval {
        max(0, dailyAggregateElapsedSeconds - dailyAggregateAccountedElapsedSeconds)
    }

    var accountedElapsedSeconds: TimeInterval {
        eventCountElapsedSeconds
        + typeSummaryElapsedSeconds
        + dailyAggregateElapsedSeconds
        + runUpdateElapsedSeconds
    }

    var otherElapsedSeconds: TimeInterval {
        max(0, totalElapsedSeconds - accountedElapsedSeconds)
    }
}

struct HealthArchiveTypeCaptureState: Equatable, Sendable {
    let sampleTypeIdentifier: String
    let observationID: Int64?
    let count: Int
    let contentHash: String
    let earliestDate: Date?
    let latestDate: Date?
    let yearlyCounts: [Int: Int]
    let anchorData: Data?

    init(
        sampleTypeIdentifier: String,
        observationID: Int64? = nil,
        count: Int,
        contentHash: String,
        earliestDate: Date?,
        latestDate: Date?,
        yearlyCounts: [Int: Int],
        anchorData: Data?
    ) {
        self.sampleTypeIdentifier = sampleTypeIdentifier
        self.observationID = observationID
        self.count = count
        self.contentHash = contentHash
        self.earliestDate = earliestDate
        self.latestDate = latestDate
        self.yearlyCounts = yearlyCounts
        self.anchorData = anchorData
    }
}

struct HealthArchiveIntegrityReport: Equatable, Sendable {
    let schemaVersion: Int?
    let sqliteIntegrityStatus: String
    let foreignKeyIssueCount: Int
    let requiredTableNames: Set<String>
    let missingTableNames: Set<String>

    var passed: Bool {
        schemaVersion == 2 &&
        sqliteIntegrityStatus == "ok" &&
        foreignKeyIssueCount == 0 &&
        missingTableNames.isEmpty
    }
}

struct HealthArchiveRecordRequest: Equatable, Sendable {
    let visibleAtObservationID: Int64?
    let sampleTypeIdentifier: String?
    let fingerprints: Set<String>
    let disappearedOnly: Bool
    let firstSeenAfter: Date?
    let firstSeenBefore: Date?
    let afterCursor: RecordCursor?
    let limit: Int?

    nonisolated init(
        visibleAtObservationID: Int64? = nil,
        sampleTypeIdentifier: String? = nil,
        fingerprints: Set<String> = [],
        disappearedOnly: Bool = false,
        firstSeenAfter: Date? = nil,
        firstSeenBefore: Date? = nil,
        afterCursor: RecordCursor? = nil,
        limit: Int? = nil
    ) {
        self.visibleAtObservationID = visibleAtObservationID
        self.sampleTypeIdentifier = sampleTypeIdentifier
        self.fingerprints = fingerprints
        self.disappearedOnly = disappearedOnly
        self.firstSeenAfter = firstSeenAfter
        self.firstSeenBefore = firstSeenBefore
        self.afterCursor = afterCursor
        self.limit = limit
    }
}

struct RecordCursor: Equatable, Sendable {
    let startDate: Date
    let strictFingerprint: String
}

enum HealthArchiveDiffKind: String, Codable, Sendable {
    case appeared
    case disappeared
    case representationChanged
}

struct HealthArchiveDiffRequest: Equatable, Sendable {
    let fromObservationID: Int64
    let toObservationID: Int64
    let sampleTypeIdentifier: String?

    init(fromObservationID: Int64, toObservationID: Int64, sampleTypeIdentifier: String? = nil) {
        self.fromObservationID = fromObservationID
        self.toObservationID = toObservationID
        self.sampleTypeIdentifier = sampleTypeIdentifier
    }
}

struct HealthArchiveDiffSummary: Equatable, Sendable {
    let fromObservationID: Int64
    let toObservationID: Int64
    let sampleTypeIdentifier: String?
    let appearedCount: Int
    let disappearedCount: Int
    let representationChangedCount: Int

    var totalChangeCount: Int {
        appearedCount + disappearedCount + representationChangedCount
    }
}

struct HealthArchiveDiffRecordRequest: Equatable, Sendable {
    let fromObservationID: Int64
    let toObservationID: Int64
    let sampleTypeIdentifier: String?
    let kind: HealthArchiveDiffKind
    let afterCursor: RecordCursor?
    let limit: Int?

    init(
        fromObservationID: Int64,
        toObservationID: Int64,
        sampleTypeIdentifier: String? = nil,
        kind: HealthArchiveDiffKind,
        afterCursor: RecordCursor? = nil,
        limit: Int? = nil
    ) {
        self.fromObservationID = fromObservationID
        self.toObservationID = toObservationID
        self.sampleTypeIdentifier = sampleTypeIdentifier
        self.kind = kind
        self.afterCursor = afterCursor
        self.limit = limit
    }
}

struct HealthArchiveAggregateComparisonRequest: Equatable, Sendable {
    let fromObservationID: Int64
    let toObservationID: Int64
    let sampleTypeIdentifier: String?
    let afterBucketStart: Date?
    let limit: Int?

    init(
        fromObservationID: Int64,
        toObservationID: Int64,
        sampleTypeIdentifier: String? = nil,
        afterBucketStart: Date? = nil,
        limit: Int? = nil
    ) {
        self.fromObservationID = fromObservationID
        self.toObservationID = toObservationID
        self.sampleTypeIdentifier = sampleTypeIdentifier
        self.afterBucketStart = afterBucketStart
        self.limit = limit
    }
}

struct HealthArchiveAggregateComparisonRow: Equatable, Sendable {
    let sampleTypeIdentifier: String
    let bucketStart: Date
    let bucketEnd: Date
    let fromVisibleRecordCount: Int
    let toVisibleRecordCount: Int
    let fromValueSum: Double?
    let toValueSum: Double?

    var visibleRecordDelta: Int {
        toVisibleRecordCount - fromVisibleRecordCount
    }

    var valueSumDelta: Double? {
        switch (fromValueSum, toValueSum) {
        case let (fromValueSum?, toValueSum?):
            return toValueSum - fromValueSum
        case let (nil, toValueSum?):
            return toValueSum
        case let (fromValueSum?, nil):
            return -fromValueSum
        case (nil, nil):
            return nil
        }
    }
}

struct HealthArchiveSourceProvenanceRequest: Equatable, Sendable {
    let visibleAtObservationID: Int64?
    let sampleTypeIdentifier: String?
    let limit: Int?
}

struct HealthArchiveSourceProvenanceRow: Equatable, Sendable {
    let sampleTypeIdentifier: String
    let sourceNameHash: String?
    let sourceBundleIdentifier: String?
    let sourceProductType: String?
    let sourceVersion: String?
    let sourceOperatingSystemVersion: String?
    let deviceModel: String?
    let visibleRecordCount: Int
    let valueSum: Double?
    let earliestStartDate: Date?
    let latestEndDate: Date?
}

struct HealthArchiveConsolidationEvidenceRequest: Equatable, Sendable {
    let fromObservationID: Int64
    let toObservationID: Int64
    let sampleTypeIdentifier: String?
}

struct HealthArchiveConsolidationEvidence: Equatable, Sendable {
    let sampleTypeIdentifier: String
    let fromObservationID: Int64
    let toObservationID: Int64
    let disappearedCount: Int
    let appearedCount: Int
    let representationChangedCount: Int
    let fromVisibleRecordCount: Int
    let toVisibleRecordCount: Int
    let fromValueSum: Double?
    let toValueSum: Double?
    let coverageOverlapSeconds: Double?
    let densityBefore: Double?
    let densityAfter: Double?
    let sourceCompatible: Bool
    let label: String
    let uncertaintyReason: String?
}

struct HealthArchiveReportRequest: Equatable, Sendable {
    let reportID: UUID
    let title: String
    let includedFingerprints: Set<String>
    let typeIdentifierFilter: String?
    let disappearedOnly: Bool
    let firstSeenAfter: Date?
    let firstSeenBefore: Date?
    let diffFromObservationID: Int64?
    let diffToObservationID: Int64?
    let diffKind: HealthArchiveDiffKind?

    init(
        reportID: UUID,
        title: String,
        includedFingerprints: Set<String> = [],
        typeIdentifierFilter: String? = nil,
        disappearedOnly: Bool = false,
        firstSeenAfter: Date? = nil,
        firstSeenBefore: Date? = nil,
        diffFromObservationID: Int64? = nil,
        diffToObservationID: Int64? = nil,
        diffKind: HealthArchiveDiffKind? = nil
    ) {
        self.reportID = reportID
        self.title = title
        self.includedFingerprints = includedFingerprints
        self.typeIdentifierFilter = typeIdentifierFilter
        self.disappearedOnly = disappearedOnly
        self.firstSeenAfter = firstSeenAfter
        self.firstSeenBefore = firstSeenBefore
        self.diffFromObservationID = diffFromObservationID
        self.diffToObservationID = diffToObservationID
        self.diffKind = diffKind
    }
}

struct HealthArchiveExportPreview: Equatable, Sendable {
    let reportID: UUID
    let title: String
    let exportKind: String
    let estimatedRecordCount: Int
    let typeIdentifierFilter: String?
    let disappearedOnly: Bool
    let firstSeenAfter: Date?
    let firstSeenBefore: Date?
    let diffFromObservationID: Int64?
    let diffToObservationID: Int64?
    let diffKind: HealthArchiveDiffKind?
}

struct HealthArchiveExportManifest: Equatable, Sendable {
    let exportID: UUID
    let exportKind: String
    let createdAt: Date
    let recordCount: Int
    let manifestHash: String
}

struct ArchivedHealthRecord: Identifiable, Equatable, Sendable, Encodable {
    let id: String
    let sampleTypeIdentifier: String
    let strictFingerprint: String
    let semanticFingerprint: String?
    let healthKitUUIDHash: String?
    let startDate: Date
    let endDate: Date
    let firstSeenAt: Date
    let lastSeenAt: Date?
    let lastVerifiedAt: Date?
    let disappearedAt: Date?

    // Value fields
    let valueKind: String?  // "quantity", "category", "workout", nil
    let value: Double?
    let unit: String?
    let categoryValue: Int?
    let workoutActivityType: Int?
    let durationSeconds: Double?

    // Source/device metadata
    let sourceName: String?
    let sourceBundleIdentifier: String?
    let deviceName: String?

    // Display helper
    nonisolated var displayValue: String? {
        if let value, let unit {
            return "\(String(format: "%.1f", value)) \(unit)"
        }
        if let categoryValue {
            return "Category: \(categoryValue)"
        }
        if let durationSeconds {
            return String(format: "%.1f min", durationSeconds / 60.0)
        }
        return nil
    }
}
