import SwiftUI
import SharedModels
import SwiftData
import WidgetKit
import BackgroundTasks

@MainActor
private enum CloudPreviewContainer {
    static let value: ModelContainer = {
        let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
        return try! ModelContainer(
            for: ReviewCard.self, CourseReviewCard.self, UserProgress.self,
                 TestResult.self, DailyLog.self, SavedSong.self, Story.self, Conversation.self,
            configurations: configuration
        )
    }()
}

typealias CloudModelContextProvider = @MainActor @Sendable () -> ModelContext
private let appRefreshTaskIdentifier = "com.conjuga.app.refresh"

private struct CloudModelContextProviderKey: EnvironmentKey {
    static let defaultValue: CloudModelContextProvider = {
        CloudPreviewContainer.value.mainContext
    }
}

extension EnvironmentValues {
    var cloudModelContextProvider: CloudModelContextProvider {
        get { self[CloudModelContextProviderKey.self] }
        set { self[CloudModelContextProviderKey.self] = newValue }
    }
}

@main
struct ConjugaApp: App {
    @AppStorage("onboardingComplete") private var onboardingComplete = false
    @Environment(\.scenePhase) private var scenePhase
    @State private var isReady = true
    @State private var syncMonitor = SyncStatusMonitor()
    @State private var studyTimer = StudyTimerService()
    @State private var dictionary = DictionaryService()
    @State private var verbExampleCache = VerbExampleCache()
    @State private var reflexiveStore = ReflexiveVerbStore()
    @State private var youtubeVideoStore = YouTubeVideoStore()

    let localContainer: ModelContainer
    let cloudContainer: ModelContainer

    init() {
        guard let localURL = SharedStore.localStoreURL() else {
            fatalError("App group 'group.com.conjuga.app' is not accessible. Check entitlements and provisioning profile.")
        }

        // One-time force-reset of the local store to clear stale schema metadata
        // accumulated from previous container configurations.
        Self.performOneTimeLocalStoreResetIfNeeded(at: localURL)

        // DIAGNOSTIC: what's in the store file BEFORE we open it via SwiftData?
        StoreInspector.dump(at: localURL, label: "before-open")

        do {
            localContainer = try Self.makeValidatedLocalContainer(at: localURL)
            SharedStore.localContainer = localContainer

            // DIAGNOSTIC: what's in the store file AFTER SwiftData opened it?
            StoreInspector.dump(at: localURL, label: "after-open")
            print("[ConjugaApp] localContainer identity: \(ObjectIdentifier(localContainer))")

            let cloudConfig = ModelConfiguration(
                "cloud",
                schema: Schema([
                    ReviewCard.self, CourseReviewCard.self, VerbReviewCard.self, UserProgress.self,
                    TestResult.self, DailyLog.self, SavedSong.self, Story.self, Conversation.self,
                    TextbookExerciseAttempt.self, ExtraStudyMark.self, VocabStudyGroup.self,
                    LexemeReviewCard.self, LexemeStudyGroup.self,
                ]),
                cloudKitDatabase: .private("iCloud.com.conjuga.app")
            )
            cloudContainer = try ModelContainer(
                for: ReviewCard.self, CourseReviewCard.self, VerbReviewCard.self, UserProgress.self,
                     TestResult.self, DailyLog.self, SavedSong.self, Story.self, Conversation.self,
                     TextbookExerciseAttempt.self, ExtraStudyMark.self, VocabStudyGroup.self,
                     LexemeReviewCard.self, LexemeStudyGroup.self,
                configurations: cloudConfig
            )

        } catch {
            fatalError("Failed to create ModelContainer: \(error)")
        }
    }

