import Cocoa
import FlutterMacOS
import CoreGraphics
import Network
import UserNotifications

// ---------------------------------------------------------------------------
// MacOSPermissionsHandler
//
// Native-side MethodChannel handler for macOS-specific permissions
// that the Flutter `permission_handler` plugin does not cover:
//
//   • Input Monitoring  (CGPreflightListenEventAccess /
//                        CGRequestListenEventAccess) for global PTT.
//   • Local Network     (NWBrowser trigger for _ts3._tcp) so macOS 15+
//                        shows the Local Network Privacy prompt.
//   • Notifications     (UNUserNotificationCenter authorization).
//
// Channel name: `app.chanora/macos_permissions`
//
// Trace: SRS-198, SRS-297, SRS-300, SysRS-166, SDD-091.
// ---------------------------------------------------------------------------

/// Polling interval for Input Monitoring state changes.
/// TCC does not emit a callback when the user toggles Input Monitoring
/// in System Settings, so we poll at a reasonable cadence.
private let kInputMonitoringPollInterval: TimeInterval = 2.0

class MacOSPermissionsHandler: NSObject, FlutterPlugin {
  private var channel: FlutterMethodChannel?
  private var inputMonitoringTimer: Timer?
  private var lastInputMonitoringState: String = "Unknown"

  // -- FlutterPlugin -------------------------------------------------------

  static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(
      name: "app.chanora/macos_permissions",
      binaryMessenger: registrar.messenger
    )
    let instance = MacOSPermissionsHandler()
    instance.channel = channel
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    // -- Input Monitoring ---------------------------------------------------
    case "checkInputMonitoring":
      result(inputMonitoringStateString())
      startInputMonitoringPolling()

    case "requestInputMonitoring":
      requestInputMonitoring(result: result)

    case "openInputMonitoringSettings":
      openInputMonitoringSettings(result: result)

    // -- Local Network ------------------------------------------------------
    case "checkLocalNetwork":
      checkLocalNetwork(result: result)

    case "triggerLocalNetworkPrompt":
      triggerLocalNetworkPrompt(result: result)

    // -- Notifications ------------------------------------------------------
    case "checkNotifications":
      checkNotifications(result: result)

    case "requestNotifications":
      requestNotifications(result: result)

