//
//  Database.swift
//  App
//
//  Created by Rasmus Krämer on 11.04.22.
//

import Foundation
import RealmSwift

class Database {
    public static var shared = {
        return Database()
    }()
    
    private let logger = AppLogger(category: "Database")

    private init() {}
    
    public func setServerConnectionConfig(config: ServerConnectionConfig) {
        let config = config
        let realm = try! Realm()
        let existing: ServerConnectionConfig? = realm.object(ofType: ServerConnectionConfig.self, forPrimaryKey: config.id)
        
        if let existing = existing {
            do {
                try existing.update {
                    existing.name = config.name
                    existing.address = config.address
                    existing.userId = config.userId
                    existing.username = config.username
                    existing.token = config.token
                }
            } catch {
                logger.error("failed to update server config")
                debugPrint(error)
            }
            
            setLastActiveConfigIndex(index: existing.index)
        } else {
            if config.index == 0 {
                let lastConfig: ServerConnectionConfig? = realm.objects(ServerConnectionConfig.self).last
                
                if lastConfig != nil {
                    config.index = lastConfig!.index + 1
                } else {
                    config.index = 1
                }
            }
            
            do {
                try realm.write {
                    realm.add(config)
                }
            } catch(let exception) {
                logger.error("failed to save server config")
                debugPrint(exception)
            }
            
            setLastActiveConfigIndex(index: config.index)
        }
    }
    
    public func deleteServerConnectionConfig(id: String) {
        let realm = try! Realm()
        let config = realm.object(ofType: ServerConnectionConfig.self, forPrimaryKey: id)
        
        do {
            try realm.write {
                if config != nil {
                    realm.delete(config!)
                }
            }
        } catch(let exception) {
            logger.error("failed to delete server config")
            debugPrint(exception)
        }
    }
    
    public func getServerConnectionConfigs() -> [ServerConnectionConfig] {
        let realm = try! Realm()
        return Array(realm.objects(ServerConnectionConfig.self))
    }
    
    public func setLastActiveConfigIndexToNil() {
        setLastActiveConfigIndex(index: nil)
    }
    
    private func setLastActiveConfigIndex(index: Int?) {
        let realm = try! Realm()
        do {
            try realm.write {
                let existing = realm.objects(ServerConnectionConfigActiveIndex.self).last
                
                if ( existing?.index != index ) {
                    if let existing = existing {
                        realm.delete(existing)
                    }
                    
                    let activeConfig = ServerConnectionConfigActiveIndex()
                    activeConfig.index = index
                    realm.add(activeConfig)
                }
            }
        } catch(let exception) {
            logger.error("failed to save server config active index")
            debugPrint(exception)
        }
    }
    
    public func getLastActiveConfigIndex() -> Int? {
        let realm = try! Realm()
        return realm.objects(ServerConnectionConfigActiveIndex.self).first?.index ?? nil
    }
    
    public func setDeviceSettings(deviceSettings: DeviceSettings) {
        let realm = try! Realm()
        let existing = realm.objects(DeviceSettings.self)

        do {
            try realm.write {
                realm.delete(existing)
                realm.add(deviceSettings)
            }
        } catch {
            logger.error("failed to save device settings")
        }
    }
    
    public func getLocalLibraryItems(mediaType: MediaType? = nil) -> [LocalLibraryItem] {
        do {
            let realm = try Realm()
            return Array(realm.objects(LocalLibraryItem.self))
        } catch {
            debugPrint(error)
            return []
        }
    }
    
    public func getLocalLibraryItem(byServerLibraryItemId: String) -> LocalLibraryItem? {
        do {
            let realm = try Realm()
            return realm.objects(LocalLibraryItem.self).first(where: { $0.libraryItemId == byServerLibraryItemId })
        } catch {
            debugPrint(error)
            return nil
        }
    }
    
    public func getLocalLibraryItem(localLibraryItemId: String) -> LocalLibraryItem? {
        do {
            let realm = try Realm()
            return realm.object(ofType: LocalLibraryItem.self, forPrimaryKey: localLibraryItemId)
        } catch {
            debugPrint(error)
            return nil
        }
    }
    
    public func saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) throws {
        let realm = try Realm()
        try realm.write { realm.add(localLibraryItem, update: .modified) }
    }
    
    public func getLocalFile(localFileId: String) -> LocalFile? {
        do {
            let realm = try Realm()
            return realm.object(ofType: LocalFile.self, forPrimaryKey: localFileId)
        } catch {
            debugPrint(error)
            return nil
        }
    }
    
    public func getDownloadItem(downloadItemId: String) -> DownloadItem? {
        do {
            let realm = try Realm()
            return realm.object(ofType: DownloadItem.self, forPrimaryKey: downloadItemId)
        } catch {
            debugPrint(error)
            return nil
        }
    }
    
    public func getDownloadItem(libraryItemId: String) -> DownloadItem? {
        do {
            let realm = try Realm()
            return realm.objects(DownloadItem.self).filter("libraryItemId == %@", libraryItemId).first
        } catch {
            debugPrint(error)
            return nil
        }
    }
    
    public func getDownloadItem(downloadItemPartId: String) -> DownloadItem? {
        do {
            let realm = try Realm()
            return realm.objects(DownloadItem.self).filter("SUBQUERY(downloadItemParts, $part, $part.id == %@) .@count > 0", downloadItemPartId).first
        } catch {
            debugPrint(error)
            return nil
        }
    }
    
    public func saveDownloadItem(_ downloadItem: DownloadItem) throws {
        let realm = try Realm()
        return try realm.write { realm.add(downloadItem, update: .modified) }
    }
    
    public func getDeviceSettings() -> DeviceSettings {
        let realm = try! Realm()
        return realm.objects(DeviceSettings.self).first ?? getDefaultDeviceSettings()
    }
    
    public func getAllLocalMediaProgress() -> [LocalMediaProgress] {
        do {
            let realm = try Realm()
            return Array(realm.objects(LocalMediaProgress.self))
        } catch {
            debugPrint(error)
            return []
        }
    }
    
    // For books this will just be the localLibraryItemId for podcast episodes this will be "{localLibraryItemId}-{episodeId}"
    public func getLocalMediaProgress(localMediaProgressId: String) -> LocalMediaProgress? {
        do {
            let realm = try Realm()
            return realm.object(ofType: LocalMediaProgress.self, forPrimaryKey: localMediaProgressId)
        } catch {
            debugPrint(error)
            return nil
        }
    }
    
    public func removeLocalMediaProgress(localMediaProgressId: String) throws {
        let realm = try Realm()
        try realm.write {
            let progress = realm.object(ofType: LocalMediaProgress.self, forPrimaryKey: localMediaProgressId)
            realm.delete(progress!)
        }
    }
    
    public func getAllPlaybackSessions() -> [PlaybackSession] {
        do {
            let realm = try Realm()
            return Array(realm.objects(PlaybackSession.self))
        } catch {
            debugPrint(error)
            return []
        }
    }
    
    public func getPlaybackSession(id: String) -> PlaybackSession? {
        do {
            let realm = try Realm()
            realm.refresh() // Refresh, because working with stale sessions leads to wrong times
            return realm.object(ofType: PlaybackSession.self, forPrimaryKey: id)
        } catch {
            debugPrint(error)
            return nil
        }
    }
}
