package com.matchlivetv.match_live_tv

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Build
import android.os.IBinder
import com.pedro.library.view.OpenGlView
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result

class StreamingPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, EventChannel.StreamHandler {

    private lateinit var appContext: Context
    private lateinit var methodChannel: MethodChannel
    private lateinit var eventChannel: EventChannel

    private var activityBinding: ActivityPluginBinding? = null
    private var eventSink: EventChannel.EventSink? = null
    private var boundService: StreamingForegroundService? = null
    private var serviceBound = false

    private val engineListener = object : StreamingEngineListener {
        override fun onStateChanged(state: StreamingState, message: String?) {
            ServiceEventBus.emit(
                mapOf(
                    "type" to "state",
                    "state" to state.name.lowercase(),
                    "message" to message,
                ),
            )
            boundService?.updateNotificationForState(state)
        }

        override fun onMetricsUpdated(metrics: StreamingMetrics) {
            ServiceEventBus.emit(
                mapOf(
                    "type" to "metrics",
                    "metrics" to metrics.toMap(),
                ),
            )
        }

        override fun onError(message: String) {
            ServiceEventBus.emit(
                mapOf(
                    "type" to "error",
                    "message" to message,
                ),
            )
        }
    }

    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val binder = service as? StreamingForegroundService.LocalBinder ?: return
            boundService = binder.getService()
            serviceBound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            boundService = null
            serviceBound = false
        }
    }

    private val busListener: (Map<String, Any?>) -> Unit = { event ->
        eventSink?.success(event)
    }

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        appContext = binding.applicationContext
        methodChannel = MethodChannel(binding.binaryMessenger, METHOD_CHANNEL)
        methodChannel.setMethodCallHandler(this)

        eventChannel = EventChannel(binding.binaryMessenger, EVENT_CHANNEL)
        eventChannel.setStreamHandler(this)

        binding.platformViewRegistry.registerViewFactory(
            PREVIEW_VIEW_TYPE,
            StreamingPreviewFactory { mainHandlerRebindPreview() },
        )

        ServiceEventBus.addListener(busListener)
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        methodChannel.setMethodCallHandler(null)
        eventChannel.setStreamHandler(null)
        ServiceEventBus.removeListener(busListener)
        releaseEngine()
        unbindServiceIfNeeded()
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {
        activityBinding = binding
        mainHandlerRebindPreview()
    }

    override fun onDetachedFromActivityForConfigChanges() {
        activityBinding = null
    }

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
        activityBinding = binding
        mainHandlerRebindPreview()
    }

    private fun mainHandlerRebindPreview() {
        android.os.Handler(android.os.Looper.getMainLooper()).post {
            attachPreviewIfPossible()
        }
    }

    override fun onDetachedFromActivity() {
        activityBinding = null
        unbindServiceIfNeeded()
    }

    override fun onMethodCall(call: MethodCall, result: Result) {
        when (call.method) {
            "startStream" -> startStream(call, result)
            "stopStream" -> stopStream(result)
            "getMetrics" -> getMetrics(result)
            "startPreview" -> startPreview(result)
            "stopPreview" -> stopPreview(result)
            "bindPreview" -> bindPreview(result)
            "unbindPreview" -> unbindPreview(result)
            else -> result.notImplemented()
        }
    }

    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        eventSink = events
    }

    override fun onCancel(arguments: Any?) {
        eventSink = null
    }

    private fun startStream(call: MethodCall, result: Result) {
        if (activityBinding?.activity == null) {
            result.error("no_activity", "Activity non disponibile per lo streaming", null)
            return
        }

        val args = call.arguments as? Map<*, *>
        val rtmpUrl = args?.get("rtmpUrl") as? String
        if (rtmpUrl.isNullOrBlank()) {
            result.error("invalid_args", "rtmpUrl is required", null)
            return
        }

        val config = StreamingConfig(
            rtmpUrl = rtmpUrl,
            width = (args["width"] as? Number)?.toInt() ?: 1280,
            height = (args["height"] as? Number)?.toInt() ?: 720,
            videoBitrate = (args["videoBitrate"] as? Number)?.toInt() ?: 2_500_000,
            audioBitrate = (args["audioBitrate"] as? Number)?.toInt() ?: 128_000,
            fps = (args["fps"] as? Number)?.toInt() ?: 30,
            rotation = (args["rotation"] as? Number)?.toInt() ?: 0,
            maxReconnectAttempts = (args["maxReconnectAttempts"] as? Number)?.toInt() ?: 10,
            reconnectDelayMs = (args["reconnectDelayMs"] as? Number)?.toLong() ?: 5_000L,
        )

        val engine = getOrCreateEngine()
        engine.removeListener(engineListener)
        engine.addListener(engineListener)

        attachPreviewIfPossible()

        val intent = StreamingForegroundService.startIntent(appContext)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            appContext.startForegroundService(intent)
        } else {
            appContext.startService(intent)
        }
        bindServiceIfNeeded()

        try {
            engine.startStream(config)
            result.success(true)
        } catch (exception: Exception) {
            appContext.startService(StreamingForegroundService.stopIntent(appContext))
            result.error("start_failed", exception.message, null)
        }
    }

    private fun stopStream(result: Result) {
        appContext.startService(StreamingForegroundService.stopIntent(appContext))
        getEngine()?.let { engine ->
            engine.removeListener(engineListener)
            engine.stopStream()
        }
        unbindServiceIfNeeded()
        result.success(true)
    }

    private fun getMetrics(result: Result) {
        val metrics = boundService?.getMetrics()
            ?: getEngine()?.getMetrics()?.toMap()
            ?: emptyMap()
        result.success(metrics)
    }

    private fun bindPreview(result: Result) {
        attachPreviewIfPossible()
        result.success(StreamPreviewHolder.openGlView != null)
    }

    private fun unbindPreview(result: Result) {
        getEngine()?.unbindStreamPreview()
        result.success(true)
    }

    private fun startPreview(result: Result) {
        attachPreviewIfPossible()
        result.success(StreamPreviewHolder.openGlView != null)
    }

    private fun stopPreview(result: Result) {
        getEngine()?.unbindStreamPreview()
        result.success(true)
    }

    private fun getOrCreateEngine(): StreamingEngine {
        val context = activityBinding?.activity ?: appContext
        return StreamingEngineHolder.getOrCreate(context).also { engine ->
            engine.removeListener(engineListener)
            engine.addListener(engineListener)
        }
    }

    private fun getEngine(): StreamingEngine? = StreamingEngineHolder.engine

    private fun releaseEngine() {
        getEngine()?.let { engine ->
            engine.removeListener(engineListener)
            engine.stopStream()
        }
        StreamingEngineHolder.release()
    }

    private fun attachPreviewIfPossible() {
        val glView = StreamPreviewHolder.openGlView ?: return
        val engine = getOrCreateEngine()
        engine.bindStreamPreview(glView)
    }

    private fun bindServiceIfNeeded() {
        if (serviceBound) {
            return
        }
        val intent = Intent(appContext, StreamingForegroundService::class.java)
        appContext.bindService(
            intent,
            serviceConnection,
            Context.BIND_AUTO_CREATE,
        )
    }

    private fun unbindServiceIfNeeded() {
        if (!serviceBound) {
            return
        }
        try {
            appContext.unbindService(serviceConnection)
        } catch (_: IllegalArgumentException) {
        }
        serviceBound = false
        boundService = null
    }

    companion object {
        private const val METHOD_CHANNEL = "com.matchlivetv.match_live_tv/streaming"
        private const val EVENT_CHANNEL = "com.matchlivetv.match_live_tv/streaming_events"
        const val PREVIEW_VIEW_TYPE = "match_live_tv/streaming_preview"
    }
}
