import SwiftUI
import SwiftData

struct RecordChangeEvolutionChart: View {
    let snapshots: [HealthSnapshot]
    let currentSnapshotID: UUID
    let typeIdentifier: String
    let displayName: String

    @Query private var allDeltas: [SnapshotDelta]
    @State private var cachedCountsByObservationID: [Int64: Int] = [:]
    @State private var cachedDiffsByPair: [ArchiveDiffKey: RecordChangeDiff]?

    private var sortedSnapshots: [HealthSnapshot] {
        snapshots.sorted(by: HealthSnapshot.timelineSort)
    }

    private var currentIndex: Int? {
        sortedSnapshots.firstIndex { $0.id == currentSnapshotID }
    }

    private var contextSnapshots: [HealthSnapshot] {
        guard let idx = currentIndex else { return [] }
        let start = max(0, idx - 3)
        let end = min(sortedSnapshots.count, idx + 4)
        return Array(sortedSnapshots[start..<end])
    }

    private var diffTaskID: String {
        (
            [typeIdentifier, currentSnapshotID.uuidString]
            + contextSnapshots.map { "\($0.id.uuidString):\($0.archiveObservationID ?? -1)" }
        )
        .joined(separator: "|")
    }

    private var maxCount: Int {
        let counts = contextSnapshots.map(countForSnapshot)
        return counts.max() ?? 1
    }

    private var chartPoints: [ChartPoint] {
        contextSnapshots.map { snapshot in
            let previousSnapshot = snapshot.previousInTimeline(sortedSnapshots)
            return ChartPoint(
                snapshot: snapshot,
                count: max(countForSnapshot(snapshot), 0),
                diff: recordDiff(current: snapshot, previous: previousSnapshot)
            )
        }
    }

    private var maxChangeCount: Int {
        let maxChange = chartPoints
            .map { max($0.diff.added, $0.diff.disappeared) }
            .max() ?? 0
        return max(maxChange, 1)
    }

    private var hasAnyRecordChanges: Bool {
        chartPoints.contains { $0.diff.added > 0 || $0.diff.disappeared > 0 }
    }

    private func recordDiff(current: HealthSnapshot, previous: HealthSnapshot?) -> RecordChangeDiff {
        guard let previous = previous else { return RecordChangeDiff() }

        if let currentObservationID = current.archiveObservationID,
           let previousObservationID = previous.archiveObservationID,
           let cachedDiffsByPair {
            let key = ArchiveDiffKey(fromObservationID: previousObservationID, toObservationID: currentObservationID)
            return cachedDiffsByPair[key] ?? RecordChangeDiff(isExact: true)
        }

        if let typeCount = current.typeCounts?.first(where: { $0.typeIdentifier == typeIdentifier }),
           let cache = typeCount.detailCache,
           cache.matchesBaseline(previous.id) {
            return RecordChangeDiff(
                added: cache.addedCount,
                disappeared: cache.disappearedCount,
                isExact: true
            )
        }

        guard let delta = allDeltas.first(where: {
            $0.fromSnapshotID == previous.id &&
            $0.toSnapshotID == current.id
        }),
        let typeDelta = delta.typeDeltas?.first(where: { $0.typeIdentifier == typeIdentifier }) else {
            return RecordChangeDiff()
        }

        switch typeDelta.transition {
        case .unchanged:
            return RecordChangeDiff()
        case .changed:
            if typeDelta.countDelta > 0 {
                return RecordChangeDiff(added: typeDelta.countDelta)
            }
            if typeDelta.countDelta < 0 {
                return RecordChangeDiff(disappeared: abs(typeDelta.countDelta))
            }
            return RecordChangeDiff()
        case .appeared:
            return RecordChangeDiff(added: max(typeDelta.countDelta, 1))
        case .disappeared:
            return RecordChangeDiff(disappeared: max(abs(typeDelta.countDelta), 1))
        }
    }

    var body: some View {
        if contextSnapshots.count < 2 {
            emptyState
        } else {
            evolutionView
                .task(id: diffTaskID) {
                    await loadArchiveEvolutionCache()
                }
        }
    }

    private var emptyState: some View {
        VStack(spacing: 8) {
            Image(systemName: "chart.line.uptrend.xyaxis")
                .font(.system(size: 24, weight: .semibold))
                .foregroundStyle(.secondary)

            Text("Not enough snapshots")
                .font(.caption)
                .foregroundStyle(.secondary)
        }
        .frame(maxWidth: .infinity)
        .padding(24)
        .background(Color(.systemBackground).opacity(0.5), in: RoundedRectangle(cornerRadius: 8))
    }

