package com.musicweb.player.player

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MimeTypes
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaStyleNotificationHelper
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import com.musicweb.player.MainActivity
import com.musicweb.player.R
import org.json.JSONArray

data class TrackItem(
    val path: String,
    val url: String,
    val name: String,
    val folder: String? = null,
)

class PlayerService : MediaSessionService() {
    private lateinit var player: ExoPlayer
    private lateinit var mediaSession: MediaSession
    private val queue = mutableListOf<TrackItem>()
    private var lastErrorMessage: String? = null
    private val progressHandler = Handler(Looper.getMainLooper())
    private val progressRunnable = object : Runnable {
        override fun run() {
            broadcastPlaybackState()
            progressHandler.postDelayed(this, 1000L)
        }
    }

    override fun onCreate() {
        super.onCreate()
        createChannel()
        player = ExoPlayer.Builder(this).build().apply {
            addListener(object : Player.Listener {
                override fun onIsPlayingChanged(isPlaying: Boolean) {
                    updateNotification()
                    broadcastPlaybackState()
                }

                override fun onPlayerError(error: PlaybackException) {
                    lastErrorMessage = error.message ?: error.errorCodeName
                    Log.e(TAG, "playback failed", error)
                    updateNotification()
                    broadcastPlaybackState()
                }

                override fun onPlaybackStateChanged(playbackState: Int) {
                    broadcastPlaybackState()
                }

                override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
                    broadcastPlaybackState()
                }
            })
        }
        mediaSession = MediaSession.Builder(this, player).build()
        progressHandler.post(progressRunnable)
    }

    override fun onBind(intent: Intent?): IBinder? {
        updateNotification()
        return super.onBind(intent)
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = mediaSession

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_SET_QUEUE -> {
                val items = parseQueue(intent.getStringExtra(EXTRA_QUEUE_JSON).orEmpty())
                val index = intent.getIntExtra(EXTRA_INDEX, 0)
                setQueue(items, index, playWhenReady = false)
            }
            ACTION_PLAY_QUEUE -> {
                val items = parseQueue(intent.getStringExtra(EXTRA_QUEUE_JSON).orEmpty())
                val index = intent.getIntExtra(EXTRA_INDEX, 0)
                setQueue(items, index, playWhenReady = true)
            }
            ACTION_TOGGLE -> toggle()
            ACTION_NEXT -> next()
            ACTION_PREVIOUS -> previous()
        }
        updateNotification()
        return START_STICKY
    }

    override fun onDestroy() {
        progressHandler.removeCallbacks(progressRunnable)
        mediaSession.release()
        player.release()
        super.onDestroy()
    }

    private fun setQueue(items: List<TrackItem>, index: Int, playWhenReady: Boolean) {
        if (items.isEmpty()) return
        lastErrorMessage = null
        queue.clear()
        queue.addAll(items)
        val mediaItems = queue.map { item ->
            MediaItem.Builder()
                .setUri(item.url)
                .setMediaId(item.path)
                .setMimeType(mimeTypeFor(item.url, item.path))
                .setMediaMetadata(
                    MediaMetadata.Builder()
                        .setTitle(item.name)
                        .setArtist(item.folder.orEmpty())
                        .build()
                )
                .build()
        }
        val safeIndex = index.coerceIn(0, mediaItems.lastIndex)
        Log.d(TAG, "setQueue size=${mediaItems.size}, index=$safeIndex, url=${queue[safeIndex].url}")
        player.setMediaItems(mediaItems, safeIndex, 0L)
        player.prepare()
        if (playWhenReady) player.play()
        broadcastPlaybackState()
    }

    private fun toggle() {
        if (player.isPlaying) player.pause() else player.play()
    }

    private fun next() {
        if (player.hasNextMediaItem()) player.seekToNextMediaItem()
        player.play()
    }

    private fun previous() {
        if (player.hasPreviousMediaItem()) player.seekToPreviousMediaItem() else player.seekTo(0, 0L)
        player.play()
    }

    private fun updateNotification() {
        startForeground(NOTIFICATION_ID, buildNotification())
    }

    private fun broadcastPlaybackState() {
        val duration = player.duration.takeIf { it > 0 } ?: 0L
        val position = player.currentPosition.coerceAtLeast(0L)
        val current = queue.getOrNull(player.currentMediaItemIndex)
        sendBroadcast(
            Intent(ACTION_PLAYBACK_STATE).apply {
                setPackage(packageName)
                putExtra(EXTRA_IS_PLAYING, player.isPlaying)
                putExtra(EXTRA_POSITION, position)
                putExtra(EXTRA_DURATION, duration)
                putExtra(EXTRA_CURRENT_INDEX, player.currentMediaItemIndex)
                putExtra(EXTRA_TRACK_NAME, current?.name.orEmpty())
                putExtra(EXTRA_TRACK_PATH, current?.path.orEmpty())
                putExtra(EXTRA_ERROR, lastErrorMessage.orEmpty())
            }
        )
    }

    private fun buildNotification(): Notification {
        val openIntent = PendingIntent.getActivity(
            this,
            0,
            Intent(this, MainActivity::class.java),
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )
        val playing = player.isPlaying
        val title = queue.getOrNull(player.currentMediaItemIndex)?.name ?: getString(R.string.app_name)
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle(title)
            .setContentText(lastErrorMessage ?: if (playing) "正在播放" else "已暂停")
            .setContentIntent(openIntent)
            .setOngoing(playing)
            .setOnlyAlertOnce(true)
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setStyle(
                MediaStyleNotificationHelper.MediaStyle(mediaSession)
                    .setShowActionsInCompactView(0, 1, 2)
            )
            .addAction(android.R.drawable.ic_media_previous, "上一曲", commandIntent(ACTION_PREVIOUS, 1))
            .addAction(
                if (playing) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play,
                if (playing) "暂停" else "播放",
                commandIntent(ACTION_TOGGLE, 2)
            )
            .addAction(android.R.drawable.ic_media_next, "下一曲", commandIntent(ACTION_NEXT, 3))
            .build()
    }

    private fun commandIntent(action: String, requestCode: Int): PendingIntent {
        val intent = Intent(this, PlayerService::class.java).apply { this.action = action }
        return PendingIntent.getService(
            this,
            requestCode,
            intent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )
    }

    private fun createChannel() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
        val manager = getSystemService<NotificationManager>() ?: return
        manager.createNotificationChannel(
            NotificationChannel(CHANNEL_ID, "Music Playback", NotificationManager.IMPORTANCE_LOW)
        )
    }

    private fun parseQueue(json: String): List<TrackItem> {
        if (json.isBlank()) return emptyList()
        val array = JSONArray(json)
        return buildList(array.length()) {
            for (i in 0 until array.length()) {
                val item = array.getJSONObject(i)
                add(
                    TrackItem(
                        path = item.optString("path"),
                        url = normalizeUrl(item.optString("url")),
                        name = item.optString("name"),
                        folder = item.optString("folder").takeIf { it.isNotBlank() },
                    )
                )
            }
        }
    }

    private fun normalizeUrl(url: String): String {
        val raw = if (url.startsWith("http://") || url.startsWith("https://")) {
            url
        } else {
            val uri = Uri.parse(url)
            val path = uri.toString().ifBlank { return url }
            "$DEFAULT_BASE_URL${path.trimStart('/')}"
        }
        val parsed = Uri.parse(raw)
        val encodedPath = parsed.pathSegments.joinToString("/", prefix = "/") { Uri.encode(it) }
        return parsed.buildUpon().encodedPath(encodedPath).build().toString()
    }

    private fun mimeTypeFor(url: String, path: String): String? {
        val value = "$path $url".lowercase()
        return when {
            value.contains(".m4a") || value.contains(".mp4") -> MimeTypes.AUDIO_MP4
            value.contains(".aac") -> MimeTypes.AUDIO_AAC
            value.contains(".mp3") -> MimeTypes.AUDIO_MPEG
            value.contains(".ogg") || value.contains(".oga") -> MimeTypes.AUDIO_OGG
            value.contains(".opus") -> MimeTypes.AUDIO_OPUS
            value.contains(".wav") -> MimeTypes.AUDIO_WAV
            value.contains(".flac") -> MimeTypes.AUDIO_FLAC
            else -> null
        }
    }

    companion object {
        private const val TAG = "MusicWebPlayer"
        const val ACTION_SET_QUEUE = "com.musicweb.player.action.SET_QUEUE"
        const val ACTION_PLAY_QUEUE = "com.musicweb.player.action.PLAY_QUEUE"
        const val ACTION_TOGGLE = "com.musicweb.player.action.TOGGLE"
        const val ACTION_NEXT = "com.musicweb.player.action.NEXT"
        const val ACTION_PREVIOUS = "com.musicweb.player.action.PREVIOUS"
        const val ACTION_PLAYBACK_STATE = "com.musicweb.player.action.PLAYBACK_STATE"
        const val EXTRA_QUEUE_JSON = "queue_json"
        const val EXTRA_INDEX = "index"
        const val EXTRA_IS_PLAYING = "is_playing"
        const val EXTRA_POSITION = "position"
        const val EXTRA_DURATION = "duration"
        const val EXTRA_CURRENT_INDEX = "current_index"
        const val EXTRA_TRACK_NAME = "track_name"
        const val EXTRA_TRACK_PATH = "track_path"
        const val EXTRA_ERROR = "error"
        private const val CHANNEL_ID = "musicweb_playback"
        private const val NOTIFICATION_ID = 1001
        private const val DEFAULT_BASE_URL = "http://110.42.102.94:8006/"
    }
}
