import Foundation
import WidgetKit
import ComposeApp

/// Manages shared data between the main app and the widget extension
/// Uses App Group container to share files
final class WidgetDataManager {
    static let shared = WidgetDataManager()

    /// Tracks the last time `reloadAllTimelines()` was called for debouncing
    private static var lastReloadTime: Date = .distantPast
    /// Minimum interval between `reloadAllTimelines()` calls (seconds)
    private static let reloadDebounceInterval: TimeInterval = 2.0

    // MARK: - API Column Names (Single Source of Truth)
    // These match the column names returned by the API's task columns endpoint
    static let overdueColumn = "overdue_tasks"
    static let dueWithin7DaysColumn = "due_soon_tasks"
    static let due8To30DaysColumn = "upcoming_tasks"
    static let completedColumn = "completed_tasks"
    static let cancelledColumn = "cancelled_tasks"

    private let appGroupIdentifier: String = {
        Bundle.main.infoDictionary?["AppGroupIdentifier"] as? String ?? "group.com.tt.honeyDue.dev"
    }()
    private let tasksFileName = "widget_tasks.json"
    private let actionsFileName = "widget_pending_actions.json"
    private let pendingTasksFileName = "widget_pending_tasks.json"
    private let tokenKey = "widget_auth_token"
    private let dirtyFlagKey = "widget_tasks_dirty"
    private let apiBaseURLKey = "widget_api_base_url"
    private let limitationsEnabledKey = "widget_limitations_enabled"
    private let isPremiumKey = "widget_is_premium"

    /// Serial queue for thread-safe file I/O operations
    private let fileQueue = DispatchQueue(label: "com.honeydue.widget.fileio")

    private var sharedDefaults: UserDefaults? {
        UserDefaults(suiteName: appGroupIdentifier)
    }

    private init() {}

    /// Reload all widget timelines, debounced to avoid excessive reloads.
    /// Only triggers a reload if at least `reloadDebounceInterval` has elapsed since the last reload.
    private func reloadWidgetTimelinesIfNeeded() {
        let now = Date()
        if now.timeIntervalSince(Self.lastReloadTime) >= Self.reloadDebounceInterval {
            Self.lastReloadTime = now
            WidgetCenter.shared.reloadAllTimelines()
        }
    }

    // MARK: - Auth Token Sharing

    /// Save auth token to shared App Group for widget access
    /// Call this after successful login or when token is refreshed
    func saveAuthToken(_ token: String) {
        sharedDefaults?.set(token, forKey: tokenKey)
        print("WidgetDataManager: Saved auth token to shared container")
    }

    /// Get auth token from shared App Group
    /// Used by widget to authenticate API requests
    func getAuthToken() -> String? {
        return sharedDefaults?.string(forKey: tokenKey)
    }

    /// Clear auth token from shared App Group
    /// Call this on logout
    func clearAuthToken() {
        sharedDefaults?.removeObject(forKey: tokenKey)
        print("WidgetDataManager: Cleared auth token from shared container")
    }

    /// Save API base URL to shared container for widget
    func saveAPIBaseURL(_ url: String) {
        sharedDefaults?.set(url, forKey: apiBaseURLKey)
    }

    /// Get API base URL from shared container
    func getAPIBaseURL() -> String? {
        return sharedDefaults?.string(forKey: apiBaseURLKey)
    }

    // MARK: - Subscription Status Sharing

    /// Save subscription status for widget to determine which view to show
    /// Call this when subscription status changes
    func saveSubscriptionStatus(limitationsEnabled: Bool, isPremium: Bool) {
        sharedDefaults?.set(limitationsEnabled, forKey: limitationsEnabledKey)
        sharedDefaults?.set(isPremium, forKey: isPremiumKey)
        print("WidgetDataManager: Saved subscription status - limitations=\(limitationsEnabled), premium=\(isPremium)")
        // Reload widget to reflect new subscription status
        WidgetCenter.shared.reloadAllTimelines()
    }

    /// Check if limitations are enabled (from backend)
    func areLimitationsEnabled() -> Bool {
        return sharedDefaults?.bool(forKey: limitationsEnabledKey) ?? false
    }

    /// Check if user has premium subscription
    func isPremium() -> Bool {
        return sharedDefaults?.bool(forKey: isPremiumKey) ?? false
    }

    /// Check if widget should show interactive features
    /// Returns true if: limitations disabled OR user is premium
    func shouldShowInteractiveWidget() -> Bool {
        let limitationsEnabled = areLimitationsEnabled()
        let premium = isPremium()
        // Interactive if limitations are off, or if user is premium
        return !limitationsEnabled || premium
    }

