package com.dtourolle.jellytau.player

import android.content.Context
import android.content.Intent
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.OptIn
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
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(private val appContext: 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

        /** Static Activity reference that can be set before player initialization */
        @Volatile
        private var pendingActivity: java.lang.ref.WeakReference<android.app.Activity>? = null

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

        /**
         * Set Activity reference (can be called before player is initialized).
         * This is safe to call from MainActivity.onCreate().
         */
        @JvmStatic
        fun setActivity(activity: android.app.Activity) {
            pendingActivity = java.lang.ref.WeakReference(activity)
            android.util.Log.d("JellyTauPlayer", "Activity reference stored in companion object")

            // If player is already initialized, set it there too
            instance?.let { player ->
                player.currentActivity = pendingActivity
                android.util.Log.d("JellyTauPlayer", "Activity reference also set in player instance")
            }
        }

        /**
         * 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).apply {
                            // Use the pending Activity reference if available
                            pendingActivity?.let { activityRef ->
                                currentActivity = activityRef
                                android.util.Log.d("JellyTauPlayer", "Applied pending Activity reference to new instance")
                            }
                        }

                        // Detect and report hardware codec capabilities to Rust
                        detectAndReportCodecs()
                    }
                }
            }
        }

        /**
         * Detect and report hardware codec capabilities to Rust.
         * Called during player initialization.
         */
        @JvmStatic
        fun detectAndReportCodecs() {
            val capabilities = CodecDetector.detectHardwareCodecs()

            // Convert lists to comma-separated strings for JNI transfer
            val videoCodecsStr = capabilities.videoCodecs.joinToString(",")
            val audioCodecsStr = capabilities.audioCodecs.joinToString(",")

            android.util.Log.i("JellyTauPlayer", "Reporting codecs to Rust: video=$videoCodecsStr, audio=$audioCodecsStr")

            // Call native method to store in Rust
            nativeOnCodecsDetected(videoCodecsStr, audioCodecsStr)
        }

        /**
         * Native method to report detected codecs to Rust.
         */
        private external fun nativeOnCodecsDetected(videoCodecs: String, audioCodecs: String)

        /**
         * Check if the player is initialized.
         */
        @JvmStatic
        fun isInitialized(): Boolean {
            return instance != null
        }

        /**
         * 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

    /**
     * Guards against nativeOnPlaybackEnded() firing more than once per loaded
     * media. ExoPlayer can re-enter STATE_ENDED (e.g. transient buffering near
     * end of a transcoded stream), which would otherwise notify the backend
     * twice and, for example, decrement the sleep-timer episode counter twice.
     * Reset whenever new media is loaded.
     */
    private var endedNotified = false

    /** Current media metadata for notification updates */
    private var currentTitle: String = ""
    private var currentArtist: String = ""
    private var currentAlbum: String? = null
    private var currentDurationMs: Long = 0
    private var currentArtworkUrl: String? = null
    private var currentArtworkBitmap: android.graphics.Bitmap? = null

    /** Media type enum */
    enum class MediaType { AUDIO, VIDEO }

    /** SurfaceView for video playback */
    private var surfaceView: SurfaceView? = null
    private var surfaceHolder: SurfaceHolder? = null
    /** Last reported video frame size, used to fit the surface to the screen preserving aspect ratio */
    private var videoWidth: Int = 0
    private var videoHeight: Int = 0
    private var currentMediaType: MediaType = MediaType.AUDIO
    private var currentActivity: java.lang.ref.WeakReference<android.app.Activity>? = null

    /** Audio focus management for video playback */
    private val audioManager: AudioManager by lazy {
        appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
    }
    private var audioFocusRequest: AudioFocusRequest? = null

    init {
        // Configure audio attributes for music playback with audio focus handling
        val audioAttributes = AudioAttributes.Builder()
            .setUsage(C.USAGE_MEDIA)
            .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
            .build()

        // Create ExoPlayer with audio focus handling
        exoPlayer = ExoPlayer.Builder(appContext)
            .setAudioAttributes(audioAttributes, /* handleAudioFocus= */ true)
            // Pause when the audio output is removed (wired headphones unplugged or
            // Bluetooth device disconnected). ExoPlayer listens for the system
            // ACTION_AUDIO_BECOMING_NOISY broadcast, which fires for both cases.
            // The resulting pause flows through onIsPlayingChanged, keeping Rust and
            // the lockscreen notification in sync automatically.
            .setHandleAudioBecomingNoisy(true)
            .build()

        // Set up player listener
        exoPlayer.addListener(object : Player.Listener {
            override fun onPlaybackStateChanged(playbackState: Int) {
                val stateName = when (playbackState) {
                    Player.STATE_IDLE -> "IDLE"
                    Player.STATE_BUFFERING -> "BUFFERING"
                    Player.STATE_READY -> "READY"
                    Player.STATE_ENDED -> "ENDED"
                    else -> "UNKNOWN"
                }
                android.util.Log.d("JellyTauPlayer", "▶ State: $stateName | playWhenReady=${exoPlayer.playWhenReady} | isPlaying=${exoPlayer.isPlaying}")

                when (playbackState) {
                    Player.STATE_READY -> {
                        // Media loaded and ready
                        val duration = exoPlayer.duration / 1000.0
                        android.util.Log.d("JellyTauPlayer", "▶ Media ready! Duration: $duration seconds")
                        nativeOnMediaLoaded(duration)

                        val state = if (exoPlayer.isPlaying) "playing" else "paused"
                        nativeOnStateChanged(state, currentMediaId)

                        // Update notification with loaded metadata
                        updatePlaybackServiceNotification(exoPlayer.isPlaying)
                    }
                    Player.STATE_ENDED -> {
                        // Playback completed
                        android.util.Log.d("JellyTauPlayer", "▶ Playback ended")
                        stopPositionUpdates()
                        // Only notify the backend once per loaded media. ExoPlayer
                        // can re-enter STATE_ENDED, which would double-count things
                        // like the sleep-timer episode counter.
                        if (!endedNotified) {
                            endedNotified = true
                            nativeOnPlaybackEnded()
                        } else {
                            android.util.Log.d("JellyTauPlayer", "▶ Playback ended already notified - ignoring")
                        }
                    }
                    Player.STATE_BUFFERING -> {
                        android.util.Log.d("JellyTauPlayer", "▶ Buffering...")
                        nativeOnBuffering(0)
                    }
                    Player.STATE_IDLE -> {
                        android.util.Log.d("JellyTauPlayer", "▶ Player idle")
                    }
                }
            }

            override fun onIsPlayingChanged(isPlaying: Boolean) {
                android.util.Log.d("JellyTauPlayer", "▶▶▶ IS PLAYING CHANGED: $isPlaying")
                val state = if (isPlaying) "playing" else "paused"
                nativeOnStateChanged(state, currentMediaId)

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

                // Update notification with new play/pause state
                updatePlaybackServiceNotification(isPlaying)
            }

            override fun onPlayerError(error: PlaybackException) {
                android.util.Log.e("JellyTauPlayer", "▶▶▶ PLAYER ERROR: ${error.errorCodeName}", error)
                android.util.Log.e("JellyTauPlayer", "  Error code: ${error.errorCode}")
                android.util.Log.e("JellyTauPlayer", "  Error message: ${error.message}")
                val message = error.message ?: "Unknown playback error"
                val recoverable = error.errorCode != PlaybackException.ERROR_CODE_UNSPECIFIED
                nativeOnError(message, recoverable)
            }

            override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
                android.util.Log.d("JellyTauPlayer", "▶ Video size: ${videoSize.width}x${videoSize.height} par=${videoSize.pixelWidthHeightRatio}")
                // Apply pixel aspect ratio so anamorphic content isn't distorted
                videoWidth = (videoSize.width * videoSize.pixelWidthHeightRatio).toInt()
                videoHeight = videoSize.height
                fitSurfaceToScreen()
            }

            override fun onRenderedFirstFrame() {
                android.util.Log.d("JellyTauPlayer", "▶▶▶ FIRST VIDEO FRAME RENDERED!")
            }

            override fun onVolumeChanged(volume: Float) {
                android.util.Log.d("JellyTauPlayer", "▶ Volume: $volume")
            }

            override fun onTracksChanged(tracks: androidx.media3.common.Tracks) {
                android.util.Log.d("JellyTauPlayer", "▶▶▶ TRACKS CHANGED")
                val audioTracks = tracks.groups.filter { it.type == C.TRACK_TYPE_AUDIO }
                val videoTracks = tracks.groups.filter { it.type == C.TRACK_TYPE_VIDEO }

                android.util.Log.d("JellyTauPlayer", "  Audio tracks: ${audioTracks.size}")
                var hasSelectedAudio = false
                audioTracks.forEach { group ->
                    android.util.Log.d("JellyTauPlayer", "    Audio group: ${group.length} tracks, selected=${group.isSelected}")
                    for (i in 0 until group.length) {
                        val trackFormat = group.getTrackFormat(i)
                        val isSelected = group.isTrackSelected(i)
                        if (isSelected) hasSelectedAudio = true
                        android.util.Log.d("JellyTauPlayer", "      Track $i: ${trackFormat.sampleMimeType}, ${trackFormat.sampleRate}Hz, ${trackFormat.channelCount}ch, selected=$isSelected")
                    }
                }

                android.util.Log.d("JellyTauPlayer", "  Video tracks: ${videoTracks.size}")
                videoTracks.forEach { group ->
                    android.util.Log.d("JellyTauPlayer", "    Video group: ${group.length} tracks, selected=${group.isSelected}")
                }

                // CRITICAL FIX: Auto-select first audio track if none is selected
                // This fixes the issue where some videos play without audio
                if (!hasSelectedAudio && audioTracks.isNotEmpty() && currentMediaType == MediaType.VIDEO) {
                    android.util.Log.w("JellyTauPlayer", "⚠️  NO AUDIO TRACK SELECTED! Auto-selecting first audio track...")

                    val trackSelector = exoPlayer.trackSelector
                    if (trackSelector != null) {
                        try {
                            // Select the first audio track group
                            val firstAudioGroup = audioTracks[0]
                            val override = androidx.media3.common.TrackSelectionOverride(
                                firstAudioGroup.mediaTrackGroup,
                                0 // Select the first track in this group
                            )

                            val parameters = trackSelector.parameters
                                .buildUpon()
                                .clearOverridesOfType(C.TRACK_TYPE_AUDIO)
                                .addOverride(override)
                                .build()

                            trackSelector.setParameters(parameters)
                            android.util.Log.d("JellyTauPlayer", "✓ Auto-selected first audio track")
                        } catch (e: Exception) {
                            android.util.Log.e("JellyTauPlayer", "Failed to auto-select audio track", e)
                        }
                    }
                }
            }

            override fun onAudioSessionIdChanged(audioSessionId: Int) {
                android.util.Log.d("JellyTauPlayer", "▶▶▶ AUDIO SESSION ID CHANGED: $audioSessionId")
            }
        })
    }

    /**
     * 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
            endedNotified = false
            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()

            // Clear video surface if we were playing video
            if (currentMediaType == MediaType.VIDEO) {
                clearVideoSurface()
                // Abandon audio focus when stopping video
                abandonAudioFocus()
            }

            currentMediaId = null
            currentMediaType = MediaType.AUDIO
            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
    }

    /**
     * Set the active audio track by stream index.
     * @param streamIndex The index of the audio track to select
     */
    fun setAudioTrack(streamIndex: Int) {
        mainHandler.post {
            try {
                val trackSelector = exoPlayer.trackSelector
                if (trackSelector != null) {
                    android.util.Log.d("JellyTauPlayer", "Setting audio track to index: $streamIndex")
                    // ExoPlayer uses track groups - find the audio track at the specified index
                    val currentTracks = exoPlayer.currentTracks
                    val audioGroups = currentTracks.groups.filter { it.type == C.TRACK_TYPE_AUDIO }

                    if (audioGroups.isNotEmpty() && streamIndex < audioGroups.size) {
                        // Create override to select the specific audio track
                        val override = androidx.media3.common.TrackSelectionOverride(
                            audioGroups[streamIndex].mediaTrackGroup,
                            0 // Select the first track in this group
                        )

                        val parameters = trackSelector.parameters
                            .buildUpon()
                            .clearOverridesOfType(C.TRACK_TYPE_AUDIO)
                            .addOverride(override)
                            .build()

                        trackSelector.setParameters(parameters)
                        android.util.Log.d("JellyTauPlayer", "Audio track set successfully")
                    } else {
                        android.util.Log.w("JellyTauPlayer", "Invalid audio track index: $streamIndex (available: ${audioGroups.size})")
                    }
                } else {
                    android.util.Log.w("JellyTauPlayer", "TrackSelector not available")
                }
            } catch (e: Exception) {
                android.util.Log.e("JellyTauPlayer", "Failed to set audio track", e)
            }
        }
    }

    /**
     * Set the active subtitle track by stream index.
     * @param streamIndex The index of the subtitle track to select, or -1 to disable subtitles
     */
    fun setSubtitleTrack(streamIndex: Int) {
        mainHandler.post {
            try {
                val trackSelector = exoPlayer.trackSelector
                if (trackSelector != null) {
                    if (streamIndex < 0) {
                        // Disable subtitles
                        android.util.Log.d("JellyTauPlayer", "Disabling subtitles")
                        val parameters = trackSelector.parameters
                            .buildUpon()
                            .clearOverridesOfType(C.TRACK_TYPE_TEXT)
                            .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true)
                            .build()

                        trackSelector.setParameters(parameters)
                        android.util.Log.d("JellyTauPlayer", "Subtitles disabled successfully")
                    } else {
                        // Enable and select specific subtitle track
                        android.util.Log.d("JellyTauPlayer", "Setting subtitle track to index: $streamIndex")
                        val currentTracks = exoPlayer.currentTracks
                        val subtitleGroups = currentTracks.groups.filter { it.type == C.TRACK_TYPE_TEXT }

                        if (subtitleGroups.isNotEmpty() && streamIndex < subtitleGroups.size) {
                            // Create override to select the specific subtitle track
                            val override = androidx.media3.common.TrackSelectionOverride(
                                subtitleGroups[streamIndex].mediaTrackGroup,
                                0 // Select the first track in this group
                            )

                            val parameters = trackSelector.parameters
                                .buildUpon()
                                .clearOverridesOfType(C.TRACK_TYPE_TEXT)
                                .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false)
                                .addOverride(override)
                                .build()

                            trackSelector.setParameters(parameters)
                            android.util.Log.d("JellyTauPlayer", "Subtitle track set successfully")
                        } else {
                            android.util.Log.w("JellyTauPlayer", "Invalid subtitle track index: $streamIndex (available: ${subtitleGroups.size})")
                        }
                    }
                } else {
                    android.util.Log.w("JellyTauPlayer", "TrackSelector not available")
                }
            } catch (e: Exception) {
                android.util.Log.e("JellyTauPlayer", "Failed to set subtitle track", e)
            }
        }
    }

    /**
     * Get the ExoPlayer instance for MediaSession integration.
     */
    fun getExoPlayer(): ExoPlayer = exoPlayer

    /**
     * Get the application context.
     */
    fun getAppContext(): Context = appContext

    /**
     * Load media with full metadata for MediaSession display.
     * @param url The media URL to load
     * @param mediaId The unique ID for this media item
     * @param title The display title
     * @param artist The artist name (nullable)
     * @param album The album name (nullable)
     * @param artworkUrl URL to artwork image (nullable)
     * @param durationMs Duration in milliseconds
     * @param mediaType Type of media ("audio" or "video")
     * @param subtitlesJson JSON array of subtitle tracks
     */
    fun loadWithMetadata(
        url: String,
        mediaId: String,
        title: String,
        artist: String?,
        album: String?,
        artworkUrl: String?,
        durationMs: Long,
        mediaType: String = "audio",
        subtitlesJson: String = "[]"
    ) {
        mainHandler.post {
            currentMediaId = mediaId
            endedNotified = false

            // Store metadata for notification updates
            currentTitle = title
            currentArtist = artist ?: ""
            currentAlbum = album
            currentDurationMs = durationMs
            currentArtworkUrl = artworkUrl
            currentArtworkBitmap = null  // Reset on new track

            // Detect media type
            currentMediaType = if (mediaType.equals("video", ignoreCase = true)) {
                MediaType.VIDEO
            } else {
                MediaType.AUDIO
            }

            android.util.Log.d("JellyTauPlayer", "Loading media: type=$currentMediaType, url=$url")

            // Update audio attributes based on media type
            val audioAttributes = if (currentMediaType == MediaType.VIDEO) {
                AudioAttributes.Builder()
                    .setUsage(C.USAGE_MEDIA)
                    .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
                    .build()
            } else {
                AudioAttributes.Builder()
                    .setUsage(C.USAGE_MEDIA)
                    .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
                    .build()
            }

            // For video: handleAudioFocus=false because we manage it manually via requestAudioFocus()
            // For audio: handleAudioFocus=true to let ExoPlayer manage it automatically
            val handleAudioFocus = currentMediaType == MediaType.AUDIO
            exoPlayer.setAudioAttributes(audioAttributes, handleAudioFocus)
            android.util.Log.d("JellyTauPlayer", "Audio attributes set for $currentMediaType (handleAudioFocus=$handleAudioFocus)")

            // Log audio capabilities
            android.util.Log.d("JellyTauPlayer", "Audio settings: usage=${audioAttributes.usage}, contentType=${audioAttributes.contentType}")
            android.util.Log.d("JellyTauPlayer", "ExoPlayer audio session ID: ${exoPlayer.audioSessionId}")

            // Setup video surface if needed
            if (currentMediaType == MediaType.VIDEO) {
                getOrCreateSurfaceView()
                android.util.Log.d("JellyTauPlayer", "Video surface created for playback")
                // Automatically attach the surface to the Activity
                autoAttachSurface()

                // CRITICAL: Request audio focus for video playback
                requestAudioFocus()
            } else {
                clearVideoSurface()
                // Abandon audio focus when switching to audio (audio uses ExoPlayer's built-in handling)
                abandonAudioFocus()
            }

            // Build MediaItem with metadata for notification display
            val metadata = MediaMetadata.Builder()
                .setTitle(title)
                .setArtist(artist)
                .setAlbumTitle(album)
                .apply {
                    artworkUrl?.let { url ->
                        setArtworkUri(Uri.parse(url))
                    }
                }
                .build()

            // Parse subtitle tracks from JSON
            val subtitleConfigurations = mutableListOf<MediaItem.SubtitleConfiguration>()
            try {
                val subtitlesArray = org.json.JSONArray(subtitlesJson)
                android.util.Log.d("JellyTauPlayer", "Parsing ${subtitlesArray.length()} subtitle tracks")

                for (i in 0 until subtitlesArray.length()) {
                    val subtitle = subtitlesArray.getJSONObject(i)
                    val subtitleUrl = subtitle.getString("url")
                    val language = subtitle.optString("language", null)
                    val label = subtitle.optString("label", language ?: "Track $i")
                    val mimeType = subtitle.optString("mime_type", androidx.media3.common.MimeTypes.TEXT_VTT)

                    val config = MediaItem.SubtitleConfiguration.Builder(android.net.Uri.parse(subtitleUrl))
                        .setMimeType(mimeType)
                        .setLanguage(language)
                        .setLabel(label)
                        .build()

                    subtitleConfigurations.add(config)
                    android.util.Log.d("JellyTauPlayer", "Added subtitle: $label ($language) - $subtitleUrl")
                }
            } catch (e: Exception) {
                android.util.Log.w("JellyTauPlayer", "Failed to parse subtitles JSON: ${e.message}")
            }

            val mediaItemBuilder = MediaItem.Builder()
                .setUri(url)
                .setMediaId(mediaId)
                .setMediaMetadata(metadata)

            // Add subtitles if available
            if (subtitleConfigurations.isNotEmpty()) {
                mediaItemBuilder.setSubtitleConfigurations(subtitleConfigurations)
                android.util.Log.d("JellyTauPlayer", "Configured ${subtitleConfigurations.size} subtitle tracks")
            }

            val mediaItem = mediaItemBuilder.build()

            exoPlayer.setMediaItem(mediaItem)
            android.util.Log.d("JellyTauPlayer", "Media item set, calling prepare()...")
            exoPlayer.prepare()
            android.util.Log.d("JellyTauPlayer", "prepare() called")

            // Ensure volume is not muted (especially important for video)
            if (exoPlayer.volume < 0.1f) {
                exoPlayer.volume = 1.0f
                android.util.Log.w("JellyTauPlayer", "⚠️  Volume was low (${exoPlayer.volume}), set to 1.0")
            }
            android.util.Log.d("JellyTauPlayer", "✓ Current volume: ${exoPlayer.volume}, deviceVolume: ${audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)}/${audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)}")

            exoPlayer.playWhenReady = true
            android.util.Log.d("JellyTauPlayer", "playWhenReady set to TRUE. Current state: ${exoPlayer.playbackState}")

            // Start the foreground service for lockscreen controls
            startPlaybackService()

            // Download album art asynchronously (non-blocking)
            currentArtworkUrl?.let { url ->
                coroutineScope.launch {
                    try {
                        val bitmap = AlbumArtCache.getInstance(appContext).getArtwork(url)
                        currentArtworkBitmap = bitmap
                        updatePlaybackServiceNotification(exoPlayer.isPlaying)
                        android.util.Log.d("JellyTauPlayer", "Album art loaded: ${bitmap?.width}x${bitmap?.height}")
                    } catch (e: Exception) {
                        android.util.Log.e("JellyTauPlayer", "Failed to load album art", e)
                    }
                }
            }
        }
    }

    /**
     * Start the playback service for foreground notification and lockscreen controls.
     */
    fun startPlaybackService() {
        try {
            val intent = Intent(appContext, JellyTauPlaybackService::class.java)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                appContext.startForegroundService(intent)
            } else {
                appContext.startService(intent)
            }
            android.util.Log.d("JellyTauPlayer", "Playback service started")
        } catch (e: Exception) {
            // Log but don't fail - playback can work without the service
            android.util.Log.e("JellyTauPlayer", "Failed to start playback service", e)
        }
    }

    /**
     * Stop the playback service.
     */
    fun stopPlaybackService() {
        try {
            appContext.stopService(Intent(appContext, JellyTauPlaybackService::class.java))
        } catch (e: Exception) {
            android.util.Log.e("JellyTauPlayer", "Failed to stop playback service", e)
        }
    }

    /**
     * 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 {
            android.util.Log.d("JellyTauPlayer", "Started position updates coroutine")
            while (isActive) {
                if (exoPlayer.isPlaying) {
                    val positionMs = exoPlayer.currentPosition.coerceAtLeast(0)
                    val position = positionMs / 1000.0
                    val duration = if (exoPlayer.duration > 0) exoPlayer.duration / 1000.0 else 0.0
                    android.util.Log.v("JellyTauPlayer", "Position update: $position / $duration")
                    nativeOnPositionUpdate(position, duration)

                    // Keep the lockscreen scrubber live. Without this the
                    // MediaSession position only refreshes on play/pause, so the
                    // scrubber freezes mid-track and drifts out of sync.
                    JellyTauPlaybackService.getInstance()?.updatePlaybackPosition(positionMs, true)
                }
                delay(POSITION_UPDATE_INTERVAL_MS)
            }
            android.util.Log.d("JellyTauPlayer", "Position updates coroutine ended")
        }
    }

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

    /**
     * Get or create the SurfaceView for video playback.
     * Returns the view ID that can be attached to the view hierarchy.
     *
     * Note: The surface is created but not automatically attached to the view hierarchy.
     * Call attachSurfaceToActivity() or use VideoOverlayManager to attach it.
     */
    fun getOrCreateSurfaceView(): Int {
        if (surfaceView == null) {
            surfaceView = SurfaceView(appContext).apply {
                layoutParams = FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
                // Render BEHIND WebView - video shows through transparent areas
                setZOrderMediaOverlay(false)

                // Set up SurfaceHolder callbacks
                holder.addCallback(object : SurfaceHolder.Callback {
                    override fun surfaceCreated(holder: SurfaceHolder) {
                        android.util.Log.d("JellyTauPlayer", "Surface created")
                        surfaceHolder = holder
                        exoPlayer.setVideoSurfaceHolder(holder)
                        android.util.Log.d("JellyTauPlayer", "Video surface attached to ExoPlayer")
                    }

                    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
                        android.util.Log.d("JellyTauPlayer", "Surface changed: ${width}x${height}")
                    }

                    override fun surfaceDestroyed(holder: SurfaceHolder) {
                        android.util.Log.d("JellyTauPlayer", "Surface destroyed")
                        exoPlayer.clearVideoSurfaceHolder(holder)
                        surfaceHolder = null
                    }
                })
            }
        }
        return surfaceView!!.hashCode()
    }

    /**
     * Get the SurfaceView instance (for VideoOverlayManager).
     * Returns null if no surface has been created yet.
     */
    fun getSurfaceView(): SurfaceView? {
        return surfaceView
    }

    /**
     * Check if currently playing video (as opposed to audio).
     */
    fun isPlayingVideo(): Boolean {
        return currentMediaType == MediaType.VIDEO
    }

    /**
     * Attach the current surface to the given Activity.
     * This should be called from MainActivity when video playback is active.
     */
    fun attachSurfaceToActivity(activity: android.app.Activity) {
        if (surfaceView != null && currentMediaType == MediaType.VIDEO) {
            com.dtourolle.jellytau.VideoOverlayManager.attachVideoSurface(activity)
            android.util.Log.d("JellyTauPlayer", "Surface attached to Activity")
        }
    }

    /**
     * Automatically attach surface using stored Activity reference.
     */
    private fun autoAttachSurface() {
        // Get Activity reference from native context
        try {
            // The Activity is passed via JNI, so we'll get it from there
            // For now, just log that we need it
            android.util.Log.d("JellyTauPlayer", "autoAttachSurface called - Activity reference needed")

            currentActivity?.get()?.let { activity ->
                mainHandler.post {
                    attachSurfaceToActivity(activity)
                }
            } ?: run {
                android.util.Log.w("JellyTauPlayer", "Cannot attach surface - no Activity reference")
            }
        } catch (e: Exception) {
            android.util.Log.e("JellyTauPlayer", "Failed to auto-attach surface", e)
        }
    }

    /**
     * Resize the video surface (for orientation changes).
     *
     * Re-fits the surface to the screen preserving the video's aspect ratio so
     * nothing is cropped when the device rotates.
     */
    fun resizeSurface(width: Int, height: Int) {
        fitSurfaceToScreen()
    }

    /**
     * Size the video SurfaceView so the video fits entirely inside its parent
     * (the full-screen content view) while preserving aspect ratio (letterbox/
     * pillarbox). A raw SurfaceView with MATCH_PARENT otherwise stretches the
     * video to the surface bounds, which crops the bottom on rotation.
     */
    fun fitSurfaceToScreen() {
        mainHandler.post {
            val view = surfaceView ?: return@post
            val parent = view.parent as? ViewGroup
            // Available area: prefer the parent's measured size, fall back to the screen.
            val availW = parent?.width?.takeIf { it > 0 }
                ?: appContext.resources.displayMetrics.widthPixels
            val availH = parent?.height?.takeIf { it > 0 }
                ?: appContext.resources.displayMetrics.heightPixels

            if (videoWidth <= 0 || videoHeight <= 0 || availW <= 0 || availH <= 0) {
                return@post
            }

            val videoAspect = videoWidth.toFloat() / videoHeight.toFloat()
            val viewAspect = availW.toFloat() / availH.toFloat()

            val targetW: Int
            val targetH: Int
            if (videoAspect > viewAspect) {
                // Video is wider than the screen → fit width, letterbox top/bottom
                targetW = availW
                targetH = (availW / videoAspect).toInt()
            } else {
                // Video is taller than the screen → fit height, pillarbox sides
                targetH = availH
                targetW = (availH * videoAspect).toInt()
            }

            val lp = view.layoutParams
            // FrameLayout child: center the fitted surface within the full-screen parent.
            if (lp is FrameLayout.LayoutParams) {
                lp.gravity = android.view.Gravity.CENTER
            }
            lp.width = targetW
            lp.height = targetH
            view.layoutParams = lp
            view.requestLayout()
            android.util.Log.d(
                "JellyTauPlayer",
                "Video surface fitted to ${targetW}x${targetH} (video ${videoWidth}x${videoHeight}, avail ${availW}x${availH})"
            )
        }
    }

    /**
     * Clear the video surface when switching to audio playback.
     */
    private fun clearVideoSurface() {
        surfaceView?.let {
            exoPlayer.clearVideoSurface()
            surfaceView = null
            surfaceHolder = null
            android.util.Log.d("JellyTauPlayer", "Video surface cleared")
        }
    }

    /**
     * Request audio focus for video playback.
     * This is critical for video to have audio on Android.
     */
    private fun requestAudioFocus() {
        android.util.Log.d("JellyTauPlayer", "Requesting audio focus for video playback")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val audioAttributes = android.media.AudioAttributes.Builder()
                .setUsage(android.media.AudioAttributes.USAGE_MEDIA)
                .setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
                .build()

            audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                .setAudioAttributes(audioAttributes)
                .setAcceptsDelayedFocusGain(true)
                .setOnAudioFocusChangeListener { focusChange ->
                    android.util.Log.d("JellyTauPlayer", "Audio focus changed: $focusChange")
                    when (focusChange) {
                        AudioManager.AUDIOFOCUS_GAIN -> {
                            android.util.Log.d("JellyTauPlayer", "✓ Audio focus GAINED - ensuring full volume")
                            if (exoPlayer.volume < 1.0f) {
                                exoPlayer.volume = 1.0f
                                android.util.Log.d("JellyTauPlayer", "  Volume restored to 1.0 from ${exoPlayer.volume}")
                            }
                        }
                        AudioManager.AUDIOFOCUS_LOSS -> {
                            android.util.Log.d("JellyTauPlayer", "Audio focus LOST - pausing")
                            pause()
                        }
                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
                            android.util.Log.d("JellyTauPlayer", "Audio focus LOST TRANSIENT - pausing")
                            pause()
                        }
                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
                            android.util.Log.d("JellyTauPlayer", "Audio focus LOST TRANSIENT CAN DUCK - lowering volume")
                            exoPlayer.volume = 0.3f
                        }
                    }
                }
                .build()

            val result = audioManager.requestAudioFocus(audioFocusRequest!!)
            when (result) {
                AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
                    android.util.Log.d("JellyTauPlayer", "✓ Audio focus GRANTED")
                }
                AudioManager.AUDIOFOCUS_REQUEST_FAILED -> {
                    android.util.Log.e("JellyTauPlayer", "✗ Audio focus REQUEST FAILED!")
                }
                AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
                    android.util.Log.d("JellyTauPlayer", "⏳ Audio focus DELAYED")
                }
            }
        } else {
            @Suppress("DEPRECATION")
            val result = audioManager.requestAudioFocus(
                { focusChange ->
                    android.util.Log.d("JellyTauPlayer", "Audio focus changed (legacy): $focusChange")
                },
                AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN
            )
            if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                android.util.Log.d("JellyTauPlayer", "✓ Audio focus GRANTED (legacy)")
            } else {
                android.util.Log.e("JellyTauPlayer", "✗ Audio focus REQUEST FAILED (legacy)!")
            }
        }
    }

    /**
     * Abandon audio focus when stopping video playback.
     */
    private fun abandonAudioFocus() {
        android.util.Log.d("JellyTauPlayer", "Abandoning audio focus")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            audioFocusRequest?.let {
                val result = audioManager.abandonAudioFocusRequest(it)
                android.util.Log.d("JellyTauPlayer", "Audio focus abandoned: result=$result")
            }
        } else {
            @Suppress("DEPRECATION")
            audioManager.abandonAudioFocus { }
        }
        audioFocusRequest = 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)

    /**
     * Update the playback service notification with current metadata and state.
     */
    private fun updatePlaybackServiceNotification(isPlaying: Boolean) {
        try {
            val service = JellyTauPlaybackService.getInstance()
            if (service != null) {
                service.updateMediaMetadata(
                    title = currentTitle,
                    artist = currentArtist,
                    album = currentAlbum,
                    duration = currentDurationMs,
                    position = (exoPlayer.currentPosition).coerceAtLeast(0),
                    isPlaying = isPlaying
                )
            } else {
                android.util.Log.w("JellyTauPlayer", "Playback service not available for notification update")
            }
        } catch (e: Exception) {
            android.util.Log.e("JellyTauPlayer", "Failed to update playback service notification", e)
        }
    }
}
