import CoreData

enum PersistenceControllerError: Error {
    case encryptionKeyUnavailable
    case persistentStoreMissing
    case rekeyingUnavailable
}

final class PersistenceController {
    static let shared: PersistenceController = {
        do {
            return try PersistenceController()
        } catch {
            fatalError("Failed to initialize PersistenceController: \(error)")
        }
    }()

    let container: NSPersistentContainer
    private let keyManager: DatabaseEncryptionKeyManager

    var viewContext: NSManagedObjectContext {
        container.viewContext
    }

    init(
        inMemory: Bool = false,
        keyManager: DatabaseEncryptionKeyManager = .shared,
        fileManager: FileManager = .default
    ) throws {
        self.keyManager = keyManager

        let modelName = "YobbleDataModel"
        guard let modelURL = Bundle.main.url(forResource: modelName, withExtension: "momd"),
              let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else {
            fatalError("Unable to load Core Data model \(modelName)")
        }

        container = NSPersistentContainer(name: modelName, managedObjectModel: managedObjectModel)

        let description = container.persistentStoreDescriptions.first ?? NSPersistentStoreDescription()
        description.type = NSSQLiteStoreType

        if inMemory {
            description.url = URL(fileURLWithPath: "/dev/null")
        } else {
            description.url = try Self.makeStoreURL(fileManager: fileManager, fileName: "\(modelName).sqlite")
        }

        description.shouldInferMappingModelAutomatically = true
        description.shouldMigrateStoreAutomatically = true

        let key: String
        do {
            key = try keyManager.currentKey()
        } catch {
            throw PersistenceControllerError.encryptionKeyUnavailable
        }

        let pragmas: [String: String] = [
            "journal_mode": "WAL",
            "cipher_page_size": "4096",
            "key": key
        ]
        description.setOption(pragmas as NSDictionary, forKey: NSSQLitePragmasOption)
        description.setOption(FileProtectionType.completeUntilFirstUserAuthentication as NSObject, forKey: NSPersistentStoreFileProtectionKey)

        container.persistentStoreDescriptions = [description]

        container.loadPersistentStores { _, error in
            if let error {
                fatalError("Unresolved error \(error)")
            }
        }

        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        container.viewContext.automaticallyMergesChangesFromParent = true
    }

    func newBackgroundContext() -> NSManagedObjectContext {
        container.newBackgroundContext()
    }

    /// Placeholder for a future rekey flow once a password-based key is available.
    ///
    /// On iOS 16 with SQLCipher you typically re-encrypt by running `PRAGMA rekey` via a raw
    /// SQLite handle and then persisting the new key to the key manager. This helper keeps
    /// the signature in place for when that flow is implemented.
    func rekeyStore(to newKey: String) throws {
        throw PersistenceControllerError.rekeyingUnavailable
    }

    private static func makeStoreURL(fileManager: FileManager, fileName: String) throws -> URL {
        guard let baseURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
            fatalError("Unable to resolve Application Support directory")
        }
        if !fileManager.fileExists(atPath: baseURL.path) {
            try fileManager.createDirectory(at: baseURL, withIntermediateDirectories: true)
        }
        return baseURL.appendingPathComponent(fileName)
    }
}