    private var evolutionView: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                Text("Evolution")
                    .font(.headline.weight(.semibold))
                    .foregroundStyle(.primary)

                Spacer()

                Text("\(contextSnapshots.count) snapshots")
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            evolutionChart

            if hasAnyRecordChanges {
                changeLegend
            }

            HStack(spacing: 12) {
                VStack(alignment: .leading, spacing: 4) {
                    Text("Min")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                    Text("\(contextSnapshots.map(countForSnapshot).min() ?? 0)")
                        .font(.caption.weight(.semibold))
                        .monospacedDigit()
                }

                Spacer()

                VStack(alignment: .center, spacing: 4) {
                    Text("Current")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                    if let current = contextSnapshots.first(where: { $0.id == currentSnapshotID }) {
                        Text("\(countForSnapshot(current))")
                            .font(.caption.weight(.semibold))
                            .monospacedDigit()
                            .foregroundStyle(Color.accentColor)
                    }
                }

                Spacer()

                VStack(alignment: .trailing, spacing: 4) {
                    Text("Max")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                    Text("\(maxCount)")
                        .font(.caption.weight(.semibold))
                        .monospacedDigit()
                }
            }
            .padding(8)
            .background(Color(.systemBackground).opacity(0.3), in: RoundedRectangle(cornerRadius: 6))
        }
        .padding(12)
        .background(Color(.systemBackground).opacity(0.5), in: RoundedRectangle(cornerRadius: 8))
    }

    @ViewBuilder
    private var evolutionChart: some View {
        HStack(alignment: .bottom, spacing: 4) {
            ForEach(Array(chartPoints.enumerated()), id: \.element.snapshot.id) { idx, point in
                chartBar(at: idx, point: point)
            }
        }
        .frame(height: 132)
        .padding(12)
        .background(Color(.systemBackground).opacity(0.3), in: RoundedRectangle(cornerRadius: 8))
    }

    @ViewBuilder
    private func chartBar(at index: Int, point: ChartPoint) -> some View {
        let isCurrent = point.snapshot.id == currentSnapshotID
        let totalBarHeight = CGFloat(point.count) / CGFloat(max(maxCount, 1)) * 54
        let addedHeight = changeHeight(for: point.diff.added)
        let disappearedHeight = changeHeight(for: point.diff.disappeared)

        VStack(spacing: 0) {
            if isCurrent {
                Image(systemName: "star.fill")
                    .font(.system(size: 12, weight: .semibold))
                    .foregroundStyle(Color.accentColor)
                    .padding(.bottom, 4)
            }

            VStack(spacing: 3) {
                changeSegment(
                    count: point.diff.added,
                    height: addedHeight,
                    color: Color.healthyGreen,
                    pinsToBottom: true
                )

                RoundedRectangle(cornerRadius: 2)
                    .fill(Color.neutralGray.opacity(0.6))
                    .frame(height: max(4, totalBarHeight))
                    .frame(maxWidth: .infinity)

                changeSegment(
                    count: point.diff.disappeared,
                    height: disappearedHeight,
                    color: Color.criticalRed,
                    pinsToBottom: false
                )
            }
            .frame(height: 96, alignment: .bottom)
            .overlay(alignment: .bottom) {
                if point.diff.isFallback, point.diff.disappeared > 0 || point.diff.added > 0 {
                    Image(systemName: "tilde")
                        .font(.system(size: 9, weight: .bold))
                        .foregroundStyle(.secondary)
                        .offset(y: 12)
                }
            }

            Text(point.snapshot.timestamp.formatted(.dateTime.month().day()))
                .font(.caption2)
                .foregroundStyle(.secondary)
                .lineLimit(1)
                .padding(.top, 4)
        }
        .frame(maxWidth: .infinity)
    }

    private func changeHeight(for count: Int) -> CGFloat {
        guard count > 0 else { return 0 }
        let scaled = CGFloat(count) / CGFloat(maxChangeCount) * 18
        return max(4, scaled)
    }

    @ViewBuilder
    private func changeSegment(
        count: Int,
        height: CGFloat,
        color: Color,
        pinsToBottom: Bool
    ) -> some View {
        VStack {
            if pinsToBottom {
                Spacer(minLength: 0)
            }

            if count > 0 {
                RoundedRectangle(cornerRadius: 2)
                    .fill(color)
                    .frame(height: height)
                    .frame(maxWidth: .infinity)
                    .accessibilityLabel("\(count) records")
            } else {
                Color.clear.frame(height: 0)
            }

            if !pinsToBottom {
                Spacer(minLength: 0)
            }
        }
        .frame(height: 18)
    }

    private var changeLegend: some View {
        HStack(spacing: 10) {
            legendItem(color: Color.healthyGreen, label: "New")
            legendItem(color: Color.criticalRed, label: "Missing")
            Spacer(minLength: 0)
        }
        .font(.caption2)
        .foregroundStyle(.secondary)
    }

    private func legendItem(color: Color, label: String) -> some View {
        HStack(spacing: 4) {
            RoundedRectangle(cornerRadius: 2)
                .fill(color)
                .frame(width: 12, height: 4)
            Text(label)
        }
    }

    private func countForSnapshot(_ snapshot: HealthSnapshot) -> Int {
        if let observationID = snapshot.archiveObservationID,
           let cachedCount = cachedCountsByObservationID[observationID] {
            return cachedCount
        }

        return snapshot.typeCounts?.first { $0.typeIdentifier == typeIdentifier }?.count ?? 0
    }

    @MainActor
    private func loadArchiveEvolutionCache() async {
        let archiveSnapshots = contextSnapshots.filter { $0.archiveObservationID != nil }
        guard !archiveSnapshots.isEmpty else {
            cachedCountsByObservationID = [:]
            cachedDiffsByPair = nil
            return
        }

        do {
            let cache = try CoreDataArchiveCacheStore()
            var counts: [Int64: Int] = [:]
            var diffs: [ArchiveDiffKey: RecordChangeDiff] = [:]

            for snapshot in archiveSnapshots {
                guard let observationID = snapshot.archiveObservationID else { continue }
                let summary = try cache.typeSummaries(observationID: observationID)
                    .first { $0.sampleTypeIdentifier == typeIdentifier }
                counts[observationID] = summary?.visibleRecordCount ?? 0

                guard let previous = snapshot.previousInTimeline(sortedSnapshots),
                      let previousObservationID = previous.archiveObservationID else {
                    continue
                }

                if let diff = try cache.diffSummary(
                    fromObservationID: previousObservationID,
                    toObservationID: observationID,
                    sampleTypeIdentifier: typeIdentifier
                ) {
                    diffs[ArchiveDiffKey(fromObservationID: previousObservationID, toObservationID: observationID)] = RecordChangeDiff(
                        added: diff.appearedCount,
                        disappeared: diff.disappearedCount,
                        isExact: true
                    )
                }
            }

            cachedCountsByObservationID = counts
            cachedDiffsByPair = diffs
        } catch {
            cachedCountsByObservationID = [:]
            cachedDiffsByPair = nil
        }
    }
}

