import UIKit
import OSLog
import BackgroundTasks

import Theme
import XXLogger
import Defaults
import Integration
import ToastFeature
import SwiftyDropbox
import CrashReporting
import PushNotifications
import DependencyInjection

import OnboardingFeature
import DropboxFeature

let logger = Logger(subsystem: "logs_xxmessenger", category: "AppDelegate.swift")

public class AppDelegate: UIResponder, UIApplicationDelegate {
    @Dependency private var pushHandler: PushHandling
    @Dependency private var crashReporter: CrashReporter
    @Dependency private var dropboxService: DropboxInterface

    @KeyObject(.hideAppList, defaultValue: false) var hideAppList: Bool
    @KeyObject(.recordingLogs, defaultValue: true) var recordingLogs: Bool
    @KeyObject(.crashReporting, defaultValue: true) var isCrashReportingEnabled: Bool

    var calledStopNetwork = false
    var forceFailedPendingMessages = false

    var coverView: UIView?
    var backgroundTimer: Timer?
    public var window: UIWindow?

    public func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        #if DEBUG
        DependencyRegistrator.registerForMock()
        #else
        DependencyRegistrator.registerForLive()
        #endif

        if recordingLogs {
            XXLogger.start()
        }

        crashReporter.configure()
        crashReporter.setEnabled(isCrashReportingEnabled)

        UNUserNotificationCenter.current().delegate = self

        let rootScreen =
        StatusBarViewController(
            ToastViewController(
                UINavigationController(
                    rootViewController: OnboardingLaunchController()
                )
            )
        )

        window = Window()
        window?.rootViewController = rootScreen
        window?.backgroundColor = UIColor.white
        window?.makeKeyAndVisible()

        UserDefaults.standard.set(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
        return true
    }

    public func application(application: UIApplication, shouldAllowExtensionPointIdentifier: String) -> Bool {
        false
    }

    public func applicationDidEnterBackground(_ application: UIApplication) {
        if let session = try? DependencyInjection.Container.shared.resolve() as SessionType {
            let backgroundTask = application.beginBackgroundTask(withName: "xx.stop.network") {
                logger.log("Background task will expire")
            }

            // An option here would be: create async completion closure

            backgroundTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
                logger.log("Background time remaining: \(UIApplication.shared.backgroundTimeRemaining)")

                guard UIApplication.shared.backgroundTimeRemaining > 8 else {
                    if !self.calledStopNetwork {
                        self.calledStopNetwork = true
                        session.stop()
                        logger.log("Stopping client threads...")
                    } else {
                        if session.hasRunningTasks == false {
                            application.endBackgroundTask(backgroundTask)
                            timer.invalidate()
                            logger.log("Finished background processes")
                        }
                    }

                    return
                }

                guard UIApplication.shared.backgroundTimeRemaining > 9 else {
                    if !self.forceFailedPendingMessages {
                        self.forceFailedPendingMessages = true
                        logger.log("Background time is running out. Will force-fail all pending messages")
                        session.forceFailMessages()
                    } else {
                        logger.log("Background time is running out without pending messages")
                    }

                    return
                }
            }
        }
    }

    public func applicationWillResignActive(_ application: UIApplication) {
        if hideAppList {
            coverView?.removeFromSuperview()
            coverView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
            coverView?.frame = window?.bounds ?? .zero
            window?.addSubview(coverView!)
        }
    }
    
    public func applicationWillTerminate(_ application: UIApplication) {
        if let session = try? DependencyInjection.Container.shared.resolve() as SessionType {
            logger.log("applicationWillTerminate but has an ongoing session. Calling stopNetwork...")
            session.stop()
        } else {
            logger.log("applicationWillTerminate without any session")
        }
    }

    public func applicationWillEnterForeground(_ application: UIApplication) {
        if backgroundTimer != nil {
            logger.log("Invalidating background timer...")
            backgroundTimer?.invalidate()
            backgroundTimer = nil
        }

        if let session = try? DependencyInjection.Container.shared.resolve() as SessionType {
            guard self.calledStopNetwork == true else {
                logger.log("A client instance is already running. Moving on...")
                return
            }

            logger.log("A client instance is stopped. Starting network...")
            session.start()
            self.calledStopNetwork = false
        }
    }

    public func applicationDidBecomeActive(_ application: UIApplication) {
        application.applicationIconBadgeNumber = 0
        coverView?.removeFromSuperview()
    }

    public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        dropboxService.handleOpenUrl(url)
    }
}

// MARK: Notifications

extension AppDelegate: UNUserNotificationCenterDelegate {
    public func application(
        _: UIApplication,
        didReceiveRemoteNotification notification: [AnyHashable: Any],
        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
    ) {
        pushHandler.didReceiveRemote(notification, completionHandler)
    }

    public func application(
        _: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        pushHandler.didRegisterWith(deviceToken)
    }
}