package dev.castarr.tv

import android.media.AudioManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.KeyEvent
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.lifecycle.lifecycleScope
import dev.castarr.tv.update.UpdateChecker
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import dev.castarr.tv.auth.DeviceAuth
import dev.castarr.tv.data.DispatcharrRepository
import dev.castarr.tv.data.SourceRepository
import dev.castarr.tv.player.PlayerController
import dev.castarr.tv.playlist.Channel
import dev.castarr.tv.server.ControlServer
import dev.castarr.tv.ui.CastarrApp
import org.json.JSONObject

class MainActivity : ComponentActivity(), ControlServer.Listener {

    private lateinit var state: AppState
    private lateinit var server: ControlServer
    private lateinit var audioManager: AudioManager

    private val mainHandler = Handler(Looper.getMainLooper())
    private val ticker = object : Runnable {
        override fun run() {
            state.syncFromPlayer()
            server.broadcastStatus()
            mainHandler.postDelayed(this, TICK_INTERVAL_MS)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
        val auth = DeviceAuth(this)
        state = AppState(
            context = this,
            player = PlayerController(this) { onPlaybackChanged() },
            source = SourceRepository(this),
            auth = auth,
            dispatcharr = DispatcharrRepository(this, auth),
        )
        server = ControlServer(this, this)
        server.startServer()
        state.remoteAvailable = server.running
        setContent { CastarrApp(state) }
        lifecycleScope.launch { UpdateChecker.check(state) }
    }

    private fun onPlaybackChanged() {
        state.syncFromPlayer()
        val active = state.playerState == "playing" || state.playerState == "buffering"
        window.decorView.keepScreenOn = active
        mainHandler.removeCallbacks(ticker)
        if (active) mainHandler.postDelayed(ticker, TICK_INTERVAL_MS)
        server.broadcastStatus()
    }

    // --- ControlServer.Listener (called on main thread) ---

    override fun onPlay(url: String, name: String, group: String) {
        state.play(Channel(name = name, url = url, group = group, logo = ""))
    }

    override fun onTogglePlay() = state.player.toggle()

    override fun onPauseCast() = state.player.pause()

    override fun onResumeCast() = state.player.resume()

    override fun onStopCast() = state.stopPlayback()

    override fun onSeek(deltaSeconds: Long) {
        state.player.seekBy(deltaSeconds)
        state.overlayVisible = true
    }

    override fun onVolume(value: Float) {
        val max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (value * max).toInt().coerceIn(0, max), 0)
        server.broadcastStatus()
    }

    override fun onSetPlaylist(url: String) {
        state.source.configure(url, state.source.epgUrl) { result ->
            mainHandler.post {
                result
                    .onSuccess { server.broadcastChannels() }
                    .onFailure { server.broadcastToast(getString(R.string.playlist_error)) }
            }
        }
    }

    override fun onClientsChanged(count: Int, newestName: String?) {
        if (newestName != null) state.connectedRemote = newestName
        if (count == 0) state.connectedRemote = null
    }

    override fun currentStatus(): JSONObject {
        val max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
        val vol = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
        val player = state.player
        return JSONObject()
            .put("state", player.state)
            .put("channel", player.channelName)
            .put("group", player.channelGroup)
            .put("live", player.player.isCurrentMediaItemLive)
            .put("seekable", player.player.isCurrentMediaItemSeekable)
            .put("position", player.player.currentPosition.coerceAtLeast(0))
            .put("duration", player.player.duration.coerceAtLeast(0))
            .put("volume", if (max > 0) vol.toDouble() / max else 0.0)
            .put("error", player.errorMessage ?: "")
            .put("reconnecting", player.reconnecting)
            .put("audioTracks", org.json.JSONArray().also { arr ->
                state.audioTracks.forEach { arr.put(it.label) }
            })
            .put("audioSelected", state.audioTracks.indexOfFirst { it.selected })
    }