    default:
      result(FlutterMethodNotImplemented)
    }
  }

  // =========================================================================
  // Input Monitoring
  // =========================================================================

  /// Returns the current Input Monitoring state as a string
  /// consumable by the Dart side: "Granted", "Denied", "NotDetermined".
  private func inputMonitoringStateString() -> String {
    // CGPreflightListenEventAccess returns true when access is already
    // granted. On macOS 10.15+ it returns false when denied or not yet
    // determined — we cannot distinguish those two without attempting
    // CGRequestListenEventAccess, so we conservatively report
    // "NotDetermined" when preflight returns false. The Dart side
    // treats both "Denied" and "NotDetermined" as L0Focused.
    if CGPreflightListenEventAccess() {
      return "Granted"
    }
    return "NotDetermined"
  }

  /// Request Input Monitoring permission via
  /// `CGRequestListenEventAccess()`. On macOS 13+ this opens
  /// System Settings → Privacy & Security → Input Monitoring.
  private func requestInputMonitoring(result: @escaping FlutterResult) {
    // CGRequestListenEventAccess shows the system prompt.
    // It returns true if access was already granted or becomes
    // granted synchronously (rare). Most of the time it returns
    // false and the user must toggle the switch manually.
    let granted = CGRequestListenEventAccess()
    let state = granted ? "Granted" : "NotDetermined"
    lastInputMonitoringState = state
    result(state)

    // Start polling so we detect when the user grants in Settings.
    startInputMonitoringPolling()
  }

  /// Open System Settings → Privacy & Security → Input Monitoring
  /// so the user can manually enable the app.
  private func openInputMonitoringSettings(result: @escaping FlutterResult) {
    if let url = URL(
      string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent"
    ) {
      NSWorkspace.shared.open(url)
    }
    result(nil)
  }

  /// Start a periodic timer that checks Input Monitoring state and
  /// notifies the Dart side when it changes.
  private func startInputMonitoringPolling() {
    // Don't start a second timer if one is already running.
    guard inputMonitoringTimer == nil else { return }
    lastInputMonitoringState = inputMonitoringStateString()

    inputMonitoringTimer = Timer.scheduledTimer(
      withTimeInterval: kInputMonitoringPollInterval,
      repeats: true
    ) { [weak self] _ in
      self?.pollInputMonitoring()
    }
  }

  private func pollInputMonitoring() {
    let current = inputMonitoringStateString()
    guard current != lastInputMonitoringState else { return }
    lastInputMonitoringState = current

    // Notify the Dart side via the inbound method.
    channel?.invokeMethod("inputMonitoringStateChanged", arguments: [
      "state": current,
    ])
  }

  // =========================================================================
  // Local Network
  // =========================================================================

  /// Check whether Local Network access is available.
  /// On macOS 14 and earlier, Local Network Privacy does not exist,
  /// so we report "Unsupported". On macOS 15+, we attempt a brief
  /// NWBrowser scan and report based on the result.
  private func checkLocalNetwork(result: @escaping FlutterResult) {
    if #available(macOS 15.0, *) {
      // We cannot synchronously determine the Local Network state
      // without actually using the network. Report "NotDetermined"
      // and let triggerLocalNetworkPrompt resolve the actual state.
      result("NotDetermined")
    } else {
      result("Unsupported")
    }
  }

  /// Trigger the Local Network Privacy prompt by starting a brief
  /// NWBrowser for `_ts3._tcp`. On macOS 15+, this causes the system
  /// to show the Local Network permission dialog if not already
  /// determined.
  ///
  /// The browser is started and stopped after a short scan window.
  /// State changes are reported back to Dart via
  /// `localNetworkStateChanged`.
  @available(macOS 15.0, *)
  private func triggerLocalNetworkPromptImpl(result: @escaping FlutterResult) {
    let bonjourType = "_ts3._tcp"
    let browserDescriptor = NWBrowser.Descriptor.bonjourWithTXTRecord(
      type: bonjourType, domain: nil)
    let browser = NWBrowser(for: browserDescriptor, using: NWParameters.tcp)

    var resolved = false

    browser.stateUpdateHandler = { [weak self] (browserState: NWBrowser.State) in
      switch browserState {
      case .ready:
        // Browser started successfully — local network is accessible.
        if !resolved {
          resolved = true
          result("Granted")
          self?.channel?.invokeMethod("localNetworkStateChanged", arguments: [
            "state": "Granted",
          ])
        }
        browser.cancel()
      case .failed(let error):
        if !resolved {
          resolved = true
          let code = error.errorCode
          // POSIX permission-denied or network-down signals that
          // the user denied the Local Network prompt.
          if code == ENETDOWN || code == EACCES || code == EPERM {
            result("Denied")
            self?.channel?.invokeMethod("localNetworkStateChanged", arguments: [
              "state": "Denied",
            ])
          } else {
            // Network unreachable or other transient error —
            // don't assume denied.
            result("NotDetermined")
          }
        }
        browser.cancel()
      case .waiting:
        // The browser is waiting for network — this is normal and
        // may mean the permission dialog is showing. Don't resolve
        // yet; wait for .ready or .failed or the timeout.
        break
      case .setup, .cancelled:
        break
      @unknown default:
        break
      }
    }

    browser.start(queue: DispatchQueue.main)

    // Timeout: if the browser doesn't resolve within 10 seconds,
    // report NotDetermined so the Dart side doesn't hang forever.
    DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
      if !resolved {
        resolved = true
        result("NotDetermined")
        browser.cancel()
      }
    }
  }

  private func triggerLocalNetworkPrompt(result: @escaping FlutterResult) {
    if #available(macOS 15.0, *) {
      triggerLocalNetworkPromptImpl(result: result)
    } else {
      result("Unsupported")
    }
  }

  // =========================================================================
  // Notifications
  // =========================================================================

  private func checkNotifications(result: @escaping FlutterResult) {
    UNUserNotificationCenter.current().getNotificationSettings { settings in
      switch settings.authorizationStatus {
      case .authorized, .provisional:
        result("Granted")
      case .denied:
        result("Denied")
      case .notDetermined:
        result("NotDetermined")
      @unknown default:
        result("NotDetermined")
      }
    }
  }

  private func requestNotifications(result: @escaping FlutterResult) {
    UNUserNotificationCenter.current().requestAuthorization(options: [
      .alert, .sound, .badge,
    ]) { granted, _ in
      let state = granted ? "Granted" : "Denied"
      result(state)
    }
  }

  // =========================================================================
  // Cleanup
  // =========================================================================

  deinit {
    inputMonitoringTimer?.invalidate()
  }
}

// ---------------------------------------------------------------------------
// MainFlutterWindow
// ---------------------------------------------------------------------------

class MainFlutterWindow: NSWindow {
  override func awakeFromNib() {
    let flutterViewController = FlutterViewController()
    let windowFrame = self.frame
    self.contentViewController = flutterViewController
    self.setFrame(windowFrame, display: true)

    self.titlebarAppearsTransparent = true
    self.titleVisibility = .hidden
    self.styleMask.insert(.fullSizeContentView)
    self.isMovableByWindowBackground = true

    RegisterGeneratedPlugins(registry: flutterViewController)

    // Register the macOS permissions MethodChannel handler.
    MacOSPermissionsHandler.register(with: flutterViewController.registrar(forPlugin: "MacOSPermissionsHandler"))

    super.awakeFromNib()
  }
}