    // MARK: - Dirty Flag for Task Refresh

    /// Mark tasks as dirty (needs refresh from server)
    /// Called by widget after completing a task
    func markTasksDirty() {
        sharedDefaults?.set(true, forKey: dirtyFlagKey)
        print("WidgetDataManager: Marked tasks as dirty")
    }

    /// Check if tasks need refresh
    func areTasksDirty() -> Bool {
        return sharedDefaults?.bool(forKey: dirtyFlagKey) ?? false
    }

    /// Clear dirty flag after refreshing tasks
    func clearDirtyFlag() {
        sharedDefaults?.set(false, forKey: dirtyFlagKey)
        print("WidgetDataManager: Cleared dirty flag")
    }

    // MARK: - Widget Action Types (must match AppIntent.swift in widget extension)

    enum WidgetAction: Codable, Equatable {
        case completeTask(taskId: Int, taskTitle: String)

        var taskId: Int {
            switch self {
            case .completeTask(let id, _):
                return id
            }
        }

        var taskTitle: String {
            switch self {
            case .completeTask(_, let title):
                return title
            }
        }
    }

    // MARK: - Pending Action Processing

    /// Load pending actions queued by the widget (async, non-blocking)
    func loadPendingActions() async -> [WidgetAction] {
        guard let fileURL = sharedContainerURL?.appendingPathComponent(actionsFileName) else {
            return []
        }

        return await withCheckedContinuation { continuation in
            fileQueue.async {
                guard FileManager.default.fileExists(atPath: fileURL.path) else {
                    continuation.resume(returning: [])
                    return
                }

                do {
                    let data = try Data(contentsOf: fileURL)
                    let actions = try JSONDecoder().decode([WidgetAction].self, from: data)
                    continuation.resume(returning: actions)
                } catch {
                    print("WidgetDataManager: Error loading pending actions - \(error)")
                    continuation.resume(returning: [])
                }
            }
        }
    }

    /// Load pending actions synchronously (blocks calling thread).
    /// Prefer the async overload from the main app. This is kept for widget extension use.
    func loadPendingActionsSync() -> [WidgetAction] {
        guard let fileURL = sharedContainerURL?.appendingPathComponent(actionsFileName) else {
            return []
        }

        return fileQueue.sync {
            guard FileManager.default.fileExists(atPath: fileURL.path) else {
                return []
            }

            do {
                let data = try Data(contentsOf: fileURL)
                return try JSONDecoder().decode([WidgetAction].self, from: data)
            } catch {
                print("WidgetDataManager: Error loading pending actions - \(error)")
                return []
            }
        }
    }

    /// Clear all pending actions after processing
    func clearPendingActions() {
        guard let fileURL = sharedContainerURL?.appendingPathComponent(actionsFileName) else { return }

        fileQueue.async {
            do {
                try FileManager.default.removeItem(at: fileURL)
                print("WidgetDataManager: Cleared pending actions")
            } catch {
                // File might not exist
            }
        }
    }

    /// Remove a specific action after processing
    func removeAction(_ action: WidgetAction) {
        guard let fileURL = sharedContainerURL?.appendingPathComponent(actionsFileName) else { return }

        fileQueue.async {
            // Load actions within the serial queue to avoid race conditions
            var actions: [WidgetAction]
            if FileManager.default.fileExists(atPath: fileURL.path) {
                do {
                    let data = try Data(contentsOf: fileURL)
                    actions = try JSONDecoder().decode([WidgetAction].self, from: data)
                } catch {
                    print("WidgetDataManager: Failed to decode pending actions: \(error)")
                    actions = []
                }
            } else {
                actions = []
            }

            actions.removeAll { $0 == action }

            if actions.isEmpty {
                try? FileManager.default.removeItem(at: fileURL)
            } else {
                do {
                    let data = try JSONEncoder().encode(actions)
                    try data.write(to: fileURL, options: .atomic)
                } catch {
                    print("WidgetDataManager: Error saving actions - \(error)")
                }
            }
        }
    }

