Загрузка данных



import UIKit
import WidgetKit
import SBColorKit
import UserNotifications
import NotificationCenter

import CerberSDK
import Dynatrace
import CoreDataKit

import SBModelKit
import SBGlobalKit
import SBAnalyticsKit
import SBImageResourcesKit

#if DEBUG
import ClientCertificateKit
#endif

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
	
	var window: UIWindow?
	var count = 1
	
	let cerber = Cerber.shared
	private(set) var cerberEnable: Bool = false
	/// Так как Cerber не сохраняет состояние авторизации, то пришлось использовать этот Bool
	private var isAuthSuccess: Bool = false
	/// Задача по извлечению SecIdentity из хранилища CerberSDK
	private var getSecTask: Task<Void, Never>?
	
	private var registrationCompleted = false
	private var dataDeleting = false {
		didSet {
			guard !dataDeleting else { return }
			checkLoginState()
		}
	}
	
	func application(
		_ application: UIApplication,
		willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
	) -> Bool {
		configurationCerber()
		Task {
			await constructRemoteNotifications(application)
		}
		return true
	}

	func application(
		_ application: UIApplication,
		didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
	) -> Bool {
		AppDelegateHelper.check(status: .appVersion(version: "10_05_020",
													memoryTypeList: [.all]))
		configureAnalyticsManager()
		startDynatrace()
		constructNavigationBar()
		logSystemWidgetSize()
		
		/// Выводим Launch
		AppCoordinator.shared.startLaunch()
		
		// MARK: Костыли из-за ngam-а
//		#if DEBUG
//		cerberEnable = false
//		var rootViewController: UIViewController?
//		if #available(iOS 15.0, *) {
//			rootViewController = application.delegate?.window??.rootViewController
//		} else {
//			rootViewController = application.windows.first?.rootViewController
//		}
//		let directory = ClientCertificateManager.getDocumentDirectory()
//		debugPrint("Каталог для размещения сертификата: \(directory)")
//		
//		if let password = ClientCertificateManager.getTextPassword(),
//			let identity = try? ClientCertificateManager.checkForAvailableCertificate(with: password) {
//			// Заменяем пользовательский Certificate and SecIdentity
//			Task { @MainActor in
//				await AppCoordinator.shared.update(data: nil, identity: identity)
//				await AppCoordinator.shared.startAuthorization()
//			}
//		} else {
//			let viewController = ClientCertificateKit.CCMInfoViewController()
//			viewController.successHandler = { password in
//				ClientCertificateManager.saveText(password: password)
//				if let identity = try? ClientCertificateManager.checkForAvailableCertificate(with: password) {
//					// Заменяем пользовательский Certificate and SecIdentity
//					Task { @MainActor in
//						await AppCoordinator.shared.update(data: nil, identity: identity)
//						await AppCoordinator.shared.startAuthorization()
//					}
//				}
//				rootViewController?.dismiss(animated: true)
//			}
//			
//		Task { @MainActor in
//			try? await Task.sleep(nanoseconds: 2_000_000_000)
//				rootViewController?.present(viewController, animated: true)
//			}
//		}
//		#else
		/// Стартуем CerberSDK
		cerber.start()
//		#endif
		
		NotificationCenter.default.addObserver(
			self,
			selector: #selector(methodOfReceivedExitNotification(_:)),
			name: Notification.Name("SBH_EXIT_NOTIFICATION"),
			object: nil
		)
		application.setIconBadgeNumber(.zero)
		
		// Hide Autolayout Warning
		UserDefaults.standard.set(true, forKey: "_UIConstraintBasedLayoutLogUnsatisfiing")
		
		// миграция на новое название приоритета, убрать когда все пользователи перейдут
		updateVeryImportantPriorityInDefaults()
		
		return true
    }
	
	func applicationDidBecomeActive(_ application: UIApplication) {
		AppDelegateManager.shared.applicationDidBecomeActive(application)
	}
	
	func applicationWillResignActive(_ application: UIApplication) {
		AppDelegateManager.shared.applicationWillResignActive(application)
	}

	func applicationWillEnterForeground(_ application: UIApplication) {
		startDynatrace()
		Task {
			_ = try await AppCoordinator.shared.updateStateBeforeApplicationBecomeActive()
			await MainActor.run {
				AppDelegateManager.shared.applicationWillEnterForeground(application)
			}
		}
	}

	func applicationDidEnterBackground(_ application: UIApplication) {
		Task {
			try? await CoreDataKit.CoreDataManager.shared.saveAllContexts()
		}
		cleanLoggedSystemWidgetSizes()
		WidgetCenter.shared.reloadAllTimelines()
		updateAppBadge(application)
		AppDelegateManager.shared.applicationDidEnterBackground(application)
		Dynatrace.endVisit()
		Dynatrace.shutdown()
	}
	
	func applicationWillTerminate(_ application: UIApplication) {
		AppDelegateManager.shared.applicationWillTerminate(application)
	}

	func application(_ app: UIApplication,
					 open url: URL,
					 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
		if url.scheme == "open" { return true }
		AppCoordinator.open(url: url)
		return true
	}

	func application(_ application: UIApplication,
					 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
		AppCoordinator.pushDeviceTokenData = deviceToken
	}

	func application(
		_ application: UIApplication,
		didReceiveRemoteNotification userInfo: [AnyHashable : Any],
		fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
	) {
		AppCoordinator.processUserNotificationAction(identifier: userInfo)
		completionHandler(.newData)
	}
	
	func application(
		_ application: UIApplication,
		supportedInterfaceOrientationsFor window: UIWindow?
	) -> UIInterfaceOrientationMask {
		return AppDelegateHelper.supportedInterfaceOrientationsFor(window: window)
	}
	
	func application(_ application: UIApplication,
					 continue userActivity: NSUserActivity,
					 restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool {
		guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
			let incomingURL = userActivity.webpageURL,
			let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true)
		else {
			return false
		}
		
		guard let path = components.path, let params = components.queryItems else {
			return false
		}

		if path == "/sber911/incidents/" {
			if let value = params.first(where: { $0.name == "id" } )?.value, let id = Int(value)  {
				AppCoordinator.showInformation(
					in: SBModelKit.PushPlain(eventType: .detail(.it), id: id)
				)
			}
			return true
		}
		return false
	}

}