    override fun currentChannels(): List<Channel> = state.activeChannels()

    override fun channelsExtras(): JSONObject {
        val nowNext = org.json.JSONArray()
        val channels = state.activeChannels()
        channels.forEach { channel ->
            val info = state.nowNext(channel)
            nowNext.put(
                JSONObject()
                    .put("now", info.now?.title ?: "")
                    .put("start", info.now?.start ?: 0)
                    .put("stop", info.now?.stop ?: 0)
                    .put("next", info.next?.title ?: "")
            )
        }
        val favorites = org.json.JSONArray()
        state.dispatcharr.favorites.value.forEach { favorites.put(it) }
        return JSONObject()
            .put("nowNext", nowNext)
            .put("favorites", favorites)
            .put("favoritesSupported", state.sourceMode == AppState.SourceMode.DISPATCHARR)
    }

    override fun setupNeeded(): Boolean = state.screen == AppState.Screen.WELCOME

    override fun onConfigureServer(url: String) {
        if (state.screen != AppState.Screen.WELCOME || url.isBlank()) return
        state.welcomePhase = AppState.WelcomePhase.WAIT_URL
        lifecycleScope.launch {
            try {
                state.auth.fetchServerConfig(url.trim())
                val session = state.auth.startDeviceFlow()
                state.welcomeUserCode = session.userCode
                state.welcomePhase = AppState.WelcomePhase.WAIT_LOGIN
                server.broadcastLoginLink(session.verificationUriComplete, session.userCode)
                val deadline = System.currentTimeMillis() + session.expiresInSeconds * 1000L
                while (System.currentTimeMillis() < deadline) {
                    delay(session.intervalSeconds * 1000L)
                    when (state.auth.poll(session)) {
                        is dev.castarr.tv.auth.DeviceAuth.PollResult.Success -> {
                            state.welcomePhase = AppState.WelcomePhase.DONE
                            server.broadcastSetupDone()
                            state.setMode(AppState.SourceMode.DISPATCHARR)
                            delay(1500)
                            state.screen = AppState.Screen.LIVE
                            return@launch
                        }
                        dev.castarr.tv.auth.DeviceAuth.PollResult.Denied -> {
                            server.broadcastToast("Anmeldung abgelehnt — bitte erneut versuchen")
                            state.welcomePhase = AppState.WelcomePhase.WAIT_PHONE
                            state.welcomeUserCode = ""
                            return@launch
                        }
                        dev.castarr.tv.auth.DeviceAuth.PollResult.Pending -> Unit
                    }
                }
                server.broadcastToast("Anmeldecode abgelaufen — bitte erneut versuchen")
                state.welcomePhase = AppState.WelcomePhase.WAIT_PHONE
            } catch (e: Exception) {
                android.util.Log.w("Onboarding", "configure failed: ${e.javaClass.simpleName}")
                server.broadcastToast("Server nicht erreichbar oder ohne Anmeldung")
                state.welcomePhase = AppState.WelcomePhase.WAIT_PHONE
            }
        }
    }

    override fun onToggleFavorite(channelId: Int) {
        val channel = state.activeChannels().firstOrNull { it.backendId == channelId } ?: return
        state.dispatcharr.toggleFavorite(channel)
        mainHandler.postDelayed({ server.broadcastChannels() }, 800)
    }

    override fun currentPlaylistUrl(): String = state.source.m3uUrl

