package com.dtourolle.jellytau.player

import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import kotlinx.coroutines.*

/**
 * JellyTau media player wrapper using ExoPlayer (Media3).
 *
 * This class is designed to be called from Rust via JNI.
 * All player operations are marshalled to the main thread.
 */
@OptIn(UnstableApi::class)
class JellyTauPlayer(context: Context) {

    companion object {
        /** Position update interval in milliseconds */
        private const val POSITION_UPDATE_INTERVAL_MS = 250L

        /** Singleton instance for JNI access */
        @Volatile
        private var instance: JellyTauPlayer? = null

        init {
            // Load the native library for JNI callbacks
            System.loadLibrary("jellytau_lib")
        }

        /**
         * Initialize the player singleton.
         * Called from Rust via JNI during Android startup.
         */
        @JvmStatic
        fun initialize(context: Context) {
            if (instance == null) {
                synchronized(this) {
                    if (instance == null) {
                        instance = JellyTauPlayer(context.applicationContext)
                    }
                }
            }
        }

        /**
         * Get the singleton instance.
         * @throws IllegalStateException if not initialized
         */
        @JvmStatic
        fun getInstance(): JellyTauPlayer {
            return instance ?: throw IllegalStateException("JellyTauPlayer not initialized")
        }
    }

    private val mainHandler = Handler(Looper.getMainLooper())
    private val exoPlayer: ExoPlayer
    private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    private var positionUpdateJob: Job? = null

    /** Current media ID being played */
    private var currentMediaId: String? = null

    init {
        // Create ExoPlayer on main thread
        exoPlayer = ExoPlayer.Builder(context).build()

        // Set up player listener
        exoPlayer.addListener(object : Player.Listener {
            override fun onPlaybackStateChanged(playbackState: Int) {
                when (playbackState) {
                    Player.STATE_READY -> {
                        // Media loaded and ready
                        val duration = exoPlayer.duration / 1000.0
                        nativeOnMediaLoaded(duration)

                        val state = if (exoPlayer.isPlaying) "playing" else "paused"
                        nativeOnStateChanged(state, currentMediaId)
                    }
                    Player.STATE_ENDED -> {
                        // Playback completed
                        stopPositionUpdates()
                        nativeOnPlaybackEnded()
                    }
                    Player.STATE_BUFFERING -> {
                        nativeOnBuffering(0)
                    }
                    Player.STATE_IDLE -> {
                        // Player is idle
                    }
                }
            }

            override fun onIsPlayingChanged(isPlaying: Boolean) {
                val state = if (isPlaying) "playing" else "paused"
                nativeOnStateChanged(state, currentMediaId)

                if (isPlaying) {
                    startPositionUpdates()
                } else {
                    stopPositionUpdates()
                }
            }

            override fun onPlayerError(error: PlaybackException) {
                val message = error.message ?: "Unknown playback error"
                val recoverable = error.errorCode != PlaybackException.ERROR_CODE_UNSPECIFIED
                nativeOnError(message, recoverable)
            }
        })
    }

    /**
     * Load media from a URL.
     * @param url The media URL to load
     * @param mediaId The unique ID for this media item
     */
    fun load(url: String, mediaId: String) {
        mainHandler.post {
            currentMediaId = mediaId
            val mediaItem = MediaItem.fromUri(url)
            exoPlayer.setMediaItem(mediaItem)
            exoPlayer.prepare()
            exoPlayer.playWhenReady = true
        }
    }

    /**
     * Start or resume playback.
     */
    fun play() {
        mainHandler.post {
            exoPlayer.play()
        }
    }

    /**
     * Pause playback.
     */
    fun pause() {
        mainHandler.post {
            exoPlayer.pause()
        }
    }

    /**
     * Stop playback and release media.
     */
    fun stop() {
        mainHandler.post {
            stopPositionUpdates()
            exoPlayer.stop()
            exoPlayer.clearMediaItems()
            currentMediaId = null
            nativeOnStateChanged("idle", null)
        }
    }

    /**
     * Seek to a position.
     * @param positionSeconds Position in seconds
     */
    fun seek(positionSeconds: Double) {
        mainHandler.post {
            val positionMs = (positionSeconds * 1000).toLong()
            exoPlayer.seekTo(positionMs)
        }
    }

    /**
     * Set the volume.
     * @param volume Volume level from 0.0 to 1.0
     */
    fun setVolume(volume: Float) {
        mainHandler.post {
            exoPlayer.volume = volume.coerceIn(0f, 1f)
            nativeOnVolumeChanged(exoPlayer.volume, false)
        }
    }

    /**
     * Get the current playback position in seconds.
     */
    fun getPosition(): Double {
        return exoPlayer.currentPosition / 1000.0
    }

    /**
     * Get the total duration in seconds.
     */
    fun getDuration(): Double {
        val duration = exoPlayer.duration
        return if (duration > 0) duration / 1000.0 else 0.0
    }

    /**
     * Get the current volume.
     */
    fun getVolume(): Float {
        return exoPlayer.volume
    }

    /**
     * Check if media is currently loaded.
     */
    fun isLoaded(): Boolean {
        return exoPlayer.playbackState == Player.STATE_READY ||
               exoPlayer.playbackState == Player.STATE_BUFFERING
    }

    /**
     * Release player resources.
     * Call when the app is closing.
     */
    fun release() {
        mainHandler.post {
            stopPositionUpdates()
            coroutineScope.cancel()
            exoPlayer.release()
            instance = null
        }
    }

    private fun startPositionUpdates() {
        positionUpdateJob?.cancel()
        positionUpdateJob = coroutineScope.launch {
            while (isActive) {
                if (exoPlayer.isPlaying) {
                    val position = exoPlayer.currentPosition / 1000.0
                    val duration = if (exoPlayer.duration > 0) exoPlayer.duration / 1000.0 else 0.0
                    nativeOnPositionUpdate(position, duration)
                }
                delay(POSITION_UPDATE_INTERVAL_MS)
            }
        }
    }

    private fun stopPositionUpdates() {
        positionUpdateJob?.cancel()
        positionUpdateJob = null
    }

    // Native methods to call back to Rust via JNI
    // These will be implemented in the Rust android module

    /**
     * Called when position updates during playback.
     */
    private external fun nativeOnPositionUpdate(position: Double, duration: Double)

    /**
     * Called when player state changes.
     */
    private external fun nativeOnStateChanged(state: String, mediaId: String?)

    /**
     * Called when media has finished loading.
     */
    private external fun nativeOnMediaLoaded(duration: Double)

    /**
     * Called when playback reaches the end.
     */
    private external fun nativeOnPlaybackEnded()

    /**
     * Called when buffering state changes.
     */
    private external fun nativeOnBuffering(percent: Int)

    /**
     * Called when a playback error occurs.
     */
    private external fun nativeOnError(message: String, recoverable: Boolean)

    /**
     * Called when volume changes.
     */
    private external fun nativeOnVolumeChanged(volume: Float, muted: Boolean)
}