extension AppDelegate : @MainActor UNUserNotificationCenterDelegate {

	func userNotificationCenter(
		_ center: UNUserNotificationCenter,
		willPresent notification: UNNotification,
		withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
	) {
		completionHandler(.list)
	}

	func userNotificationCenter(_ center: UNUserNotificationCenter,
								didReceive response: UNNotificationResponse,
								withCompletionHandler completionHandler: @escaping () -> Void) {
		if response.actionIdentifier == UNNotificationDismissActionIdentifier {
			// MARK: - Пользователь отклонил уведомление, не предпринимая действий
		} else if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
			// MARK: - Пользователь запустил приложение
			AppCoordinator.processUserNotificationAction(
				identifier: response.notification.request.content.userInfo
			)
		}
		completionHandler()
	}

	func wasAppOpenedFromBackground(application: UIApplication) -> Bool {
		return application.applicationState == .inactive || application.applicationState == .background
	}
}

private extension AppDelegate {
	
	func constructRemoteNotifications(_ application: UIApplication) async {
		let notificationCenter = UNUserNotificationCenter.current()
		do {
			notificationCenter.delegate = self
			let granted = try await notificationCenter.requestAuthorization(
				options: [.alert, .badge, .sound]
			)
			guard granted else { return }
			let category = UNNotificationCategory(
				identifier: "UpdateAppNotificationServiceExtension",
				actions: [],
				intentIdentifiers: [],
				options: []
			)
			notificationCenter.setNotificationCategories([category])
			Task { @MainActor in
				application.registerForRemoteNotifications()
			}
		} catch {
			print("Ошибка авторизации в UNUserNotificationCenter: \(error)")
		}
	}
	
	func startDynatrace() {
		// Старт Dynatrace по настройкам из "Dynatrace.plist"
		let privacyConfig = Dynatrace.userPrivacyOptions()
		privacyConfig.dataCollectionLevel = .userBehavior
		privacyConfig.crashReportingOptedIn = true
		Dynatrace.startupWithInfoPlistSettings()
		Dynatrace.applyUserPrivacyOptions(privacyConfig) { _ in }
		if let user = AppCoordinator.shared.userProfile {
			Dynatrace.identifyUser(user.email)
		}
	}
	
