//
//  LocalMediaProgress.swift
//  App
//
//  Created by Ron Heft on 8/16/22.
//

import Foundation
import RealmSwift

class LocalMediaProgress: Object, Codable {
    @Persisted(primaryKey: true) var id: String = ""
    @Persisted(indexed: true) var localLibraryItemId: String = ""
    @Persisted(indexed: true) var localEpisodeId: String?
    @Persisted var duration: Double = 0
    @Persisted var progress: Double = 0
    @Persisted var currentTime: Double = 0
    @Persisted var isFinished: Bool = false
    @Persisted var lastUpdate: Double = 0
    @Persisted var startedAt: Double = 0
    @Persisted var finishedAt: Double?
    // For local lib items from server to support server sync
    @Persisted var serverConnectionConfigId: String?
    @Persisted var serverAddress: String?
    @Persisted var serverUserId: String?
    @Persisted(indexed: true) var libraryItemId: String?
    @Persisted(indexed: true) var episodeId: String?

    var progressPercent: Int { Int(self.progress * 100) }
    
    private enum CodingKeys : String, CodingKey {
        case id, localLibraryItemId, localEpisodeId, duration, progress, currentTime, isFinished, lastUpdate, startedAt, finishedAt, serverConnectionConfigId, serverAddress, serverUserId, libraryItemId, episodeId
    }
    
    override init() {
        super.init()
    }
    
    required init(from decoder: Decoder) throws {
        super.init()

        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(String.self, forKey: .id)
        localLibraryItemId = try values.decode(String.self, forKey: .localLibraryItemId)
        localEpisodeId = try values.decodeIfPresent(String.self, forKey: .localEpisodeId)
        duration = try values.decode(Double.self, forKey: .duration)
        progress = try values.decode(Double.self, forKey: .progress)
        currentTime = try values.decode(Double.self, forKey: .currentTime)
        isFinished = try values.decode(Bool.self, forKey: .isFinished)
        lastUpdate = try values.decode(Double.self, forKey: .lastUpdate)
        startedAt = try values.decode(Double.self, forKey: .startedAt)
        finishedAt = try values.decodeIfPresent(Double.self, forKey: .finishedAt)
        serverConnectionConfigId = try values.decodeIfPresent(String.self, forKey: .serverConnectionConfigId)
        serverAddress = try values.decodeIfPresent(String.self, forKey: .serverAddress)
        serverUserId = try values.decodeIfPresent(String.self, forKey: .serverUserId)
        libraryItemId = try values.decodeIfPresent(String.self, forKey: .libraryItemId)
        episodeId = try values.decodeIfPresent(String.self, forKey: .episodeId)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(localLibraryItemId, forKey: .localLibraryItemId)
        try container.encode(localEpisodeId, forKey: .localEpisodeId)
        try container.encode(duration, forKey: .duration)
        if progress.isNaN == false {
            try container.encode(progress, forKey: .progress)
        }
        if currentTime.isNaN == false {
            try container.encode(currentTime, forKey: .currentTime)
        }
        try container.encode(isFinished, forKey: .isFinished)
        try container.encode(lastUpdate, forKey: .lastUpdate)
        try container.encode(startedAt, forKey: .startedAt)
        try container.encode(finishedAt, forKey: .finishedAt)
        try container.encode(serverConnectionConfigId, forKey: .serverConnectionConfigId)
        try container.encode(serverAddress, forKey: .serverAddress)
        try container.encode(serverUserId, forKey: .serverUserId)
        try container.encode(libraryItemId, forKey: .libraryItemId)
        try container.encode(episodeId, forKey: .episodeId)
    }
}