    /// Clear pending state for a task after it's been synced
    func clearPendingState(forTaskId taskId: Int) {
        guard let fileURL = sharedContainerURL?.appendingPathComponent(pendingTasksFileName) else {
            return
        }

        struct PendingTaskState: Codable {
            let taskId: Int
            let pendingAction: String
            let timestamp: Date
        }

        fileQueue.async {
            guard FileManager.default.fileExists(atPath: fileURL.path) else {
                return
            }

            do {
                let data = try Data(contentsOf: fileURL)
                var states = try JSONDecoder().decode([PendingTaskState].self, from: data)
                states.removeAll { $0.taskId == taskId }

                if states.isEmpty {
                    try FileManager.default.removeItem(at: fileURL)
                } else {
                    let updatedData = try JSONEncoder().encode(states)
                    try updatedData.write(to: fileURL, options: .atomic)
                }
            } catch {
                print("WidgetDataManager: Error clearing pending state - \(error)")
            }

            // Reload widget to reflect the change
            DispatchQueue.main.async {
                WidgetCenter.shared.reloadTimelines(ofKind: "honeyDue")
            }
        }
    }

    /// Check if there are any pending actions from the widget
    var hasPendingActions: Bool {
        !loadPendingActionsSync().isEmpty
    }

    /// Task model for widget display - simplified version of TaskDetail
    struct WidgetTask: Codable {
        let id: Int
        let title: String
        let description: String?
        let priority: String?
        let inProgress: Bool
        let dueDate: String?
        let category: String?
        let residenceName: String?
        let isOverdue: Bool
        let isDueWithin7Days: Bool
        let isDue8To30Days: Bool

        enum CodingKeys: String, CodingKey {
            case id, title, description, priority, category
            case inProgress = "in_progress"
            case dueDate = "due_date"
            case residenceName = "residence_name"
            case isOverdue = "is_overdue"
            case isDueWithin7Days = "is_due_within_7_days"
            case isDue8To30Days = "is_due_8_to_30_days"
        }
    }

    /// Metrics calculated from an array of tasks - shared between app and widget
    struct TaskMetrics {
        let totalCount: Int
        let overdueCount: Int
        let upcoming7Days: Int
        let upcoming30Days: Int
    }

    /// Calculate metrics from an array of WidgetTasks
    /// This is the SINGLE SOURCE OF TRUTH for all task metrics
    /// Used by: dashboard summary, residence cards, widget
    static func calculateMetrics(from tasks: [WidgetTask]) -> TaskMetrics {
        var overdueCount = 0
        var upcoming7Days = 0
        var upcoming30Days = 0

        for task in tasks {
            if task.isOverdue { overdueCount += 1 }
            if task.isDueWithin7Days { upcoming7Days += 1 }
            if task.isDue8To30Days { upcoming30Days += 1 }
        }

        return TaskMetrics(
            totalCount: tasks.count,
            overdueCount: overdueCount,
            upcoming7Days: upcoming7Days,
            upcoming30Days: upcoming30Days
        )
    }

    /// Parse a date string (handles both "yyyy-MM-dd" and ISO datetime formats)
    /// Extracts date part if it includes time (e.g., "2025-12-26T00:00:00Z" -> "2025-12-26")
    private static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()

    static func parseDate(_ dateString: String?) -> Date? {
        guard let dateString = dateString, !dateString.isEmpty else { return nil }
        // Extract date part if it includes time
        let datePart = dateString.components(separatedBy: "T").first ?? dateString
        return dateFormatter.date(from: datePart)
    }

    /// Get the shared App Group container URL
    private var sharedContainerURL: URL? {
        FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
    }

    /// Get the URL for the tasks file
    private var tasksFileURL: URL? {
        sharedContainerURL?.appendingPathComponent(tasksFileName)
    }

