import AppKit
import Foundation

/// Displays a permission request dialog with optional caching of user decisions
class PermissionRequest {
    /// Specifies how long a permission decision should be cached
    enum AllowDuration {
        case once
        case forever
        case duration(Duration)
    }

    /// Shows a permission request dialog with customizable caching behavior
    /// - Parameters:
    ///   - key: Unique identifier for storing/retrieving cached decisions in UserDefaults
    ///   - message: The message to display in the alert dialog
    ///   - allowText: Custom text for the allow button (defaults to "Allow")
    ///   - allowDuration: If provided, automatically cache "Allow" responses for this duration
    ///   - rememberDuration: If provided, shows a checkbox to remember the decision for this duration
    ///   - window: If provided, shows the alert as a sheet attached to this window
    ///   - completion: Called with the user's decision (true for allow, false for deny)
    /// 
    /// Caching behavior:
    /// - If rememberDuration is provided and user checks "Remember my decision", both allow/deny are cached for that duration
    /// - If allowDuration is provided and user selects allow (without checkbox), decision is cached for that duration
    /// - Cached decisions are automatically returned without showing the dialog
    @MainActor
    static func show(
        _ key: String,
        message: String,
        informative: String = "",
        allowText: String = "Allow",
        allowDuration: AllowDuration = .once,
        rememberDuration: Duration? = .seconds(86400),
        window: NSWindow? = nil,
        completion: @escaping (Bool) -> Void
    ) {
        // Check if we have a stored decision that hasn't expired
        if let storedResult = getStoredResult(for: key) {
            completion(storedResult)
            return
        }

        let alert = NSAlert()
        alert.messageText = message
        alert.informativeText = informative
        alert.alertStyle = .informational

        // Add buttons (they appear in reverse order)
        alert.addButton(withTitle: allowText)
        alert.addButton(withTitle: "Don't Allow")

        // Create checkbox for remembering if duration is provided
        var checkbox: NSButton?
        if let rememberDuration = rememberDuration {
            let checkboxTitle = formatRememberText(for: rememberDuration)
            checkbox = NSButton(
                checkboxWithTitle: checkboxTitle,
                target: nil,
                action: nil)
            checkbox!.state = .off

            // Set checkbox as accessory view
            alert.accessoryView = checkbox
        }

        // Show the alert
        if let window = window {
            alert.beginSheetModal(for: window) { response in
                handleResponse(response, rememberDecision: checkbox?.state == .on, key: key, allowDuration: allowDuration, rememberDuration: rememberDuration, completion: completion)
            }
        } else {
            let response = alert.runModal()
            handleResponse(response, rememberDecision: checkbox?.state == .on, key: key, allowDuration: allowDuration, rememberDuration: rememberDuration, completion: completion)
        }
    }

    /// Handles the alert response and processes caching logic
    /// - Parameters:
    ///   - response: The alert response from the user
    ///   - rememberDecision: Whether the remember checkbox was checked
    ///   - key: The UserDefaults key for caching
    ///   - allowDuration: Optional duration for auto-caching allow responses
    ///   - rememberDuration: Optional duration for the remember checkbox
    ///   - completion: Completion handler to call with the result
    private static func handleResponse(
        _ response: NSApplication.ModalResponse,
        rememberDecision: Bool,
        key: String,
        allowDuration: AllowDuration,
        rememberDuration: Duration?,
        completion: @escaping (Bool) -> Void) {

        let result: Bool
        switch response {
        case .alertFirstButtonReturn: // Allow
            result = true
        case .alertSecondButtonReturn: // Don't Allow
            result = false
        default:
            result = false
        }

        // Store the result if checkbox is checked or if "Allow" was selected and allowDuration is set
        if rememberDecision, let rememberDuration = rememberDuration {
            storeResult(result, for: key, duration: rememberDuration)
        } else if result {
            switch allowDuration {
            case .once:
                // Don't store anything for once
                break
            case .forever:
                // Store for a very long time (100 years). When the bug comes in that
                // 100 years has passed and their forever permission expired I'll be
                // dead so it won't be my problem.
                storeResult(result, for: key, duration: .seconds(3153600000))
            case .duration(let duration):
                storeResult(result, for: key, duration: duration)
            }
        }

        completion(result)
    }

    /// Retrieves a cached permission decision if it hasn't expired
    /// - Parameter key: The UserDefaults key to check
    /// - Returns: The cached decision, or nil if no valid cached decision exists
    private static func getStoredResult(for key: String) -> Bool? {
        let userDefaults = UserDefaults.standard
        guard let data = userDefaults.data(forKey: key),
              let storedPermission = try? NSKeyedUnarchiver.unarchivedObject(
                ofClass: StoredPermission.self, from: data) else {
            return nil
        }

        if Date() > storedPermission.expiry {
            // Decision has expired, remove stored value
            userDefaults.removeObject(forKey: key)
            return nil
        }

        return storedPermission.result
    }

    /// Stores a permission decision in UserDefaults with an expiration date
    /// - Parameters:
    ///   - result: The permission decision to store
    ///   - key: The UserDefaults key to store under
    ///   - duration: How long the decision should be cached
    private static func storeResult(_ result: Bool, for key: String, duration: Duration) {
        let expiryDate = Date().addingTimeInterval(duration.timeInterval)
        let storedPermission = StoredPermission(result: result, expiry: expiryDate)
        if let data = try? NSKeyedArchiver.archivedData(withRootObject: storedPermission, requiringSecureCoding: true) {
            let userDefaults = UserDefaults.standard
            userDefaults.set(data, forKey: key)
        }
    }

    /// Formats the remember checkbox text based on the duration
    /// - Parameter duration: The duration to format
    /// - Returns: A human-readable string for the checkbox
    private static func formatRememberText(for duration: Duration) -> String {
        let seconds = duration.timeInterval

        // Warning: this probably isn't localization friendly at all so we're
        // going to have to redo this for that.
        switch seconds {
        case 0..<60:
            return "Remember my decision for \(Int(seconds)) seconds"
        case 60..<3600:
            let minutes = Int(seconds / 60)
            return "Remember my decision for \(minutes) minute\(minutes == 1 ? "" : "s")"
        case 3600..<86400:
            let hours = Int(seconds / 3600)
            return "Remember my decision for \(hours) hour\(hours == 1 ? "" : "s")"
        case 86400:
            return "Remember my decision for one day"
        default:
            let days = Int(seconds / 86400)
            return "Remember my decision for \(days) day\(days == 1 ? "" : "s")"
        }
    }

    /// Internal class for storing permission decisions with expiration dates in UserDefaults
    /// Conforms to NSSecureCoding for safe archiving/unarchiving
    @objc(StoredPermission)
    private class StoredPermission: NSObject, NSSecureCoding {
        static var supportsSecureCoding: Bool = true

        let result: Bool
        let expiry: Date

        init(result: Bool, expiry: Date) {
            self.result = result
            self.expiry = expiry
            super.init()
        }

        required init?(coder: NSCoder) {
            self.result = coder.decodeBool(forKey: "result")
            guard let expiry = coder.decodeObject(of: NSDate.self, forKey: "expiry") as? Date else {
                return nil
            }
            self.expiry = expiry
            super.init()
        }

        func encode(with coder: NSCoder) {
            coder.encode(result, forKey: "result")
            coder.encode(expiry, forKey: "expiry")
        }
    }
}
