import ActivityKit
import BackgroundTasks
import CoreData
import Foundation
import SwiftUI
import Swinject

@main struct FreeAPSApp: App {
    @Environment(\.scenePhase) var scenePhase

    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    // Read the color scheme preference from UserDefaults; defaults to system default setting
    @AppStorage("colorSchemePreference") private var colorSchemePreference: ColorSchemeOption = .systemDefault

    let coreDataStack = CoreDataStack.shared

    // Dependencies Assembler
    // contain all dependencies Assemblies
    // TODO: Remove static key after update "Use Dependencies" logic
    private static var assembler = Assembler([
        StorageAssembly(),
        ServiceAssembly(),
        APSAssembly(),
        NetworkAssembly(),
        UIAssembly(),
        SecurityAssembly()
    ], parent: nil, defaultObjectScope: .container)

    var resolver: Resolver {
        FreeAPSApp.assembler.resolver
    }

    // Temp static var
    // Use to backward compatibility with old Dependencies logic on Logger
    // TODO: Remove var after update "Use Dependencies" logic in Logger
    static var resolver: Resolver {
        FreeAPSApp.assembler.resolver
    }

    private func loadServices() {
        resolver.resolve(AppearanceManager.self)!.setupGlobalAppearance()
        _ = resolver.resolve(DeviceDataManager.self)!
        _ = resolver.resolve(APSManager.self)!
        _ = resolver.resolve(FetchGlucoseManager.self)!
        _ = resolver.resolve(FetchTreatmentsManager.self)!
        _ = resolver.resolve(FetchAnnouncementsManager.self)!
        _ = resolver.resolve(CalendarManager.self)!
        _ = resolver.resolve(UserNotificationsManager.self)!
        _ = resolver.resolve(WatchManager.self)!
        _ = resolver.resolve(HealthKitManager.self)!
        _ = resolver.resolve(BluetoothStateManager.self)!
        _ = resolver.resolve(PluginManager.self)!
        if #available(iOS 16.2, *) {
            _ = resolver.resolve(LiveActivityBridge.self)!
        }
    }

    init() {
        debug(
            .default,
            "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(BuildDetails.default.buildDate())] [buildExpires: \(BuildDetails.default.calculateExpirationDate())]"
        )
        loadServices()

        // Clear the persistentHistory and the NSManagedObjects that are older than 90 days every time the app starts
        cleanupOldData()
    }

    var body: some Scene {
        WindowGroup {
            Main.RootView(resolver: resolver)
                .preferredColorScheme(colorScheme(for: colorSchemePreference ?? .systemDefault) ?? nil)
                .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
                .environmentObject(Icons())
                .onOpenURL(perform: handleURL)
        }
        .onChange(of: scenePhase) { newScenePhase in
            debug(.default, "APPLICATION PHASE: \(newScenePhase)")

            /// If the App goes to the background we should ensure that all the changes are saved from the viewContext to the Persistent Container
            if newScenePhase == .background {
                coreDataStack.save()
            }
        }
        .backgroundTask(.appRefresh("com.openiaps.cleanup")) {
            await scheduleDatabaseCleaning()
            await cleanupOldData()
        }
    }

    private func colorScheme(for colorScheme: ColorSchemeOption) -> ColorScheme? {
        switch colorScheme {
        case .systemDefault:
            return nil // Uses the system theme.
        case .light:
            return .light
        case .dark:
            return .dark
        }
    }

    func scheduleDatabaseCleaning() {
        let request = BGAppRefreshTaskRequest(identifier: "com.openiaps.cleanup")
        request.earliestBeginDate = .now.addingTimeInterval(7 * 24 * 60 * 60) // 7 days
        do {
            try BGTaskScheduler.shared.submit(request)
            debugPrint("Task scheduled successfully")
        } catch {
            debugPrint("Failed to schedule tasks")
        }
    }

    private func cleanupOldData() {
        Task {
            async let cleanupTokens: () = coreDataStack.cleanupPersistentHistoryTokens(before: Date.oneWeekAgo)
            async let purgeData: () = purgeOldNSManagedObjects()

            await cleanupTokens
            try await purgeData
        }
    }

    private func purgeOldNSManagedObjects() async throws {
        async let glucoseDeletion: () = coreDataStack.batchDeleteOlderThan(GlucoseStored.self, dateKey: "date", days: 90)
        async let pumpEventDeletion: () = coreDataStack.batchDeleteOlderThan(PumpEventStored.self, dateKey: "timestamp", days: 90)
        async let bolusDeletion: () = coreDataStack.batchDeleteOlderThan(
            parentType: PumpEventStored.self,
            childType: BolusStored.self,
            dateKey: "timestamp",
            days: 90,
            relationshipKey: "pumpEvent"
        )
        async let tempBasalDeletion: () = coreDataStack.batchDeleteOlderThan(
            parentType: PumpEventStored.self,
            childType: TempBasalStored.self,
            dateKey: "timestamp",
            days: 90,
            relationshipKey: "pumpEvent"
        )
        async let determinationDeletion: () = coreDataStack
            .batchDeleteOlderThan(OrefDetermination.self, dateKey: "deliverAt", days: 90)
        async let batteryDeletion: () = coreDataStack.batchDeleteOlderThan(OpenAPS_Battery.self, dateKey: "date", days: 90)
        async let carbEntryDeletion: () = coreDataStack.batchDeleteOlderThan(CarbEntryStored.self, dateKey: "date", days: 90)
        async let forecastDeletion: () = coreDataStack.batchDeleteOlderThan(Forecast.self, dateKey: "date", days: 2)
        async let forecastValueDeletion: () = coreDataStack.batchDeleteOlderThan(
            parentType: Forecast.self,
            childType: ForecastValue.self,
            dateKey: "date",
            days: 2,
            relationshipKey: "forecast"
        )
        async let overrideDeletion: () = coreDataStack
            .batchDeleteOlderThan(OverrideStored.self, dateKey: "date", days: 3, isPresetKey: "isPreset")
        async let overrideRunDeletion: () = coreDataStack
            .batchDeleteOlderThan(OverrideRunStored.self, dateKey: "startDate", days: 3)

        // Await each task to ensure they are all completed
        try await glucoseDeletion
        try await pumpEventDeletion
        try await bolusDeletion
        try await tempBasalDeletion
        try await determinationDeletion
        try await batteryDeletion
        try await carbEntryDeletion
        try await forecastDeletion
        try await forecastValueDeletion
        try await overrideDeletion
        try await overrideRunDeletion
    }

    private func handleURL(_ url: URL) {
        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)

        switch components?.host {
        case "device-select-resp":
            resolver.resolve(NotificationCenter.self)!.post(name: .openFromGarminConnect, object: url)
        default: break
        }
    }
}
