package de.datait.videokonverter

import android.app.Activity
import android.media.MediaCodecList
import android.os.Handler
import android.os.Looper
import android.view.View
import android.webkit.CookieManager
import android.webkit.JavascriptInterface
import android.webkit.WebView
import androidx.annotation.OptIn
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.hls.HlsMediaSource
import androidx.media3.exoplayer.source.ProgressiveMediaSource
import androidx.media3.ui.PlayerView
import org.json.JSONArray
import org.json.JSONObject

/**
 * JavaScript Bridge: Verbindet window.VKNative mit ExoPlayer.
 * Wird per @JavascriptInterface als VKNativeAndroid registriert,
 * dann in VKWebViewClient zu window.VKNative gewrapped.
 */
@OptIn(UnstableApi::class)
class NativePlayerBridge(
    private val activity: Activity,
    private val webView: WebView,
    private val playerView: PlayerView,
    private val serverUrl: String
) : Player.Listener {

    private var exoPlayer: ExoPlayer? = null
    private var timeUpdateHandler: Handler? = null
    private var timeUpdateRunnable: Runnable? = null

    // Unterstuetzte Audio-Codecs (DTS blockiert)
    private val unsupportedAudio = listOf("dts", "dca", "dts_hd", "dts-hd", "truehd")

    // --- Codec-Abfrage ---

    @JavascriptInterface
    fun getSupportedVideoCodecs(): String {
        val codecs = mutableSetOf<String>()
        val codecList = MediaCodecList(MediaCodecList.ALL_CODECS)
        for (info in codecList.codecInfos) {
            if (info.isEncoder) continue
            for (type in info.supportedTypes) {
                val t = type.lowercase()
                when {
                    t.contains("avc") -> codecs.add("h264")
                    t.contains("hevc") || t.contains("hev") -> codecs.add("hevc")
                    t.contains("av01") -> codecs.add("av1")
                    t.contains("vp9") -> codecs.add("vp9")
                }
            }
        }
        if (codecs.isEmpty()) codecs.add("h264")
        return JSONArray(codecs.toList()).toString()
    }

    @JavascriptInterface
    fun getSupportedAudioCodecs(): String {
        // ExoPlayer unterstuetzt diese Codecs nativ
        val codecs = listOf("aac", "opus", "mp3", "flac", "ac3", "eac3", "vorbis", "pcm")
        return JSONArray(codecs).toString()
    }

    @JavascriptInterface
    fun canDirectPlay(videoInfoJson: String): Boolean {
        return try {
            val info = JSONObject(videoInfoJson)
            val videoCodec = info.optString("video_codec_normalized", "").lowercase()

            // Video-Codec pruefen
            val supportedVideo = JSONArray(getSupportedVideoCodecs())
            val videoCodecs = (0 until supportedVideo.length()).map { supportedVideo.getString(it) }
            if (videoCodec !in videoCodecs) return false

            // Audio-Codecs pruefen (DTS blockieren)
            val audioCodecs = info.optJSONArray("audio_codecs")
            if (audioCodecs != null) {
                for (i in 0 until audioCodecs.length()) {
                    val ac = audioCodecs.optString(i, "").lowercase()
                    if (ac in unsupportedAudio) return false
                }
            }
            true
        } catch (e: Exception) {
            false
        }
    }

    // --- Player-Steuerung ---

    @JavascriptInterface
    fun play(url: String, videoInfoJson: String, optsJson: String): Boolean {
        val opts = try { JSONObject(optsJson) } catch (e: Exception) { JSONObject() }
        val seekMs = opts.optLong("seekMs", 0)

        // Relative URL -> Absolute URL
        val fullUrl = if (url.startsWith("/")) "$serverUrl$url" else url

        activity.runOnUiThread {
            try {
                // Vorherigen Player bereinigen
                releasePlayer()

                // ExoPlayer erstellen (mit Audio-Attributen fuer korrekten Ton)
                val audioAttributes = AudioAttributes.Builder()
                    .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
                    .setUsage(C.USAGE_MEDIA)
                    .build()
                val player = ExoPlayer.Builder(activity)
                    .setAudioAttributes(audioAttributes, true)
                    .build()

                // PlayerView konfigurieren
                playerView.player = player
                playerView.useController = false  // Controls kommen von Web-UI
                playerView.visibility = View.VISIBLE

                // Cookie fuer Auth mitgeben
                val cookie = CookieManager.getInstance().getCookie(serverUrl) ?: ""
                val dataSourceFactory = DefaultHttpDataSource.Factory()
                    .setDefaultRequestProperties(mapOf("Cookie" to cookie))

                val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
                    .createMediaSource(MediaItem.fromUri(fullUrl))

                player.setMediaSource(mediaSource)
                player.addListener(this)
                player.prepare()

                if (seekMs > 0) {
                    player.seekTo(seekMs)
                }
                player.playWhenReady = true

                exoPlayer = player

                // Periodische Zeit-Updates starten
                startTimeUpdates()

            } catch (e: Exception) {
                callJs("if(window._vkOnError) window._vkOnError('${e.message}')")
            }
        }
        return true
    }

    /**
     * HLS-Stream ueber ExoPlayer abspielen (Fallback fuer DTS/TrueHD-Audio).
     * Wird von player.js aufgerufen wenn canDirectPlay() false ist.
     */
    @JavascriptInterface
    fun playHLS(playlistUrl: String, optsJson: String): Boolean {
        val opts = try { JSONObject(optsJson) } catch (e: Exception) { JSONObject() }
        val seekMs = opts.optLong("seekMs", 0)

        // Relative URL -> Absolute URL
        val fullUrl = if (playlistUrl.startsWith("/")) "$serverUrl$playlistUrl" else playlistUrl

        activity.runOnUiThread {
            try {
                releasePlayer()

                val audioAttributes = AudioAttributes.Builder()
                    .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
                    .setUsage(C.USAGE_MEDIA)
                    .build()
                val player = ExoPlayer.Builder(activity)
                    .setAudioAttributes(audioAttributes, true)
                    .build()

                playerView.player = player
                playerView.useController = false
                playerView.visibility = View.VISIBLE

                // Cookie fuer Auth
                val cookie = CookieManager.getInstance().getCookie(serverUrl) ?: ""
                val dataSourceFactory = DefaultHttpDataSource.Factory()
                    .setDefaultRequestProperties(mapOf("Cookie" to cookie))

                // HLS-MediaSource (nicht Progressive!)
                val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
                    .createMediaSource(MediaItem.fromUri(fullUrl))

                player.setMediaSource(mediaSource)
                player.addListener(this)
                player.prepare()

                if (seekMs > 0) {
                    player.seekTo(seekMs)
                }
                player.playWhenReady = true

                exoPlayer = player
                startTimeUpdates()

            } catch (e: Exception) {
                callJs("if(window._vkOnError) window._vkOnError('${e.message}')")
            }
        }
        return true
    }

    @JavascriptInterface
    fun togglePlay() {
        activity.runOnUiThread {
            exoPlayer?.let {
                it.playWhenReady = !it.playWhenReady
            }
        }
    }

    @JavascriptInterface
    fun pause() {
        activity.runOnUiThread {
            exoPlayer?.playWhenReady = false
        }
    }

    @JavascriptInterface
    fun resume() {
        activity.runOnUiThread {
            exoPlayer?.playWhenReady = true
        }
    }

    @JavascriptInterface
    fun seek(positionMs: Long) {
        activity.runOnUiThread {
            exoPlayer?.seekTo(positionMs.coerceAtLeast(0))
        }
    }

    @JavascriptInterface
    fun getCurrentTime(): Long {
        return exoPlayer?.currentPosition ?: 0
    }

    @JavascriptInterface
    fun getDuration(): Long {
        return exoPlayer?.duration ?: 0
    }

    @JavascriptInterface
    fun isPlaying(): Boolean {
        return exoPlayer?.isPlaying ?: false
    }

    @JavascriptInterface
    fun stop() {
        activity.runOnUiThread {
            releasePlayer()
            playerView.visibility = View.GONE
        }
    }

    @JavascriptInterface
    fun setAudioTrack(index: Int): Boolean {
        val player = exoPlayer ?: return false
        return try {
            activity.runOnUiThread {
                val tracks = player.currentTracks.groups
                var audioIdx = 0
                for (group in tracks) {
                    if (group.type == C.TRACK_TYPE_AUDIO) {
                        if (audioIdx == index) {
                            player.trackSelectionParameters = player.trackSelectionParameters
                                .buildUpon()
                                .setOverrideForType(
                                    TrackSelectionOverride(group.mediaTrackGroup, 0)
                                )
                                .build()
                            return@runOnUiThread
                        }
                        audioIdx++
                    }
                }
            }
            true
        } catch (e: Exception) {
            false
        }
    }

    @JavascriptInterface
    fun setSubtitleTrack(index: Int): Boolean {
        // Untertitel werden ueber Web-UI gehandelt
        return false
    }

    @JavascriptInterface
    fun setPlaybackSpeed(speed: Float): Boolean {
        return try {
            activity.runOnUiThread {
                exoPlayer?.setPlaybackSpeed(speed)
            }
            true
        } catch (e: Exception) {
            false
        }
    }

    // --- Player.Listener Callbacks ---

    override fun onPlaybackStateChanged(state: Int) {
        when (state) {
            Player.STATE_READY -> {
                callJs("if(window._vkOnReady) window._vkOnReady()")
                callJs("if(window._vkOnBuffering) window._vkOnBuffering(false)")
            }
            Player.STATE_ENDED -> {
                callJs("if(window._vkOnComplete) window._vkOnComplete()")
            }
            Player.STATE_BUFFERING -> {
                callJs("if(window._vkOnBuffering) window._vkOnBuffering(true)")
            }
        }
    }

    override fun onIsPlayingChanged(isPlaying: Boolean) {
        callJs("if(window._vkOnPlayStateChanged) window._vkOnPlayStateChanged($isPlaying)")
    }

    override fun onPlayerError(error: PlaybackException) {
        val msg = error.message?.replace("'", "\\'") ?: "Wiedergabefehler"
        callJs("if(window._vkOnError) window._vkOnError('$msg')")
    }

    // --- Hilfsfunktionen ---

    /** App pausiert -> ExoPlayer pausieren */
    fun pauseIfPlaying() {
        exoPlayer?.playWhenReady = false
    }

    /** Ressourcen freigeben */
    fun release() {
        activity.runOnUiThread {
            releasePlayer()
        }
    }

    private fun releasePlayer() {
        stopTimeUpdates()
        exoPlayer?.release()
        exoPlayer = null
    }

    private fun startTimeUpdates() {
        stopTimeUpdates()
        val handler = Handler(Looper.getMainLooper())
        val runnable = object : Runnable {
            override fun run() {
                val pos = exoPlayer?.currentPosition ?: 0
                callJs("if(window._vkOnTimeUpdate) window._vkOnTimeUpdate($pos)")
                handler.postDelayed(this, 500)
            }
        }
        timeUpdateHandler = handler
        timeUpdateRunnable = runnable
        handler.post(runnable)
    }

    private fun stopTimeUpdates() {
        timeUpdateRunnable?.let { timeUpdateHandler?.removeCallbacks(it) }
        timeUpdateHandler = null
        timeUpdateRunnable = null
    }

    private fun callJs(script: String) {
        activity.runOnUiThread {
            webView.evaluateJavascript(script, null)
        }
    }
}
