package com.aves.google_cast.cast

import android.os.Handler
import android.os.Looper
import android.util.Log
import com.google.android.gms.cast.MediaError
import com.google.android.gms.cast.MediaSeekOptions
import com.google.android.gms.cast.MediaStatus
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import io.flutter.plugin.common.MethodChannel

class CastMediaClient(
    private val ctx: CastContextManager,
    private val channel: MethodChannel
) {

    private var mediaClientListenerAdded = false
    private var lastClientIdentity: Int? = null

    private var lastSentIndex: Int? = null
    private var lastHandledFinishedItemId: Int? = null

    private var lastPlaybackState: Int? = null
    private var lastVolume: Double? = null
    private var lastMuted: Boolean? = null

    private val mainHandler = Handler(Looper.getMainLooper())

    private val mediaClientCallback = object : RemoteMediaClient.Callback() {

        override fun onStatusUpdated() {
            Log.d("GCAST_NATIVE", "CastMediaClient.onStatusUpdated() CALLED")

            val client = ctx.getClient() ?: run {
                Log.d("GCAST_NATIVE", "CastMediaClient.onStatusUpdated(): ctx.getClient() == null")
                return
            }

            val status = client.mediaStatus ?: run {
                Log.d("GCAST_NATIVE", "CastMediaClient.onStatusUpdated(): client.mediaStatus == null")
                return
            }

            Log.d(
                "GCAST_NATIVE",
                "CastMediaClient.onStatusUpdated(): playerState=${status.playerState} pos=${client.approximateStreamPosition} dur=${status.mediaInfo?.streamDuration}"
            )

            // QUEUE INDEX
            val custom = status.mediaInfo?.customData
            if (custom != null) {
                val index = custom.optInt("index", -1)
                val isVideo = custom.optBoolean("isVideo", false)

                if (index >= 0 && index != lastSentIndex) {
                    lastSentIndex = index

                    mainHandler.post {
                        channel.invokeMethod(
                            "onQueueIndexChanged",
                            mapOf(
                                "index" to index,
                                "isVideo" to isVideo
                            )
                        )
                    }
                }
            }

            // MEDIA STATUS — SEMPRE INVIATO
            val playerState = status.playerState
            val position = client.approximateStreamPosition
            val duration = client.mediaInfo?.streamDuration ?: 0L

            lastPlaybackState = playerState

            mainHandler.post {
                channel.invokeMethod(
                    "onMediaStatus",
                    mapOf(
                        "isPlaying" to (playerState == MediaStatus.PLAYER_STATE_PLAYING),
                        "isBuffering" to (playerState == MediaStatus.PLAYER_STATE_BUFFERING),
                        "position" to position,
                        "duration" to duration
                    )
                )
            }

            // VIDEO FINISHED
            if (playerState == MediaStatus.PLAYER_STATE_IDLE &&
                status.idleReason == MediaStatus.IDLE_REASON_FINISHED
            ) {
                val finishedItemId = try {
                    status.currentItemId
                } catch (_: Exception) {
                    null
                }

                if (finishedItemId != null && finishedItemId == lastHandledFinishedItemId) {
                    return
                }

                lastHandledFinishedItemId = finishedItemId

                mainHandler.post {
                    channel.invokeMethod("onVideoFinished", null)
                }

                return
            }

            // ERROR
            if (playerState == MediaStatus.PLAYER_STATE_IDLE &&
                status.idleReason == MediaStatus.IDLE_REASON_ERROR
            ) {
                mainHandler.post {
                    channel.invokeMethod(
                        "onCastError",
                        mapOf("message" to "Playback error")
                    )
                }
            }

            // VOLUME — SEMPRE INVIATO
            sendVolumeAlways()
        }

        override fun onMediaError(mediaError: MediaError) {
            Log.d("GCAST_NATIVE", "CastMediaClient.onMediaError(): $mediaError")

            mainHandler.post {
                channel.invokeMethod(
                    "onCastError",
                    mapOf(
                        "message" to "Media error: ${mediaError}",
                        "detailedErrorCode" to mediaError.detailedErrorCode
                    )
                )
            }
        }
    }

    // ------------------------------------------------------------
    // VOLUME SEMPRE INVIATO
    // ------------------------------------------------------------
    private fun sendVolumeAlways() {
        val session: CastSession = ctx.getSession() ?: run {
            Log.d("GCAST_NATIVE", "sendVolumeAlways(): session == null")
            return
        }

        val vol = session.volume
        val muted = session.isMute

        Log.d("GCAST_NATIVE", "sendVolumeAlways(): volume=$vol muted=$muted")

        lastVolume = vol
        lastMuted = muted

        mainHandler.post {
            channel.invokeMethod(
                "onVolumeChanged",
                mapOf(
                    "volume" to vol,
                    "isMuted" to muted
                )
            )
        }
    }

    // ------------------------------------------------------------
    // CLIENT ATTACH
    // ------------------------------------------------------------
    fun attachCurrentClientIfNeeded() {
        val client = ctx.getClient() ?: run {
            Log.d("GCAST_NATIVE", "attachCurrentClientIfNeeded(): ctx.getClient() == null")
            return
        }

        val identity = System.identityHashCode(client)

        Log.d("GCAST_NATIVE", "attachCurrentClientIfNeeded(): attaching to client identity=$identity")

        if (!mediaClientListenerAdded || lastClientIdentity != identity) {
            try {
                client.unregisterCallback(mediaClientCallback)
            } catch (_: Exception) {}

            client.registerCallback(mediaClientCallback)

            mediaClientListenerAdded = true
            lastClientIdentity = identity

            lastSentIndex = null
            lastHandledFinishedItemId = null
            lastPlaybackState = null
            lastVolume = null
            lastMuted = null
        }
    }

    fun resetClientTracking() {
        mediaClientListenerAdded = false
        lastClientIdentity = null
        lastSentIndex = null
        lastHandledFinishedItemId = null

        lastPlaybackState = null
        lastVolume = null
        lastMuted = null
    }

    // ------------------------------------------------------------
    // CONTROLLI
    // ------------------------------------------------------------
    fun play() {
        attachCurrentClientIfNeeded()
        ctx.getClient()?.play()
    }

    fun pause() {
        attachCurrentClientIfNeeded()
        ctx.getClient()?.pause()
    }

    fun seek(position: Long) {
        attachCurrentClientIfNeeded()

        val client = ctx.getClient() ?: return
        val opts = MediaSeekOptions.Builder()
            .setPosition(position)
            .setResumeState(MediaSeekOptions.RESUME_STATE_UNCHANGED)
            .build()

        client.seek(opts)
    }

    fun setVolume(v: Double) {
        attachCurrentClientIfNeeded()
        ctx.getSession()?.setVolume(v.coerceIn(0.0, 1.0))
    }

    fun setMute(m: Boolean) {
        attachCurrentClientIfNeeded()
        ctx.getSession()?.setMute(m)
    }
}