    /// Save tasks to the shared container for widget access
    func saveTasks(from response: TaskColumnsResponse) {
        guard let fileURL = tasksFileURL else {
            print("WidgetDataManager: Unable to access shared container")
            return
        }

        // Columns to exclude from widget (these are "done" states)
        let excludedColumns = [Self.completedColumn, Self.cancelledColumn]

        // Date calculation setup
        let today = Calendar.current.startOfDay(for: Date())

        // Extract tasks from active columns only and convert to WidgetTask
        var allTasks: [WidgetTask] = []

        for column in response.columns {
            // Skip completed and cancelled columns
            if excludedColumns.contains(column.name) {
                continue
            }

            // isOverdue is based on column (API correctly calculates this)
            let isOverdue = column.name == Self.overdueColumn

            for task in column.tasks {
                // Calculate isDueWithin7Days and isDue8To30Days from actual due date
                var isDueWithin7Days = false
                var isDue8To30Days = false

                if let dueDate = Self.parseDate(task.effectiveDueDate) {
                    let dueDay = Calendar.current.startOfDay(for: dueDate)
                    let daysUntilDue = Calendar.current.dateComponents([.day], from: today, to: dueDay).day ?? 0

                    // Only count future tasks (not overdue)
                    if daysUntilDue >= 0 && daysUntilDue <= 7 {
                        isDueWithin7Days = true
                    } else if daysUntilDue > 7 && daysUntilDue <= 30 {
                        isDue8To30Days = true
                    }
                }

                let widgetTask = WidgetTask(
                    id: Int(task.id),
                    title: task.title,
                    description: task.description_,
                    priority: task.priorityName ?? "",
                    inProgress: task.inProgress,
                    dueDate: task.effectiveDueDate,
                    category: task.categoryName ?? "",
                    residenceName: "",
                    isOverdue: isOverdue,
                    isDueWithin7Days: isDueWithin7Days,
                    isDue8To30Days: isDue8To30Days
                )
                allTasks.append(widgetTask)
            }
        }

        fileQueue.async {
            do {
                let encoder = JSONEncoder()
                encoder.outputFormatting = .prettyPrinted
                let data = try encoder.encode(allTasks)
                try data.write(to: fileURL, options: .atomic)
                print("WidgetDataManager: Saved \(allTasks.count) tasks to widget cache")
            } catch {
                print("WidgetDataManager: Error saving tasks - \(error)")
            }

            // Reload widget timeline (debounced) after file write completes
            DispatchQueue.main.async {
                self.reloadWidgetTimelinesIfNeeded()
            }
        }
    }

    /// Load tasks from the shared container (async, non-blocking)
    func loadTasks() async -> [WidgetTask] {
        guard let fileURL = tasksFileURL else {
            print("WidgetDataManager: Unable to access shared container")
            return []
        }

        return await withCheckedContinuation { continuation in
            fileQueue.async {
                guard FileManager.default.fileExists(atPath: fileURL.path) else {
                    print("WidgetDataManager: No cached tasks file found")
                    continuation.resume(returning: [])
                    return
                }

                do {
                    let data = try Data(contentsOf: fileURL)
                    let tasks = try JSONDecoder().decode([WidgetTask].self, from: data)
                    print("WidgetDataManager: Loaded \(tasks.count) tasks from widget cache")
                    continuation.resume(returning: tasks)
                } catch {
                    print("WidgetDataManager: Error loading tasks - \(error)")
                    continuation.resume(returning: [])
                }
            }
        }
    }

    /// Load tasks synchronously (blocks calling thread).
    /// Prefer the async overload from the main app. This is kept for widget extension use.
    func loadTasksSync() -> [WidgetTask] {
        guard let fileURL = tasksFileURL else {
            print("WidgetDataManager: Unable to access shared container")
            return []
        }

        return fileQueue.sync {
            guard FileManager.default.fileExists(atPath: fileURL.path) else {
                print("WidgetDataManager: No cached tasks file found")
                return []
            }

            do {
                let data = try Data(contentsOf: fileURL)
                let tasks = try JSONDecoder().decode([WidgetTask].self, from: data)
                print("WidgetDataManager: Loaded \(tasks.count) tasks from widget cache")
                return tasks
            } catch {
                print("WidgetDataManager: Error loading tasks - \(error)")
                return []
            }
        }
    }

    /// Get upcoming/pending tasks for widget display
    /// Uses synchronous loading since this is typically called from widget timeline providers
    func getUpcomingTasks() -> [WidgetTask] {
        let allTasks = loadTasksSync()

        // All loaded tasks are already filtered (archived and completed columns are excluded during save)
        // Sort by due date (earliest first), with overdue at top
        return allTasks.sorted { task1, task2 in
            // Overdue tasks first
            if task1.isOverdue != task2.isOverdue {
                return task1.isOverdue
            }

            // Then by due date
            guard let date1 = task1.dueDate, let date2 = task2.dueDate else {
                return task1.dueDate != nil
            }
            return date1 < date2
        }
    }

    /// Clear cached task data (call on logout)
    func clearCache() {
        guard let fileURL = tasksFileURL else { return }

        fileQueue.async {
            do {
                try FileManager.default.removeItem(at: fileURL)
                print("WidgetDataManager: Cleared widget cache")
            } catch {
                print("WidgetDataManager: Error clearing cache - \(error)")
            }

            DispatchQueue.main.async {
                WidgetCenter.shared.reloadAllTimelines()
            }
        }
    }

}