    // --- TV remote keys during playback (TV-PC / TV-PP) ---

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        // Number pad: type the channel number shown in the list (position in
        // "Alle Sender"), switch after a short pause or as soon as no longer
        // number can match. Ignored on screens with text fields.
        if (keyCode in KeyEvent.KEYCODE_0..KeyEvent.KEYCODE_9 &&
            state.screen != AppState.Screen.WELCOME && state.screen != AppState.Screen.ADVANCED
        ) {
            onDigit(keyCode - KeyEvent.KEYCODE_0)
            return true
        }
        if (state.playerVisible) {
            when (keyCode) {
                // OK opens the control overlay (buttons take over from
                // there via normal Compose focus); with the overlay open
                // this key is consumed by the focused button and never
                // reaches the activity.
                KeyEvent.KEYCODE_DPAD_CENTER,
                KeyEvent.KEYCODE_ENTER -> {
                    state.pingOverlay()
                    return true
                }
                KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
                    state.player.toggle()
                    return true
                }
                KeyEvent.KEYCODE_MEDIA_PLAY -> {
                    state.player.resume()
                    return true
                }
                KeyEvent.KEYCODE_MEDIA_PAUSE -> {
                    state.player.pause()
                    return true
                }
                KeyEvent.KEYCODE_MEDIA_STOP -> {
                    state.stopPlayback()
                    return true
                }
                KeyEvent.KEYCODE_DPAD_UP,
                KeyEvent.KEYCODE_CHANNEL_UP -> {
                    state.zap(-1)
                    return true
                }
                KeyEvent.KEYCODE_DPAD_DOWN,
                KeyEvent.KEYCODE_CHANNEL_DOWN -> {
                    state.zap(1)
                    return true
                }
                // With the overlay open, left/right moves button focus and
                // never reaches the activity. Hidden overlay: seek on
                // seekable media, otherwise just bring the controls up.
                KeyEvent.KEYCODE_DPAD_LEFT,
                KeyEvent.KEYCODE_MEDIA_REWIND -> {
                    if (state.player.player.isCurrentMediaItemSeekable) onSeek(-SEEK_STEP_SECONDS)
                    else state.pingOverlay()
                    return true
                }
                KeyEvent.KEYCODE_DPAD_RIGHT,
                KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> {
                    if (state.player.player.isCurrentMediaItemSeekable) onSeek(SEEK_STEP_SECONDS)
                    else state.pingOverlay()
                    return true
                }
                KeyEvent.KEYCODE_BACK -> {
                    if (state.overlayVisible) state.overlayVisible = false
                    else state.stopPlayback()
                    return true
                }
                else -> Unit
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    private val digitCommit = Runnable { commitDigits() }

    private fun onDigit(digit: Int) {
        if (state.digitBuffer.length >= 4) return
        state.digitBuffer += digit.toString()
        mainHandler.removeCallbacks(digitCommit)
        val number = state.digitBuffer.toIntOrNull() ?: return commitDigits()
        // Another digit could still form a valid number? Then wait for it.
        if (number * 10 > state.activeChannels().size) commitDigits()
        else mainHandler.postDelayed(digitCommit, DIGIT_COMMIT_MS)
    }

    private fun commitDigits() {
        mainHandler.removeCallbacks(digitCommit)
        val number = state.digitBuffer.toIntOrNull()
        state.digitBuffer = ""
        val channel = number?.let { state.activeChannels().getOrNull(it - 1) } ?: return
        state.play(channel)
    }

    private fun cycleAudioWithOverlay(direction: Int) {
        state.overlayVisible = true
        state.cycleAudio(direction)
        server.broadcastStatus()
    }

    override fun onSetAudio(index: Int) {
        if (index < 0) return
        state.player.selectAudio(index)
        state.syncFromPlayer()
        state.overlayVisible = true
        server.broadcastStatus()
    }

    // --- lifecycle ---

    override fun onStop() {
        super.onStop()
        // TV-NP: video must not keep playing when the user leaves the app.
        state.player.pause()
    }

    override fun onDestroy() {
        super.onDestroy()
        mainHandler.removeCallbacksAndMessages(null)
        server.stopServer()
        state.player.release()
    }

    private companion object {
        const val TICK_INTERVAL_MS = 2_000L
        const val SEEK_STEP_SECONDS = 10L
        const val DIGIT_COMMIT_MS = 1_800L
    }
}
