import Flutter
import UIKit

final class QlyraStreamHandler: NSObject, FlutterStreamHandler {
  private let onSink: (FlutterEventSink?) -> Void

  init(onSink: @escaping (FlutterEventSink?) -> Void) {
    self.onSink = onSink
  }

  func onListen(
    withArguments arguments: Any?,
    eventSink events: @escaping FlutterEventSink
  ) -> FlutterError? {
    onSink(events)
    return nil
  }

  func onCancel(withArguments arguments: Any?) -> FlutterError? {
    onSink(nil)
    return nil
  }
}

@main
@objc class AppDelegate: FlutterAppDelegate {
  private var channels: [FlutterMethodChannel] = []
  private var eventChannels: [FlutterEventChannel] = []
  private var streamHandlers: [QlyraStreamHandler] = []
  private var videoNote: QlyraVideoNote?

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    QlyraNotifications.shared.start()

    if let controller = window?.rootViewController as? FlutterViewController {
      let messenger = controller.binaryMessenger
      registerAppIcon(messenger)
      registerVideo(messenger)
      registerVideoNote(messenger)
      registerNotifications(messenger)
      registerScreen(messenger)
    }

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  private func method(_ name: String, _ messenger: FlutterBinaryMessenger,
                      _ handler: @escaping FlutterMethodCallHandler) {
    let channel = FlutterMethodChannel(name: name, binaryMessenger: messenger)
    channel.setMethodCallHandler(handler)
    channels.append(channel)
  }

  private func events(_ name: String, _ messenger: FlutterBinaryMessenger,
                      _ onSink: @escaping (FlutterEventSink?) -> Void) {
    let handler = QlyraStreamHandler(onSink: onSink)
    let channel = FlutterEventChannel(name: name, binaryMessenger: messenger)
    channel.setStreamHandler(handler)
    streamHandlers.append(handler)
    eventChannels.append(channel)
  }

  private func registerAppIcon(_ messenger: FlutterBinaryMessenger) {
    method("ru.qlyra.app/app_icon", messenger) { call, result in
      switch call.method {
      case "getAppIcon":
        result(UIApplication.shared.alternateIconName)
      case "setAppIcon":
        let requested = (call.arguments as? [String: Any])?["name"] as? String
        let iconName: String? = (requested?.isEmpty ?? true) ? nil : requested
        guard UIApplication.shared.supportsAlternateIcons else {
          result(FlutterError(code: "UNSUPPORTED",
                              message: "Alternate icons are not supported",
                              details: nil))
          return
        }
        guard UIApplication.shared.alternateIconName != iconName else {
          result(nil)
          return
        }
        UIApplication.shared.setAlternateIconName(iconName) { error in
          DispatchQueue.main.async {
            if let error = error {
              result(FlutterError(code: "APPLY_FAILED",
                                  message: error.localizedDescription,
                                  details: nil))
            } else {
              result(nil)
            }
          }
        }
      default:
        result(FlutterMethodNotImplemented)
      }
    }
  }

  private func registerVideo(_ messenger: FlutterBinaryMessenger) {
    method("ru.qlyra.app/video", messenger) { call, result in
      QlyraVideo.shared.handle(call, result: result)
    }
  }

  private func registerVideoNote(_ messenger: FlutterBinaryMessenger) {
    guard let textures = registrar(forPlugin: "QlyraVideoNote")?.textures() else { return }

    method("ru.qlyra.app/video_note", messenger) { [weak self] call, result in
      guard let self = self else { return }
      switch call.method {
      case "permission":
        QlyraVideoNote.requestPermission(result)
      case "init":
        let args = call.arguments as? [String: Any] ?? [:]
        self.videoNote?.dispose()
        let recorder = QlyraVideoNote(registry: textures)
        self.videoNote = recorder
        recorder.initialize(
          front: (args["front"] as? NSNumber)?.boolValue ?? true,
          edge: (args["size"] as? NSNumber)?.intValue ?? 480,
          fps: (args["fps"] as? NSNumber)?.intValue ?? 30,
          result: result)
      case "start":
        self.withRecorder(result) { $0.start(result: result) }
      case "switch":
        self.withRecorder(result) { $0.switchCamera(result: result) }
      case "torch":
        let on = ((call.arguments as? [String: Any])?["on"] as? NSNumber)?.boolValue ?? false
        self.withRecorder(result) { $0.setTorch(on: on, result: result) }
      case "stop":
        self.withRecorder(result) { $0.stop(result: result) }
      case "dispose":
        self.videoNote?.dispose()
        self.videoNote = nil
        result(nil)
      default:
        result(FlutterMethodNotImplemented)
      }
    }
  }

  private func withRecorder(_ result: @escaping FlutterResult,
                            _ body: (QlyraVideoNote) -> Void) {
    guard let recorder = videoNote else {
      result(FlutterError(code: "NOT_READY", message: "recorder not initialized", details: nil))
      return
    }
    body(recorder)
  }

  private func registerScreen(_ messenger: FlutterBinaryMessenger) {
    method("ru.qlyra.app/screen", messenger) { call, result in
      switch call.method {
      case "setKeepAwake":
        let enabled = ((call.arguments as? [String: Any])?["enabled"] as? NSNumber)?.boolValue ?? false
        DispatchQueue.main.async {
          UIApplication.shared.isIdleTimerDisabled = enabled
          result(nil)
        }
      default:
        result(FlutterMethodNotImplemented)
      }
    }
  }

  private func registerNotifications(_ messenger: FlutterBinaryMessenger) {
    method("ru.qlyra.app/notifications", messenger) { call, result in
      QlyraNotifications.shared.handle(call, result: result)
    }
    events("ru.qlyra.app/notification_events", messenger) { sink in
      QlyraNotifications.shared.attach(sink)
    }
  }
}
