import BackgroundTasks
import UIKit
import UserNotifications

@MainActor
final class NotificationCoordinator: NSObject, @preconcurrency UNUserNotificationCenterDelegate {
    static let refreshIdentifier = "de.rfc1437.gotcha.notifications.refresh"

    private unowned let context: AppContext
    private let center = UNUserNotificationCenter.current()
    private var timer: Timer?
    private var pollingTask: Task<Void, Never>?
#if DEBUG
    private let validatesBackgroundNotifications = ProcessInfo.processInfo.arguments.contains(
        "--validate-background-notifications"
    )
#endif

    init(context: AppContext) {
        self.context = context
    }

    func start() {
        center.delegate = self
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: Self.refreshIdentifier,
            using: nil
        ) { [weak self] task in
            Task { @MainActor in
                guard let self, let task = task as? BGAppRefreshTask else {
                    task.setTaskCompleted(success: false)
                    return
                }
                self.handle(task)
            }
        }
        timer = Timer.scheduledTimer(withTimeInterval: 5 * 60, repeats: true) {
            [weak self] _ in
            Task { @MainActor in self?.refresh(deliverAlerts: false) }
        }
    }

    func applicationDidBecomeActive() {
#if DEBUG
        guard !validatesBackgroundNotifications else { return }
#endif
        refresh(deliverAlerts: false)
    }

    func applicationDidEnterBackground() {
        scheduleBackgroundRefresh()
#if DEBUG
        guard validatesBackgroundNotifications else { return }
        let identifier = UIApplication.shared.beginBackgroundTask()
        pollingTask?.cancel()
        pollingTask = Task {
            defer { UIApplication.shared.endBackgroundTask(identifier) }
            try? await poll(deliverAlerts: true)
        }
#endif
    }

    func setEnabled(_ enabled: Bool) async throws -> Bool {
        if !enabled {
            try context.core.setNotificationsEnabled(enabled: false)
            BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: Self.refreshIdentifier)
            center.removeAllPendingNotificationRequests()
            return false
        }

        var settings = await center.notificationSettings()
        if settings.authorizationStatus == .notDetermined {
            _ = try await center.requestAuthorization(options: [.alert, .sound])
            settings = await center.notificationSettings()
        }
        guard Self.isAuthorized(settings.authorizationStatus) else { return false }
        try context.core.setNotificationsEnabled(enabled: true)
        scheduleBackgroundRefresh()
        try await poll(deliverAlerts: false)
        return true
    }

    func authorizationDescription() async -> String {
        switch await center.notificationSettings().authorizationStatus {
        case .notDetermined: return "Not requested"
        case .denied: return "Disabled in iOS Settings"
        case .authorized: return "Allowed"
        case .provisional: return "Delivered quietly"
        case .ephemeral: return "Allowed temporarily"
        @unknown default: return "Managed by iOS"
        }
    }

    func openSystemSettings() {
        guard let url = URL(string: UIApplication.openNotificationSettingsURLString) else { return }
        UIApplication.shared.open(url)
    }

    private func refresh(deliverAlerts: Bool) {
        pollingTask?.cancel()
        pollingTask = Task {
            do {
                let settings = await center.notificationSettings()
                guard
                    context.core.settings().notificationsEnabled,
                    Self.isAuthorized(settings.authorizationStatus)
                else { return }
                try await poll(deliverAlerts: deliverAlerts)
            } catch {
                // Foreground screens surface API errors when the user explicitly refreshes them.
            }
        }
    }

    private func poll(deliverAlerts: Bool) async throws {
        let rows = try await context.core.pollNotifications()
        guard deliverAlerts else { return }
        for row in rows where row.target != .none {
            let content = UNMutableNotificationContent()
            content.title = title(for: row.target)
            content.body = "Open Gotcha to view the update."
            content.sound = .default
            content.threadIdentifier = "gotcha.\(row.serverId)"
            content.userInfo = [
                "serverId": row.serverId,
                "threadId": row.id,
                "target": targetName(row.target),
                "owner": row.owner,
                "repository": row.repository,
                "number": row.number,
                "sha": row.sha,
            ]
            let request = UNNotificationRequest(
                identifier: "gotcha.\(row.serverId).\(row.id)",
                content: content,
                trigger: nil
            )
            try await center.add(request)
        }
    }

    private func scheduleBackgroundRefresh() {
        guard context.core.settings().notificationsEnabled else { return }
        let request = BGAppRefreshTaskRequest(identifier: Self.refreshIdentifier)
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
        try? BGTaskScheduler.shared.submit(request)
    }

    private func handle(_ backgroundTask: BGAppRefreshTask) {
        scheduleBackgroundRefresh()
        pollingTask?.cancel()
        let task = Task {
            do {
                let settings = await center.notificationSettings()
                guard Self.isAuthorized(settings.authorizationStatus) else {
                    backgroundTask.setTaskCompleted(success: true)
                    return
                }
                try await poll(deliverAlerts: true)
                backgroundTask.setTaskCompleted(success: true)
            } catch {
                backgroundTask.setTaskCompleted(success: false)
            }
        }
        pollingTask = task
        backgroundTask.expirationHandler = { task.cancel() }
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification
    ) async -> UNNotificationPresentationOptions {
        []
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse
    ) async {
        let userInfo = response.notification.request.content.userInfo
        context.route(notificationUserInfo: userInfo)
        guard
            let serverId = userInfo["serverId"] as? String,
            let id = userInfo["threadId"] as? NSNumber
        else { return }
        try? await context.core.markNotificationRead(serverId: serverId, id: id.int64Value)
    }

    private static func isAuthorized(_ status: UNAuthorizationStatus) -> Bool {
        status == .authorized || status == .provisional || status == .ephemeral
    }

    private func title(for target: ActivityTargetKind) -> String {
        switch target {
        case .repository: return "New repository notification"
        case .issue: return "New issue notification"
        case .pullRequest: return "New pull request notification"
        case .commit: return "New commit notification"
        case .none: return "New server notification"
        }
    }

    private func targetName(_ target: ActivityTargetKind) -> String {
        switch target {
        case .repository: return "repository"
        case .issue: return "issue"
        case .pullRequest: return "pull"
        case .commit: return "commit"
        case .none: return "none"
        }
    }
}