	func configurationCerber() {
		cerberEnable = true
		let jailbreakType: JailbreakCheckerType
		let loggerOptions: [CerberSDK.LoggerOptions]
		let mode: CerberSDK.Mode
		switch Config.Sber911.currentStand {
		case .ift:
			mode = .majorCheck
			jailbreakType = .lite
			loggerOptions = [.logEnabled]
		case .psi:
			mode = .majorGo
			jailbreakType = .lite
			loggerOptions = [.logEnabled]
		case .prom:
			mode = .prod
			jailbreakType = .full
			loggerOptions = []
		}
		cerber.options = SetupOptions(
			mode: mode,
			loginOptions: [.pao],
			jailbreakCheckerType: jailbreakType,
			accessGroupID: nil,
			loggerOptions: loggerOptions,
			enableTestSSLPinning: mode != .prod
		)
		cerber.setDelegateManager(delegate: self, delegateQueue: .main)
		
		// Настраиваем SberMAG
		cerber.sberMagOptions = SberMAGOptions(
			enabled: true,
			unavailableProxyAction: .bypass,
			unavailablePolicyAction: .bypass
		)
	}
	
	func configureAnalyticsManager() {
		var analyticStand: AnalyticsStand = .ift
		if Config.Sber911.currentStand == .prom, !Config.Sber911.isMultiServerAppVersion {
			analyticStand = .prom
		}
		let email = AppCoordinator.shared.userProfile?.email
		AnalyticsManager.shared.setup(stand: analyticStand,
									  userLoginId: email)
		AnalyticsManager.shared.setupCrashReporter(stand: analyticStand,
												   userLoginId: email)
		AnalyticsManager.shared.setSession(AppCoordinator.shared.createSession())
	}
	
	func constructNavigationBar() {
		let appearance = UINavigationBarAppearance()
		appearance.backgroundColor = Color.Old.dayNight
		appearance.shadowColor = .clear
		appearance.largeTitleTextAttributes = [
			NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 28)
		]
		UINavigationBar.appearance().scrollEdgeAppearance = appearance
		UINavigationBar.appearance().compactAppearance = appearance
		UINavigationBar.appearance().standardAppearance = appearance
		UINavigationBar.appearance().barTintColor = Color.Old.dayNight
		UINavigationBar.appearance().tintColor = Color.Old.blue
		UINavigationBar.appearance().isTranslucent = false
	}
	
	func logSystemWidgetSize() {
		guard let widgetFamilyList = getWidgetFamilyList() else { return }
		var params: [AnalyticsParam: String] = [:]
		var widgetFamilyParamList: [String] = []
		if widgetFamilyList.contains(.systemSmall) {
			widgetFamilyParamList.append("small")
		}
		if widgetFamilyList.contains(.systemMedium) {
			widgetFamilyParamList.append("medium")
		}
		if widgetFamilyList.contains(.systemLarge) {
			widgetFamilyParamList.append("large")
		}
		if !widgetFamilyParamList.isEmpty {
			params[.type(true)] = widgetFamilyParamList.joined(separator: ", ")
		}
		AnalyticsManager.shared.sendEventWith(name: AnalyticsEvent.UserInfo.SystemWidget.info, params: params)
	}
	
	func cleanLoggedSystemWidgetSizes() {
		let sharedDefaults = UserDefaults(suiteName: "group.ext.corporateHealth")
		sharedDefaults?.removeObject(forKey: "widgetFamilies")
	}
	
	func getWidgetFamilyList() -> [WidgetFamily]? {
		let sharedDefaults = UserDefaults(suiteName: "group.ext.corporateHealth")
		guard let widgetFamilyList = sharedDefaults?.array(forKey: "widgetFamilies") as? [Int] else {
			return nil
		}
		
		return widgetFamilyList.compactMap { familyRawValue in
			return WidgetFamily(rawValue: familyRawValue)
		}
	}
	
	@objc
	func methodOfReceivedExitNotification(_ notification: Foundation.Notification) {
		Task { await AppCoordinator.shared.logOutOfAccount() }
		cerber.logout()
	}
	
	func checkLoginState() {
		guard registrationCompleted, !dataDeleting else { return }
		Task { @MainActor in await AppCoordinator.shared.startAuthorization() }
	}
	
	func updateAppBadge(_ application: UIApplication) {
		let badgeModel = ViewObjectManager.shared.badgeModel
		let count = badgeModel.activeCount + badgeModel.completedCount
		Task { @MainActor in
			application.setIconBadgeNumber(count)
		}
	}
	
	func updateVeryImportantPriorityInDefaults() {
		let key = "Sber_Incident_Filter_Priority"
		if var incidentPriority = UserDefaults.standard.object(forKey: key) as? [String] {
			if let index = incidentPriority.firstIndex(of: "Очень важно") {
				incidentPriority[index] = "Очень важный"
				UserDefaults.standard.set(incidentPriority, forKey: key)
			}
		}
	}
	
	func clear(_ completion: @escaping () -> Void) {
		getSecTask?.cancel()
		getSecTask = nil
		registrationCompleted = false
		dataDeleting = true
		isAuthSuccess = false
		AppCoordinator.clearAll { [weak self] in
			Task { @MainActor [weak self] in
				self?.dataDeleting = false
			}
			// ничего не сделать с warning и не убрать completion, пока Cerber не даст новый апи
			completion()
		}
	}
}

