//
//  SharedModelContainer.swift
//  Reflect
//
//  Factory for creating ModelContainer shared between main app and widget extension.
//

import Foundation
import SwiftData
import os.log

/// Errors that can occur when creating the shared model container
enum SharedModelContainerError: LocalizedError {
    case appGroupNotAvailable(String)
    case modelContainerCreationFailed(Error)

    var errorDescription: String? {
        switch self {
        case .appGroupNotAvailable(let groupID):
            return "App Group container not available for: \(groupID)"
        case .modelContainerCreationFailed(let error):
            return "Failed to create ModelContainer: \(error.localizedDescription)"
        }
    }
}

enum SharedModelContainer {
    private static let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.88oakapps.reflect", category: "SharedModelContainer")

    /// Indicates whether the app is running with in-memory storage due to a failed App Group container.
    /// When `true`, all data will be lost on app restart.
    static private(set) var isUsingInMemoryFallback = false

    /// Creates a ModelContainer with the appropriate configuration for app group sharing
    /// - Parameter useCloudKit: Whether to enable CloudKit sync (defaults to true)
    /// - Returns: Configured ModelContainer
    /// - Throws: SharedModelContainerError if creation fails
    static func create(useCloudKit: Bool = true) throws -> ModelContainer {
        let schema = Schema([MoodEntryModel.self])
        let storeURL = try Self.storeURL

        let configuration: ModelConfiguration
        if useCloudKit {
            // CloudKit-enabled configuration
            configuration = ModelConfiguration(
                schema: schema,
                url: storeURL,
                cloudKitDatabase: .private(cloudKitContainerID)
            )
        } else {
            // Local-only configuration
            configuration = ModelConfiguration(
                schema: schema,
                url: storeURL,
                cloudKitDatabase: .none
            )
        }

        do {
            return try ModelContainer(for: schema, configurations: [configuration])
        } catch {
            logger.error("Failed to create ModelContainer: \(error.localizedDescription)")
            throw SharedModelContainerError.modelContainerCreationFailed(error)
        }
    }

    /// Creates a ModelContainer, falling back to in-memory storage if shared container fails
    /// - Parameter useCloudKit: Whether to enable CloudKit sync (defaults to true)
    /// - Returns: Configured ModelContainer (shared or in-memory fallback)
    static func createWithFallback(useCloudKit: Bool = true) -> ModelContainer {
        do {
            return try create(useCloudKit: useCloudKit)
        } catch {
            logger.warning("Falling back to in-memory storage due to: \(error.localizedDescription)")
            logger.critical("App is using in-memory storage — all mood data will be lost on restart. App Group container failed: \(error.localizedDescription)")
            #if DEBUG
            assertionFailure("SharedModelContainer fell back to in-memory storage. App Group container is unavailable: \(error.localizedDescription)")
            #endif
            isUsingInMemoryFallback = true
            // Fall back to in-memory storage
            let schema = Schema([MoodEntryModel.self])
            let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
            do {
                return try ModelContainer(for: schema, configurations: [config])
            } catch {
                // This should never happen with in-memory storage, but handle it gracefully
                logger.critical("Failed to create even in-memory ModelContainer: \(error.localizedDescription)")
                preconditionFailure("Unable to create ModelContainer: \(error)")
            }
        }
    }

    /// The URL for the SwiftData store in the shared app group container
    /// - Throws: SharedModelContainerError if app group is not available
    static var storeURL: URL {
        get throws {
            guard let containerURL = FileManager.default.containerURL(
                forSecurityApplicationGroupIdentifier: appGroupID
            ) else {
                logger.error("App Group container not available for: \(appGroupID)")
                throw SharedModelContainerError.appGroupNotAvailable(appGroupID)
            }
            return containerURL.appendingPathComponent(storeFileName)
        }
    }

    /// App Group identifier based on build configuration
    static var appGroupID: String {
        #if DEBUG
        return Constants.groupShareIdDebug
        #else
        return Constants.groupShareId
        #endif
    }

    /// CloudKit container identifier based on build configuration
    static var cloudKitContainerID: String {
        #if DEBUG
        return "iCloud.com.88oakapps.reflect.debug"
        #else
        return "iCloud.com.88oakapps.reflect"
        #endif
    }

    /// Store file name based on build configuration
    static var storeFileName: String {
        #if DEBUG
        return "Reflect-Debug.store"
        #else
        return "Reflect.store"
        #endif
    }
}