private struct ChartPoint {
    let snapshot: HealthSnapshot
    let count: Int
    let diff: RecordChangeDiff
}

private struct RecordChangeDiff {
    let added: Int
    let disappeared: Int
    let isExact: Bool

    init(added: Int = 0, disappeared: Int = 0, isExact: Bool = false) {
        self.added = added
        self.disappeared = disappeared
        self.isExact = isExact
    }

    var isFallback: Bool {
        !isExact
    }
}

private struct ArchiveDiffKey: Hashable {
    let fromObservationID: Int64
    let toObservationID: Int64
}

#Preview {
    let mockSnapshots = (0..<7).map { idx in
        let snapshot = HealthSnapshot(
            timestamp: Calendar.current.date(byAdding: .day, value: idx - 3, to: Date.now) ?? .now,
            osVersion: "iOS 26.4",
            deviceName: "Preview iPhone",
            deviceID: "preview-device"
        )

        let recordCount = Int.random(in: 5000..<15000)
        let mockRecords = (0..<recordCount).map { i in
            HealthRecordValue(
                typeIdentifier: "HKQuantityTypeIdentifierStepCount",
                sampleUUIDHash: UUID().uuidString,
                recordFingerprint: "fp-\(idx)-\(i)",
                startDate: Date.now.addingTimeInterval(-Double(i * 3600)),
                endDate: Date.now.addingTimeInterval(-Double(i * 3600) + 3600),
                displayValue: nil
            )
        }

        let typeCount = TypeCount(
            typeIdentifier: "HKQuantityTypeIdentifierStepCount",
            displayName: "Step Count",
            count: recordCount
        )
        typeCount.setRecordValues(mockRecords)
        snapshot.typeCounts = [typeCount]

        return snapshot
    }

    RecordChangeEvolutionChart(
        snapshots: mockSnapshots,
        currentSnapshotID: mockSnapshots[3].id,
        typeIdentifier: "HKQuantityTypeIdentifierStepCount",
        displayName: "Step Count"
    )
    .padding(16)
}
