import Cocoa
import Darwin
import FlutterMacOS

@main
class AppDelegate: FlutterAppDelegate, NSWindowDelegate {
  private let skillDirectoryChannelName = "plus.svc.xworkmate/skill_directory_access"
  private let appLifecycleChannelName = "plus.svc.xworkmate/app_lifecycle"
  private let dockDisplayMode: DockDisplayMode = .regular
  private let statusProvider = FlutterAppStatusProvider()
  private var directoryAccessSessions: [String: URL] = [:]
  private var appLifecycleChannel: FlutterMethodChannel?
  private var appLifecycleMessengerId: ObjectIdentifier?
  private var skillDirectoryChannel: FlutterMethodChannel?
  private var skillDirectoryMessengerId: ObjectIdentifier?
  private lazy var statusViewModel = AppStatusViewModel(provider: statusProvider)
  private var statusBarController: StatusBarController?
  private var terminationInFlight = false
  private var terminationTimeoutWorkItem: DispatchWorkItem?

  override func applicationDidFinishLaunching(_ notification: Notification) {
    super.applicationDidFinishLaunching(notification)

    applyDockDisplayMode()
    mainFlutterWindow?.delegate = self

    if let controller = mainFlutterWindow?.contentViewController as? FlutterViewController {
      registerApplicationChannels(for: controller)
    }
    setUpStatusBarController()
    statusViewModel.startRefreshing()
    DispatchQueue.main.async { [weak self] in
      self?.showMainWindow()
    }
  }

