//
//  MainSceneDelegate.swift
//  Tusker
//
//  Created by Shadowfacts on 1/6/20.
//  Copyright © 2020 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm
import MessageUI
import CoreData
import Duckable
import UserAccounts
import ComposeUI

class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate {
    
    var window: UIWindow?

    private var launchActivity: NSUserActivity?
    
    var rootViewController: TuskerRootViewController? {
        window?.rootViewController as? TuskerRootViewController
    }
    
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        
        if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
            launchActivity = activity
        }
        stateRestorationLogger.info("MainSceneDelegate.launchActivity = \(self.launchActivity?.activityType ?? "nil", privacy: .public)")
        
        window = UIWindow(windowScene: windowScene)
        
        showAppOrOnboardingUI(session: session)
        if !connectionOptions.urlContexts.isEmpty {
            self.scene(scene, openURLContexts: connectionOptions.urlContexts)
        }

        window!.makeKeyAndVisible()
        
        NotificationCenter.default.addObserver(self, selector: #selector(themePrefChanged), name: .themePreferenceChanged, object: nil)
        themePrefChanged()
        
        if let shortcutItem = connectionOptions.shortcutItem {
            _ = AppShortcutItem.handle(shortcutItem)
        }
    }

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url,
              var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
              let rootViewController else {
            return
        }
        
        if components.host == "compose" {
            if let mastodonController = window!.windowScene!.session.mastodonController {
                let draft = mastodonController.createDraft()
                let text = components.queryItems?.first(where: { $0.name == "text" })?.value
                draft.text = text ?? ""
                rootViewController.compose(editing: draft, animated: true, isDucked: false)
            }
        } else {
            // Assume anything else is a search query
            components.scheme = "https"
            let query = components.string!
            rootViewController.performSearch(query: query)
        }
    }
    
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        stateRestorationLogger.info("MainSceneDelegate.scene(_:continue:) called with \(userActivity.activityType, privacy: .public)")
        let context: any UserActivityHandlingContext
        if let account = UserActivityManager.getAccount(from: userActivity),
           account.id != scene.session.mastodonController!.accountInfo!.id {
            stateRestorationLogger.info("MainSceneDelegate cannot resume user activity for different account")
            return
        } else {
            context = ActiveAccountUserActivityHandlingContext(isHandoff: true, root: rootViewController!)
        }
        _ = userActivity.handleResume(manager: UserActivityManager(scene: scene as! UIWindowScene, context: context))
    }
    
    func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
        completionHandler(AppShortcutItem.handle(shortcutItem))
    }
    
    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
        
        Preferences.save()
        DraftsPersistentContainer.shared.save()
    }
    
    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }
    
    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
        
        Preferences.save()
        DraftsPersistentContainer.shared.save()
    }
    
    func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
        if let mastodonController = window?.windowScene?.session.mastodonController {
            if let vcActivity = rootViewController?.stateRestorationActivity() {
                vcActivity.isStateRestorationActivity = true
                stateRestorationLogger.info("MainSceneDelegate returning stateRestorationActivity of type \(vcActivity.activityType, privacy: .public) from VC")
                return vcActivity
            } else {
                // need to have an activity to make sure the same account is used
                return UserActivityManager.mainSceneActivity(accountID: mastodonController.accountInfo!.id)
            }
        } else {
            return nil
        }
    }
    
    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }
    
    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.
        
        if let rootVC = window?.rootViewController as? BackgroundableViewController {
            rootVC.sceneDidEnterBackground()
        }
        
        if let context = scene.session.mastodonController?.persistentContainer.viewContext,
           // if the user quickly opens and then closes the app, this may race with loading the persistent store, so in that event we skip cleanup/save
            let psc = context.persistentStoreCoordinator,
           !psc.persistentStores.isEmpty {
            var minDate = Date()
            minDate.addTimeInterval(-7 * 24 * 60 * 60)
            
            let statusReq: NSFetchRequest<NSFetchRequestResult> = StatusMO.fetchRequest()
            statusReq.predicate = NSPredicate(format: "(lastFetchedAt = nil) OR (lastFetchedAt < %@)", minDate as NSDate)
            let deleteStatusReq = NSBatchDeleteRequest(fetchRequest: statusReq)
            deleteStatusReq.resultType = .resultTypeCount
            if let res = try? context.execute(deleteStatusReq) as? NSBatchDeleteResult {
                Logging.general.info("Pruned \(res.result as! Int) statuses")
            }
            
            
            let accountReq: NSFetchRequest<NSFetchRequestResult> = AccountMO.fetchRequest()
            accountReq.predicate = NSPredicate(format: "((lastFetchedAt = nil) OR (lastFetchedAt < %@)) AND (statuses.@count = 0)", minDate as NSDate)
            let deleteAccountReq = NSBatchDeleteRequest(fetchRequest: accountReq)
            deleteAccountReq.resultType = .resultTypeCount
            if let res = try? context.execute(deleteAccountReq) as? NSBatchDeleteResult {
                Logging.general.info("Pruned \(res.result as! Int) accounts")
            }
            
            try? context.save()
        }
        
        BackgroundManager.shared.scheduleTasks()
    }
    
    func showAppOrOnboardingUI(session: UISceneSession? = nil) {
        let session = session ?? window!.windowScene!.session
        if UserAccountsManager.shared.onboardingComplete {
            let account: UserAccountInfo
            if let activity = launchActivity,
               let activityAccount = UserActivityManager.getAccount(from: activity) {
                account = activityAccount
            } else {
                account = UserAccountsManager.shared.getMostRecentAccount()!
            }
            
            if session.mastodonController == nil {
                session.mastodonController = MastodonController.getForAccount(account)
            }
            
            activateAccount(account, animated: false)
            
            if let activity = launchActivity {
                func doRestoreActivity(context: UserActivityHandlingContext) {
                    _ = activity.handleResume(manager: UserActivityManager(scene: window!.windowScene!, context: context))
                    context.finalize(activity: activity)
                }
                if activity.isStateRestorationActivity {
                    doRestoreActivity(context: StateRestorationUserActivityHandlingContext(root: rootViewController!))
                } else if activity.activityType != UserActivityType.mainScene.rawValue {
                    doRestoreActivity(context: ActiveAccountUserActivityHandlingContext(isHandoff: false, root: rootViewController!))
                } else {
                    stateRestorationLogger.fault("MainSceneDelegate launched with non-restorable activity \(activity.activityType, privacy: .public)")
                }
            }
        } else {
            window!.rootViewController = createOnboardingUI()
        }
    }
    
    func activateAccount(_ account: UserAccountInfo, animated: Bool) {
        let oldMostRecentAccount = UserAccountsManager.shared.mostRecentAccountID
        UserAccountsManager.shared.setMostRecentAccount(account)
        window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account)
        
        // iPadOS shows the title below the App Name
        // macOS uses the title as the window title, but uses the subtitle in addition to the default window title (the app name)
        // so this way we get basically the same behavior on both
        if ProcessInfo.processInfo.isiOSAppOnMac || ProcessInfo.processInfo.isMacCatalystApp {
            window!.windowScene!.subtitle = account.instanceURL.host!
        } else {
            window!.windowScene!.title = account.instanceURL.host!
        }
        
        let newRoot = createAppUI()
        if let container = window?.rootViewController as? AccountSwitchingContainerViewController {
            let direction: AccountSwitchingContainerViewController.AnimationDirection
            if animated,
               let oldIndex = UserAccountsManager.shared.accounts.firstIndex(where: { $0.id == oldMostRecentAccount }),
               let newIndex = UserAccountsManager.shared.accounts.firstIndex(of: account) {
                direction = newIndex > oldIndex ? .upwards : .downwards
            } else {
                direction = .none
            }
            container.setRoot(newRoot, for: account, animating: direction)
        } else {
            window!.rootViewController = AccountSwitchingContainerViewController(root: newRoot, for: account)
        }
    }
    
    func logoutCurrent() {
        guard let account = window?.windowScene?.session.mastodonController?.accountInfo else {
            return
        }
        LogoutService(accountInfo: account).run()
        if UserAccountsManager.shared.onboardingComplete {
            activateAccount(UserAccountsManager.shared.accounts.first!, animated: false)
        } else {
            window!.rootViewController = createOnboardingUI()
        }
    }

    func createAppUI() -> AccountSwitchableViewController {
        let mastodonController = window!.windowScene!.session.mastodonController!
        mastodonController.initialize()
        
        let split = MainSplitViewController(mastodonController: mastodonController)
        if UIDevice.current.userInterfaceIdiom == .phone,
           #available(iOS 16.0, *) {
            // TODO: maybe the duckable container should be outside the account switching container
            return DuckableContainerViewController(child: split)
        } else {
            return split
        }
    }
    
    func createOnboardingUI() -> UIViewController {
        let onboarding = OnboardingViewController()
        onboarding.onboardingDelegate = self
        return onboarding
    }
    
    @objc func themePrefChanged() {
        applyAppearancePreferences()
    }
    
    func showAddAccount() {
        rootViewController?.presentPreferences {
            NotificationCenter.default.post(name: .addAccount, object: nil)
        }
    }
    
}

extension MainSceneDelegate: OnboardingViewControllerDelegate {
    func didFinishOnboarding(account: UserAccountInfo) {
        activateAccount(account, animated: false)
    }
}