extension LocalMediaProgress {
    convenience init(localLibraryItem: LocalLibraryItem, episode: PodcastEpisode?) {
        self.init()
        
        self.id = localLibraryItem.id
        self.localLibraryItemId = localLibraryItem.id
        self.libraryItemId = localLibraryItem.libraryItemId
        
        self.serverAddress = localLibraryItem.serverAddress
        self.serverUserId = localLibraryItem.serverUserId
        self.serverConnectionConfigId = localLibraryItem.serverConnectionConfigId
        
        self.duration = localLibraryItem.getDuration()
        self.progress = 0.0
        self.currentTime = 0.0
        self.isFinished = false
        self.lastUpdate = Date().timeIntervalSince1970 * 1000
        self.startedAt = 0
        self.finishedAt = nil
        
        if let episode = episode {
            self.id += "-\(episode.id)"
            self.episodeId = episode.id
            self.duration = episode.duration ?? 0.0
        }
    }
    
    convenience init(localLibraryItem: LocalLibraryItem, episode: PodcastEpisode?, progress: MediaProgress) {
        self.init(localLibraryItem: localLibraryItem, episode: episode)
        self.duration = progress.duration
        self.progress = progress.progress
        self.currentTime = progress.currentTime
        self.isFinished = progress.isFinished
        self.lastUpdate = progress.lastUpdate
        self.startedAt = progress.startedAt
        self.finishedAt = progress.finishedAt
    }
    
    func updateIsFinished(_ finished: Bool) throws {
        try self.realm?.write {
            if self.isFinished != finished {
                self.progress = finished ? 1.0 : 0.0
            }

            if self.startedAt == 0 && finished {
                self.startedAt = Date().timeIntervalSince1970 * 1000
            }
            
            self.isFinished = finished
            self.lastUpdate = Date().timeIntervalSince1970 * 1000
            self.finishedAt = finished ? lastUpdate : nil
        }
    }
    
    func updateFromPlaybackSession(_ playbackSession: PlaybackSession) throws {
        try self.realm?.write {
            self.currentTime = playbackSession.currentTime
            self.progress = playbackSession.progress
            self.lastUpdate = Date().timeIntervalSince1970 * 1000
            self.isFinished = playbackSession.progress >= 100.0
            self.finishedAt = self.isFinished ? self.lastUpdate : nil
        }
    }
    
    func updateFromServerMediaProgress(_ serverMediaProgress: MediaProgress) throws {
        try self.realm?.write {
            self.isFinished = serverMediaProgress.isFinished
            self.progress = serverMediaProgress.progress
            self.currentTime = serverMediaProgress.currentTime
            self.duration = serverMediaProgress.duration
            self.lastUpdate = serverMediaProgress.lastUpdate
            self.finishedAt = serverMediaProgress.finishedAt
            self.startedAt = serverMediaProgress.startedAt
        }
    }
    
    static func getLocalMediaProgressId(localLibraryItemId: String?, localEpisodeId: String?) -> String? {
        if let itemId = localLibraryItemId, let episodeId = localEpisodeId {
            return "\(itemId)-\(episodeId)"
        } else if let itemId = localLibraryItemId {
            return itemId
        } else {
            return nil
        }
    }
    
    static func fetchOrCreateLocalMediaProgress(localMediaProgressId: String?, localLibraryItemId: String?, localEpisodeId: String?) throws -> LocalMediaProgress? {
        let localMediaProgressId = localMediaProgressId != nil ? localMediaProgressId : LocalMediaProgress.getLocalMediaProgressId(localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
        
        let realm = try Realm()
        return try realm.write { () -> LocalMediaProgress? in
            if let localMediaProgressId = localMediaProgressId {
                // Check if it existing in the database, if not, we need to create it
                if let progress = Database.shared.getLocalMediaProgress(localMediaProgressId: localMediaProgressId) {
                    return progress
                }
            }
            
            if let localLibraryItemId = localLibraryItemId {
                guard let localLibraryItem = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) else { return nil }
                let episode = localLibraryItem.getPodcastEpisode(episodeId: localEpisodeId)
                let progress = LocalMediaProgress(localLibraryItem: localLibraryItem, episode: episode)
                realm.add(progress)
                return progress
            } else {
                return nil
            }
        }
    }
}