  override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
    return false
  }

  override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
    return true
  }

  override func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
    guard !terminationInFlight else {
      return .terminateLater
    }
    terminationInFlight = true
    var hasReplied = false
    let finishTermination: () -> Void = { [weak self] in
      guard let self else {
        return
      }
      guard !hasReplied else {
        return
      }
      hasReplied = true
      self.terminationTimeoutWorkItem?.cancel()
      self.terminationTimeoutWorkItem = nil
      self.terminationInFlight = false
      sender.reply(toApplicationShouldTerminate: true)
    }

    let timeoutWorkItem = DispatchWorkItem(block: finishTermination)
    terminationTimeoutWorkItem = timeoutWorkItem
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: timeoutWorkItem)
    statusViewModel.prepareForExit(completion: finishTermination)
    return .terminateLater
  }

  override func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
    showMainWindow()
    return true
  }

  override func applicationWillTerminate(_ notification: Notification) {
    statusViewModel.stopRefreshing()
    for (_, url) in directoryAccessSessions {
      url.stopAccessingSecurityScopedResource()
    }
    directoryAccessSessions.removeAll()
    super.applicationWillTerminate(notification)
  }

  func registerApplicationChannels(for controller: FlutterViewController) {
    registerAppLifecycleChannel(for: controller)
    registerSkillDirectoryChannel(for: controller)
  }

  func registerSkillDirectoryChannel(for controller: FlutterViewController) {
    let messengerObject = controller.engine.binaryMessenger as AnyObject
    let messengerId = ObjectIdentifier(messengerObject)
    if skillDirectoryMessengerId == messengerId {
      return
    }
    let channel = FlutterMethodChannel(
      name: skillDirectoryChannelName,
      binaryMessenger: controller.engine.binaryMessenger
    )
    channel.setMethodCallHandler { [weak self] call, result in
      self?.handleSkillDirectoryCall(call, result: result)
    }
    skillDirectoryChannel = channel
    skillDirectoryMessengerId = messengerId
  }

  func registerAppLifecycleChannel(for controller: FlutterViewController) {
    let messengerObject = controller.engine.binaryMessenger as AnyObject
    let messengerId = ObjectIdentifier(messengerObject)
    if appLifecycleMessengerId == messengerId {
      return
    }
    appLifecycleChannel = FlutterMethodChannel(
      name: appLifecycleChannelName,
      binaryMessenger: controller.engine.binaryMessenger
    )
    appLifecycleMessengerId = messengerId
    statusViewModel.bind(channel: appLifecycleChannel)
  }

  private func setUpStatusBarController() {
    guard statusBarController == nil else {
      return
    }
    statusBarController = StatusBarController(
      viewModel: statusViewModel,
      openAppHandler: { [weak self] in
        self?.showMainWindow()
      },
      quitAndPauseHandler: { [weak self] in
        self?.requestTermination()
      }
    )
  }

  private func requestTermination() {
    NSApp.terminate(nil)
  }

  @IBAction func showMainWindow(_ sender: Any?) {
    showMainWindow()
  }

  func windowShouldClose(_ sender: NSWindow) -> Bool {
    sender.orderOut(nil)
    return false
  }

  private func showMainWindow() {
    guard let window = mainFlutterWindow else {
      return
    }
    NSApp.unhide(nil)
    if window.isMiniaturized {
      window.deminiaturize(nil)
    }
    window.makeKeyAndOrderFront(nil)
    window.orderFrontRegardless()
    NSApp.activate(ignoringOtherApps: true)
  }

  private func applyDockDisplayMode() {
    switch dockDisplayMode {
    case .regular:
      NSApp.setActivationPolicy(.regular)
    case .menuBarOnly:
      NSApp.setActivationPolicy(.accessory)
    }
  }

  private func handleSkillDirectoryCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "resolveUserHomeDirectory":
      result(resolveUserHomeDirectoryPath())
    case "authorizeDirectory":
      authorizeDirectory(call, result: result)
    case "authorizeDirectories":
      authorizeDirectories(call, result: result)
    case "startDirectoryAccess":
      startDirectoryAccess(call, result: result)
    case "stopDirectoryAccess":
      stopDirectoryAccess(call, result: result)
    default:
      result(FlutterMethodNotImplemented)
    }
  }

  private func authorizeDirectory(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    let arguments = call.arguments as? [String: Any]
    let suggestedPath = (arguments?["suggestedPath"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines)

    let panel = buildAuthorizationPanel(
      title: "授权技能目录",
      message: "请选择要授予 XWorkmate 只读访问权限的技能目录。",
      allowsMultipleSelection: false,
      suggestedPath: suggestedPath
    )
    guard panel.runModal() == .OK, let selectedURL = panel.url else {
      result(nil)
      return
    }

    do {
      result(try authorizationPayload(for: selectedURL))
    } catch {
      result(
        FlutterError(
          code: "bookmark_create_failed",
          message: error.localizedDescription,
          details: nil
        )
      )
    }
  }

  private func authorizeDirectories(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    let arguments = call.arguments as? [String: Any]
    let suggestedPaths = (arguments?["suggestedPaths"] as? [String] ?? [])
      .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
      .filter { !$0.isEmpty }
    let panel = buildAuthorizationPanel(
      title: "批量授权技能目录",
      message: "请选择要授予 XWorkmate 只读访问权限的一个或多个技能目录。",
      allowsMultipleSelection: true,
      suggestedPath: suggestedPaths.first ?? ""
    )
    guard panel.runModal() == .OK else {
      result([])
      return
    }

    do {
      let payload = try panel.urls.map(authorizationPayload(for:))
      result(payload)
    } catch {
      result(
        FlutterError(
          code: "bookmark_create_failed",
          message: error.localizedDescription,
          details: nil
        )
      )
    }
  }

  private func buildAuthorizationPanel(
    title: String,
    message: String,
    allowsMultipleSelection: Bool,
    suggestedPath: String
  ) -> NSOpenPanel {
    let panel = NSOpenPanel()
    panel.title = title
    panel.message = message
    panel.prompt = "授权"
    panel.canChooseDirectories = true
    panel.canChooseFiles = false
    panel.allowsMultipleSelection = allowsMultipleSelection
    panel.canCreateDirectories = false
    panel.resolvesAliases = true
    panel.showsHiddenFiles = true
    if let initialURL = initialDirectoryURL(for: suggestedPath) {
      panel.directoryURL = initialURL
    }
    return panel
  }

  private func authorizationPayload(for selectedURL: URL) throws -> [String: Any] {
    let resolvedURL = selectedURL.standardizedFileURL
    let bookmarkData = try resolvedURL.bookmarkData(
      options: [.withSecurityScope],
      includingResourceValuesForKeys: nil,
      relativeTo: nil
    )
    return [
      "path": resolvedURL.path,
      "bookmark": bookmarkData.base64EncodedString(),
    ]
  }

  private func startDirectoryAccess(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    let arguments = call.arguments as? [String: Any]
    let bookmark = (arguments?["bookmark"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
    guard !bookmark.isEmpty, let bookmarkData = Data(base64Encoded: bookmark) else {
      result(
        FlutterError(
          code: "invalid_bookmark",
          message: "Missing directory bookmark.",
          details: nil
        )
      )
      return
    }

    do {
      var isStale = false
      let url = try URL(
        resolvingBookmarkData: bookmarkData,
        options: [.withSecurityScope],
        relativeTo: nil,
        bookmarkDataIsStale: &isStale
      )
      guard url.startAccessingSecurityScopedResource() else {
        result(
          FlutterError(
            code: "directory_access_denied",
            message: "Failed to start security-scoped access.",
            details: nil
          )
        )
        return
      }

      let accessId = UUID().uuidString
      directoryAccessSessions[accessId] = url
      var payload: [String: Any] = [
        "accessId": accessId,
        "path": url.standardizedFileURL.path,
      ]
      if isStale,
         let refreshedBookmark = try? url.bookmarkData(
           options: [.withSecurityScope],
           includingResourceValuesForKeys: nil,
           relativeTo: nil
         ) {
        payload["bookmark"] = refreshedBookmark.base64EncodedString()
      }
      result(payload)
    } catch {
      result(
        FlutterError(
          code: "directory_access_failed",
          message: error.localizedDescription,
          details: nil
        )
      )
    }
  }

  private func stopDirectoryAccess(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    let arguments = call.arguments as? [String: Any]
    let accessId = (arguments?["accessId"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
    guard !accessId.isEmpty else {
      result(nil)
      return
    }
    if let url = directoryAccessSessions.removeValue(forKey: accessId) {
      url.stopAccessingSecurityScopedResource()
    }
    result(nil)
  }

  private func initialDirectoryURL(for suggestedPath: String) -> URL? {
    let trimmed = suggestedPath.trimmingCharacters(in: .whitespacesAndNewlines)
    if trimmed.isEmpty {
      return URL(fileURLWithPath: resolveUserHomeDirectoryPath(), isDirectory: true)
    }

    var candidate = URL(fileURLWithPath: expandUserPath(trimmed))
    var isDirectory: ObjCBool = false
    while true {
      if FileManager.default.fileExists(atPath: candidate.path, isDirectory: &isDirectory) {
        return isDirectory.boolValue ? candidate.deletingLastPathComponent() : candidate.deletingLastPathComponent()
      }
      let parent = candidate.deletingLastPathComponent()
      if parent.path == candidate.path || parent.path.isEmpty {
        break
      }
      candidate = parent
    }
    return URL(fileURLWithPath: resolveUserHomeDirectoryPath(), isDirectory: true)
  }

  private func expandUserPath(_ path: String) -> String {
    guard path.hasPrefix("~/") else {
      return path
    }
    let relative = String(path.dropFirst(2))
    return (resolveUserHomeDirectoryPath() as NSString).appendingPathComponent(relative)
  }

  private func resolveUserHomeDirectoryPath() -> String {
    if let directoryPointer = getpwuid(getuid())?.pointee.pw_dir {
      return String(cString: directoryPointer)
    }
    return FileManager.default.homeDirectoryForCurrentUser.path
  }
}
