import Foundation
import SwiftData

/// Builds the SwiftData cache container. The iCloud Drive JSON documents are the
/// source of truth, so this store is a rebuildable cache — wiping it is always
/// safe; `SyncEngine` repopulates it from the container on next launch.
enum WorkoutsModelContainer {
    /// Bump whenever the cache schema changes — wipes and rebuilds from files.
    /// 2: added health-metric columns to `Workout` and `activityTypeRaw` to `Routine`.
    /// 3: added `categoryRaw` to `Exercise`.
    /// 4: removed the weight-reminder columns and `categoryRaw` from `Exercise` and
    ///    the `completed` column from `WorkoutLog`; added `machineSettings` to both.
    /// 5: `weight` went Int → Double on `Exercise` and `WorkoutLog`; added
    ///    `setEntries` and `logUpdatedAt` to `WorkoutLog`.
    /// 6: added `deletedLogIDs` to `Workout` (per-log merge deletion tombstones).
    /// 7: renamed `Split` entity to `Routine`.
    /// 8: added `Schedule` cache entity.
    /// 9: added `libraryName` to `Exercise` and `WorkoutLog`.
    static let currentSchemaVersion = 9
    private static let schemaVersionKey = "Workouts.persistenceSchemaVersion"
    private static let identityTokenKey = "Workouts.iCloudIdentityToken"

    private static var storeURL: URL {
        URL.applicationSupportDirectory.appending(path: "Workouts.store")
    }

    /// Sidecar file holding queued-but-unwritten document writes (`WriteBacklog`,
    /// iOS only). Lives beside the cache store — outside the iCloud container —
    /// and is wiped with it on an account change: a backlog from one account must
    /// never drain into another account's container.
    static var pendingWritesURL: URL {
        URL.applicationSupportDirectory.appending(path: "PendingWrites.json")
    }

    static func make() -> ModelContainer {
        let schema = Schema([Routine.self, Exercise.self, Workout.self, WorkoutLog.self, Schedule.self])
        ensureStoreDirectoryExists()
        wipeIfNeeded()
        wipeIfAccountChanged()

        do {
            // `.none` is load-bearing: the default `.automatic` would silently
            // enable CloudKit mirroring on top of our iCloud Drive file sync.
            let config = ModelConfiguration(schema: schema, url: storeURL, cloudKitDatabase: .none)
            let container = try ModelContainer(for: schema, configurations: [config])
            UserDefaults.standard.set(currentSchemaVersion, forKey: schemaVersionKey)
            return container
        } catch {
            print("Workouts: ModelContainer creation failed at \(storeURL.path): \(error). Falling back to in-memory.")
        }

        do {
            let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
            return try ModelContainer(for: schema, configurations: [config])
        } catch {
            fatalError("Workouts: could not create even an in-memory ModelContainer: \(error)")
        }
    }

    /// Records the current iCloud identity token as the cache's owner. Call after
    /// rebuilding the cache for a newly-signed-in account.
    static func persistCurrentIdentityToken() {
        UserDefaults.standard.set(currentIdentityTokenData(), forKey: identityTokenKey)
    }

    // MARK: - Wipe helpers

    private static func ensureStoreDirectoryExists() {
        let parent = storeURL.deletingLastPathComponent()
        try? FileManager.default.createDirectory(at: parent, withIntermediateDirectories: true)
    }

    private static func wipeIfNeeded() {
        let stored = UserDefaults.standard.integer(forKey: schemaVersionKey)
        guard stored < currentSchemaVersion else { return }
        wipeStore()
    }

    /// Wipes the cache when the signed-in iCloud account differs from the one the
    /// cache was built for. A nil token is "not ready yet", not "no account" — so
    /// only compare once a token resolves; mid-session changes are handled live.
    private static func wipeIfAccountChanged() {
        guard let current = currentIdentityTokenData() else { return }
        let stored = UserDefaults.standard.data(forKey: identityTokenKey)
        guard current != stored else { return }
        wipeStore()
        try? FileManager.default.removeItem(at: pendingWritesURL)
        UserDefaults.standard.set(current, forKey: identityTokenKey)
    }

    private static func currentIdentityTokenData() -> Data? {
        guard let token = FileManager.default.ubiquityIdentityToken else { return nil }
        return try? NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: false)
    }

    private static func wipeStore() {
        let base = storeURL
        for url in [base, URL(fileURLWithPath: base.path + "-wal"), URL(fileURLWithPath: base.path + "-shm")] {
            try? FileManager.default.removeItem(at: url)
        }
    }
}
