import CryptoKit
import Foundation

enum HashService {
    struct TypeHashBuilder {
        private var hasher = SHA256()

        init(typeIdentifier: String) {
            hasher.update(data: Data(typeIdentifier.utf8))
        }

        mutating func append(recordFingerprint: String) {
            hasher.update(data: Data("|".utf8))
            hasher.update(data: Data(recordFingerprint.utf8))
        }

        mutating func finalize() -> String {
            let digest = hasher.finalize()
            return digest.map { String(format: "%02x", $0) }.joined()
        }
    }

    nonisolated(unsafe) private static let iso8601Formatter: ISO8601DateFormatter = {
        let f = ISO8601DateFormatter()
        f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        return f
    }()

    nonisolated static func archiveContentHash(domain: String, parts: [String?]) -> String {
        let input = ([domain] + parts.map { $0 ?? "null" }).joined(separator: "\u{1f}")
        let digest = SHA256.hash(data: Data(input.utf8))
        return digest.map { String(format: "%02x", $0) }.joined()
    }

    // SHA256 of "typeIdentifier|totalCount|earliestDateISO|latestDateISO"
    // ⚠️ MVP limitation: hash covers only count + date range, not value distribution.
    // silentReplacement detection based on this hash is best-effort only —
    // it will miss replacements that preserve total count and date boundaries.
    nonisolated static func typeHash(
        typeIdentifier: String,
        totalCount: Int,
        earliestDate: Date?,
        latestDate: Date?
    ) -> String {
        let earliest = earliestDate.map { iso8601Formatter.string(from: $0) } ?? ""
        let latest   = latestDate.map   { iso8601Formatter.string(from: $0) } ?? ""
        let input = "\(typeIdentifier)|\(totalCount)|\(earliest)|\(latest)"
        let digest = SHA256.hash(data: Data(input.utf8))
        return digest.map { String(format: "%02x", $0) }.joined()
    }

    nonisolated static func typeHash(typeIdentifier: String, recordFingerprints: [String]) -> String {
        var hasher = SHA256()
        hasher.update(data: Data(typeIdentifier.utf8))
        for fingerprint in recordFingerprints.sorted() {
            hasher.update(data: Data("|".utf8))
            hasher.update(data: Data(fingerprint.utf8))
        }
        let digest = hasher.finalize()
        return digest.map { String(format: "%02x", $0) }.joined()
    }

    nonisolated static func typeHashInRecordedOrder(typeIdentifier: String, recordFingerprints: [String]) -> String {
        var builder = TypeHashBuilder(typeIdentifier: typeIdentifier)
        for fingerprint in recordFingerprints {
            builder.append(recordFingerprint: fingerprint)
        }
        return builder.finalize()
    }

    nonisolated static func sampleFingerprint(
        typeIdentifier: String,
        sampleUUID: String,
        startDate: Date,
        endDate: Date
    ) -> String {
        let input = [
            typeIdentifier,
            sampleUUID,
            iso8601Formatter.string(from: startDate),
            iso8601Formatter.string(from: endDate)
        ].joined(separator: "|")
        let digest = SHA256.hash(data: Data(input.utf8))
        return digest.map { String(format: "%02x", $0) }.joined()
    }

    nonisolated static func sampleUUIDHash(_ sampleUUID: String) -> String {
        let digest = SHA256.hash(data: Data(sampleUUID.utf8))
        return digest.map { String(format: "%02x", $0) }.joined()
    }

    nonisolated static func archiveSemanticFingerprint(
        typeIdentifier: String,
        startDate: Date,
        endDate: Date,
        value: Double?,
        unit: String?,
        categoryValue: Int?,
        workoutActivityType: UInt?,
        sourceBundleIdentifier: String?
    ) -> String {
        let input = [
            typeIdentifier,
            iso8601Formatter.string(from: startDate),
            iso8601Formatter.string(from: endDate),
            value.map { String(format: "%.12g", $0) } ?? "",
            unit ?? "",
            categoryValue.map(String.init) ?? "",
            workoutActivityType.map(String.init) ?? "",
            sourceBundleIdentifier ?? ""
        ].joined(separator: "|")
        let digest = SHA256.hash(data: Data(input.utf8))
        return digest.map { String(format: "%02x", $0) }.joined()
    }

    // Per-snapshot: sort TypeCounts by typeIdentifier, SHA256 of concatenated type hashes.
    // Filter criterion: quality == .complete; do not use contentHash != "" as a proxy.
    // A TypeCount with quality = .failed but contentHash = "nonEmpty" must be excluded.
    static func snapshotChecksum(typeCounts: [TypeCount]) -> String {
        let completeHashes = typeCounts
            .filter { $0.quality == .complete }
            .sorted { $0.typeIdentifier < $1.typeIdentifier }
            .map { $0.contentHash }
            .joined()
        let digest = SHA256.hash(data: Data(completeHashes.utf8))
        return digest.map { String(format: "%02x", $0) }.joined()
    }

    // SHA256 of sorted active typeIdentifier strings.
    // ⚠️ Covers the FULL intended registry (selectedTypeIDs), including types that may have
    // failed, timed out, or been unauthorized — never filter down to only the successfully-
    // fetched subset. A query failure must not silently change the registry hash.
    nonisolated static func typeSetHash(typeIDs: [String]) -> String {
        let sorted = typeIDs.sorted().joined(separator: "|")
        let digest = SHA256.hash(data: Data(sorted.utf8))
        return digest.map { String(format: "%02x", $0) }.joined()
    }
}