extension AppDelegate: @MainActor CerberDelegate {
	
	/// SDK будет захватывать основное окно приложения
	func willReplaceWindow() {}
	
	/// SDK собирается возвращать основное окно приложения.
	func willReturnWindow() {}
	
	/// SDK после возврата основного окна приложения.
	func didReturnWindow() {}
	
	/// Метод вызывается при успешной авторизации.
	///
	/// - Parameter type: тип авторизации.
	/// - Parameter isFirstLogin: Первый/повторный вход (для аналитики).
	/// - Parameter isCertificateRenewed: признак того, что сертификат обновился
	func didLogin(type: CerberSDK.LoginOptions, isFirstLogin: Bool, isCertificateRenewed: Bool) {
		guard isAuthSuccess == false || isCertificateRenewed else { return }
		if isCertificateRenewed || isFirstLogin {
			getSecTask?.cancel()
			getSecTask = nil
		}
		guard getSecTask == nil else { return }
		getSecTask = Task { @MainActor [weak self] in
			do {
				let dic = try await Cerber.shared.getCertificate()
				let identity = dic[CerberUsageKeys.kIdentityKey]
				if identity != nil {
					// Заменяем пользовательский Certificate and SecIdentity
					await AppCoordinator.shared.update(
						data: dic[CerberUsageKeys.kCertificateChainKey] as? Data,
						identity: (identity as! SecIdentity)
					)
					self?.isAuthSuccess = true
				}
				self?.registrationCompleted = true
				if isCertificateRenewed {
					DynatraceManager.shared.logActionWith(name: "cerber certificate renewed")
				}
				if !(self?.dataDeleting ?? true) {
					await AppCoordinator.shared.startAuthorization()
				}
			} catch {
				AppCoordinator.shared.doNotUserCertificate(cerberError: error)
			}
		}
	}
	
	/// Метод вызывается при неуспешной авторизации. Добавили для сбора аналитики
	///
	/// - Parameter errorCode: Код ошибки.
	/// - Parameter errorDescription: Описание ошибки.
	/// - Parameter isFirstLogin: Первый/повторный вход (для аналитики).
	func didFailLogin(errorCode: String, errorDescription: String, isFirstLogin: Bool) {
		getSecTask?.cancel()
		getSecTask = nil
		DynatraceManager.shared.logError(
			nil,
			forNetwork: "errorCode: \(errorCode), errorDescription: \(errorDescription)",
			to: "Cerber.didFailLogin"
		)
		let definition: String = "Код ошибки: \(errorCode)\nОписание ошибки: \(errorDescription)"
			
		Task { @MainActor in
			AppCoordinator.shared.doNotUserCertificate(
				cerberError: NSError(domain: definition, code: -300)
			)
		}
	}
	
	func didChangeDeviceType(type: CerberSDK.DeviceType, completion: @escaping () -> Void) {
		completion()
	}
	
	/// Метод вызывается при успешном обновлении токена авторизации.
	///
	/// - Parameter token: обновленный токен.
	func didRefreshTeamIDToken(token: String) {
	}
	
	/// Необходима очистка данных приложения.
	///
	/// - Parameter reason: Причина разлогина ( для аналитики ).
	func wipe(reason: String) {
		DynatraceManager.shared.logError(
			nil,
			forNetwork: "wipe reason: \(reason)",
			to: "Cerber.wipe"
		)
		clear { }
	}
	
	/// Необходима очистка данных пользователя.
	func userChanged(completion: @escaping () -> Void) {
		clear(completion)
	}
}