    var body: some Scene {
        WindowGroup {
            Group {
                if !isReady {
                    VStack(spacing: 16) {
                        ProgressView()
                            .controlSize(.large)
                        Text("Preparing data...")
                            .foregroundStyle(.secondary)
                    }
                } else if onboardingComplete {
                    MainTabView()
                } else {
                    OnboardingView()
                }
            }
            .overlay(alignment: .bottom) {
                if syncMonitor.shouldShowToast {
                    SyncToast()
                        .padding(.bottom, 100)
                        .transition(.move(edge: .bottom).combined(with: .opacity))
                }
            }
            .animation(.spring(duration: 0.35), value: syncMonitor.shouldShowToast)
            .environment(syncMonitor)
            .environment(\.cloudModelContextProvider, { cloudContainer.mainContext })
            .environment(studyTimer)
            .environment(dictionary)
            .environment(verbExampleCache)
            .environment(reflexiveStore)
            .environment(youtubeVideoStore)
            .task {
                let needsSeed = await DataLoader.needsSeeding(container: localContainer)
                if needsSeed {
                    isReady = false
                }

                await StartupCoordinator.bootstrap(localContainer: localContainer)

                if !isReady {
                    isReady = true
                }

                Task { @MainActor in
                    dictionary.buildIfNeeded(context: localContainer.mainContext)
                }

                Task { @MainActor in
                    syncMonitor.beginSync()
                    await StartupCoordinator.runMaintenance(
                        localContainer: localContainer,
                        cloudContainer: cloudContainer
                    )
                    // Reset a broken streak immediately on launch so the
                    // dashboard never shows a stale number even if the user
                    // hasn't navigated to it yet.
                    let progress = ReviewStore.fetchOrCreateUserProgress(context: cloudContainer.mainContext)
                    progress.validateStreakIfStale(context: cloudContainer.mainContext)
                    WidgetDataService.update(
                        localContainer: localContainer,
                        cloudContainer: cloudContainer
                    )
                    syncMonitor.endSync()
                }
            }
            .onChange(of: scenePhase) { _, newPhase in
                switch newPhase {
                case .active:
                    studyTimer.start()
                case .inactive, .background:
                    studyTimer.stop(context: cloudContainer.mainContext)
                @unknown default:
                    break
                }

                if newPhase == .background {
                    WidgetDataService.update(
                        localContainer: localContainer,
                        cloudContainer: cloudContainer
                    )
                    Self.scheduleAppRefresh()
                }
            }
        }
        .modelContainer(localContainer)
        .backgroundTask(.appRefresh(appRefreshTaskIdentifier)) {
            Self.scheduleAppRefresh()
            await refreshWidgetData()
        }
    }

    @MainActor
    private func refreshWidgetData() async {
        WidgetDataService.update(
            localContainer: localContainer,
            cloudContainer: cloudContainer
        )
        WidgetCenter.shared.reloadAllTimelines()
    }

    nonisolated static func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: appRefreshTaskIdentifier)
        // Minimum delay — system decides actual run time based on usage patterns.
        // We want the widget refreshed before the user typically opens the app.
        request.earliestBeginDate = Calendar.current.startOfDay(
            for: Calendar.current.date(byAdding: .day, value: 1, to: Date())!
        )

        do {
            try BGTaskScheduler.shared.submit(request)
        } catch {
            print("Failed to schedule app refresh: \(error)")
        }
    }

    private static func makeValidatedLocalContainer(at url: URL) throws -> ModelContainer {
        let container = try makeLocalContainer(at: url)
        if localStoreIsUsable(container: container) {
            return container
        }

        deleteStoreFiles(at: url)
        UserDefaults.standard.removeObject(forKey: "courseDataVersion")
        print("[ConjugaApp] ⚠️ Reset corrupted local reference store — this triggers full re-seed")

        return try makeLocalContainer(at: url)
    }

    private static func makeLocalContainer(at url: URL) throws -> ModelContainer {
        // Built from the single shared model list so the app and the widget
        // extension always open the store with an identical schema.
        let schema = Schema(SharedStore.localSchemaModels)
        let localConfig = ModelConfiguration(
            "local",
            schema: schema,
            url: url,
            cloudKitDatabase: .none
        )
        return try ModelContainer(for: schema, configurations: localConfig)
    }

    private static func localStoreIsUsable(container: ModelContainer) -> Bool {
        let context = ModelContext(container)
        do {
            _ = try context.fetchCount(FetchDescriptor<Verb>())
            return true
        } catch {
            print("Local reference store validation failed: \(error)")
            return false
        }
    }

    private static func deleteStoreFiles(at url: URL) {
        let fileManager = FileManager.default
        for suffix in ["", "-wal", "-shm"] {
            let candidateURL = URL(fileURLWithPath: url.path + suffix)
            guard fileManager.fileExists(atPath: candidateURL.path) else { continue }
            try? fileManager.removeItem(at: candidateURL)
        }
    }

    /// One-time nuclear reset of the local reference store.
    /// Clears accumulated stale schema metadata from previous container configurations.
    /// Bump the version number to force another reset if the schema changes again.
    private static func performOneTimeLocalStoreResetIfNeeded(at url: URL) {
        let resetVersion = 6  // bump: Lexeme added to local container
        let key = "localStoreResetVersion"
        let defaults = UserDefaults.standard

        guard defaults.integer(forKey: key) < resetVersion else { return }

        print("[ConjugaApp] Performing one-time local store reset (v\(resetVersion))")
        deleteStoreFiles(at: url)

        // Clear any version flags that gate seeding so everything re-seeds cleanly.
        defaults.removeObject(forKey: "courseDataVersion")
        defaults.removeObject(forKey: "courseProgressMigrationVersion")

        defaults.set(resetVersion, forKey: key)
    }

}
