// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.

import Foundation
import Combine
import GRDB
import SessionSnodeKit
import SessionUtilitiesKit

public final class ClosedGroupPoller: Poller {
    public static var namespaces: [SnodeAPI.Namespace] = [.legacyClosedGroup]

    // MARK: - Settings
    
    override var namespaces: [SnodeAPI.Namespace] { ClosedGroupPoller.namespaces }
    override var maxNodePollCount: UInt { 0 }
    
    private static let minPollInterval: Double = 3
    private static let maxPollInterval: Double = 30

    // MARK: - Initialization
    
    public static let shared: ClosedGroupPoller = ClosedGroupPoller()

    // MARK: - Public API
    
    public func start(using dependencies: Dependencies = Dependencies()) {
        // Fetch all closed groups (excluding any don't contain the current user as a
        // GroupMemeber as the user is no longer a member of those)
        dependencies.storage
            .read { db in
                try ClosedGroup
                    .select(.threadId)
                    .joining(
                        required: ClosedGroup.members
                            .filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db, using: dependencies))
                    )
                    .asRequest(of: String.self)
                    .fetchAll(db)
            }
            .defaulting(to: [])
            .forEach { [weak self] publicKey in
                self?.startIfNeeded(for: publicKey, using: dependencies)
            }
    }

    // MARK: - Abstract Methods
    
    override func pollerName(for publicKey: String) -> String {
        return "closed group with public key: \(publicKey)"
    }

    override func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
        // Get the received date of the last message in the thread. If we don't have
        // any messages yet, pick some reasonable fake time interval to use instead
        let lastMessageDate: Date = Storage.shared
            .read { db in
                try Interaction
                    .filter(Interaction.Columns.threadId == publicKey)
                    .select(.receivedAtTimestampMs)
                    .order(Interaction.Columns.timestampMs.desc)
                    .asRequest(of: Int64.self)
                    .fetchOne(db)
            }
            .map { receivedAtTimestampMs -> Date? in
                guard receivedAtTimestampMs > 0 else { return nil }
                
                return Date(timeIntervalSince1970: (TimeInterval(receivedAtTimestampMs) / 1000))
            }
            .defaulting(to: Date().addingTimeInterval(-5 * 60))
        
        let timeSinceLastMessage: TimeInterval = dependencies.dateNow.timeIntervalSince(lastMessageDate)
        let minPollInterval: Double = ClosedGroupPoller.minPollInterval
        let limit: Double = (12 * 60 * 60)
        let a: TimeInterval = ((ClosedGroupPoller.maxPollInterval - minPollInterval) / limit)
        let nextPollInterval: TimeInterval = a * min(timeSinceLastMessage, limit) + minPollInterval
        SNLog("Next poll interval for closed group with public key: \(publicKey) is \(nextPollInterval) s.")
        
        return nextPollInterval
    }

    override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
        SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).")
        return true
    }
}
