package xyz.cottongin.radio247.ui.screens.stationlist

import android.app.Application
import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import xyz.cottongin.radio247.RadioApplication
import xyz.cottongin.radio247.data.api.SomaFmApi
import xyz.cottongin.radio247.data.importing.M3uParser
import xyz.cottongin.radio247.data.importing.ParsedStation
import xyz.cottongin.radio247.data.importing.PlsParser
import xyz.cottongin.radio247.data.model.Playlist
import xyz.cottongin.radio247.data.model.Station
import xyz.cottongin.radio247.data.model.StationPreference
import xyz.cottongin.radio247.data.model.StationStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

data class TabInfo(
    val playlist: Playlist?,
    val label: String,
    val isBuiltIn: Boolean
)

data class PendingImport(
    val suggestedName: String,
    val stations: List<ParsedStation>
)

enum class SortMode { DEFAULT, NAME_ASC, NAME_DESC }

data class StationListViewState(
    val tabs: List<TabInfo> = emptyList(),
    val selectedTabIndex: Int = 0,
    val sortMode: SortMode = SortMode.DEFAULT,
    val stations: List<Station> = emptyList(),
    val isPollingListeners: Boolean = false,
    val hiddenCount: Int = 0,
    val showHidden: Boolean = false,
    val stationIdsWithStreams: Set<Long> = emptySet()
)

@OptIn(ExperimentalCoroutinesApi::class)
class StationListViewModel(application: Application) : AndroidViewModel(application) {
    private val app = application as RadioApplication
    private val stationDao = app.database.stationDao()
    private val playlistDao = app.database.playlistDao()
    val controller = app.controller

    val playbackState = controller.state

    private val _selectedTabIndex = MutableStateFlow(0)
    private val _sortMode = MutableStateFlow(SortMode.DEFAULT)
    val sortMode: StateFlow<SortMode> = _sortMode.asStateFlow()

    private val _pendingImport = MutableStateFlow<PendingImport?>(null)
    val pendingImport: StateFlow<PendingImport?> = _pendingImport.asStateFlow()

    private val _isPollingListeners = MutableStateFlow(false)
    private val _showHidden = MutableStateFlow(false)
    private var pollingJob: Job? = null

    private val playlistsFlow = playlistDao.getAllPlaylists()
        .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())

    private val currentStationsFlow = combine(
        _selectedTabIndex, playlistsFlow, _showHidden
    ) { tabIndex, playlists, showHidden ->
        Triple(tabIndex, playlists, showHidden)
    }.flatMapLatest { (tabIndex, playlists, showHidden) ->
        val tabs = buildTabs(playlists)
        val playlist = if (tabIndex < tabs.size) tabs[tabIndex].playlist else null
        if (showHidden) {
            if (playlist == null) stationDao.getHiddenUnsortedStations()
            else stationDao.getHiddenStationsByPlaylist(playlist.id)
        } else {
            if (playlist == null) stationDao.getUnsortedStations()
            else stationDao.getStationsByPlaylist(playlist.id)
        }
    }

    private val hiddenCountFlow = combine(
        _selectedTabIndex, playlistsFlow
    ) { tabIndex, playlists ->
        val tabs = buildTabs(playlists)
        if (tabIndex < tabs.size) tabs[tabIndex].playlist else null
    }.flatMapLatest { playlist ->
        if (playlist == null) stationDao.getHiddenUnsortedCount()
        else stationDao.getHiddenCountByPlaylist(playlist.id)
    }

    private val stationIdsWithStreamsFlow = app.database.stationStreamDao().getStationIdsWithStreams()
        .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())

    val viewState: StateFlow<StationListViewState> = combine(
        playlistsFlow, _selectedTabIndex, _sortMode, currentStationsFlow,
        _isPollingListeners, hiddenCountFlow, _showHidden, stationIdsWithStreamsFlow
    ) { values ->
        @Suppress("UNCHECKED_CAST")
        val playlists = values[0] as List<Playlist>
        val tabIndex = values[1] as Int
        val sortMode = values[2] as SortMode
        @Suppress("UNCHECKED_CAST")
        val stations = values[3] as List<Station>
        val isPolling = values[4] as Boolean
        val hiddenCount = values[5] as Int
        val showHidden = values[6] as Boolean
        @Suppress("UNCHECKED_CAST")
        val stationIdsWithStreams = (values[7] as List<Long>).toSet()

        val tabs = buildTabs(playlists)
        val safeIndex = tabIndex.coerceIn(0, (tabs.size - 1).coerceAtLeast(0))
        val currentTabIsBuiltIn = tabs.getOrNull(safeIndex)?.isBuiltIn == true
        val sorted = applySortMode(stations, sortMode, currentTabIsBuiltIn)
        StationListViewState(
            tabs = tabs,
            selectedTabIndex = safeIndex,
            sortMode = sortMode,
            stations = sorted,
            isPollingListeners = isPolling,
            hiddenCount = hiddenCount,
            showHidden = showHidden,
            stationIdsWithStreams = stationIdsWithStreams
        )
    }.stateIn(viewModelScope, SharingStarted.Lazily, StationListViewState())

    private fun buildTabs(playlists: List<Playlist>): List<TabInfo> {
        val myStations = TabInfo(playlist = null, label = "My Stations", isBuiltIn = false)
        val pinned = playlists
            .filter { it.pinned }
            .sortedBy { it.sortOrder }
            .map { TabInfo(playlist = it, label = it.name, isBuiltIn = it.isBuiltIn) }
        val unpinned = playlists
            .filter { !it.pinned }
            .sortedBy { it.sortOrder }
            .map { TabInfo(playlist = it, label = it.name, isBuiltIn = it.isBuiltIn) }
        return pinned + listOf(myStations) + unpinned
    }

    fun renamePlaylist(playlist: Playlist, newName: String) {
        viewModelScope.launch {
            playlistDao.rename(playlist.id, newName)
        }
    }

    fun togglePinned(playlist: Playlist) {
        viewModelScope.launch {
            playlistDao.updatePinned(playlist.id, !playlist.pinned)
        }
    }

    private fun applySortMode(
        stations: List<Station>,
        sortMode: SortMode,
        isBuiltInTab: Boolean
    ): List<Station> {
        val starred = stations.filter { it.starred }
        val unstarred = stations.filter { !it.starred }

        fun List<Station>.sorted(): List<Station> = when (sortMode) {
            SortMode.DEFAULT -> if (isBuiltInTab) sortedByDescending { it.listenerCount } else this
            SortMode.NAME_ASC -> sortedBy { it.name.lowercase() }
            SortMode.NAME_DESC -> sortedByDescending { it.name.lowercase() }
        }

        return starred.sorted() + unstarred.sorted()
    }

    fun selectTab(index: Int) {
        _selectedTabIndex.value = index
        _sortMode.value = SortMode.DEFAULT
        _showHidden.value = false
        pollingJob?.cancel()
        _isPollingListeners.value = false
    }

    fun setSortMode(mode: SortMode) {
        _sortMode.value = mode
    }

    fun refreshListenerCounts() {
        pollListenerCounts()
    }

    private fun pollListenerCounts() {
        pollingJob?.cancel()
        pollingJob = viewModelScope.launch {
            _isPollingListeners.value = true
            try {
                val counts = SomaFmApi.fetchListenerCounts(app.okHttpClient)
                if (counts.isEmpty()) return@launch

                val stations = currentStationsFlow
                    .stateIn(viewModelScope)
                    .value

                val starred = stations.filter { it.starred }
                val unstarred = stations.filter { !it.starred }

                for (station in starred) {
                    val streamId = SomaFmApi.extractStreamId(station.url) ?: continue
                    val count = counts[streamId] ?: continue
                    stationDao.updateListenerCount(station.id, count)
                }

                for (station in unstarred) {
                    val streamId = SomaFmApi.extractStreamId(station.url) ?: continue
                    val count = counts[streamId] ?: continue
                    stationDao.updateListenerCount(station.id, count)
                }
            } finally {
                _isPollingListeners.value = false
            }
        }
    }

    fun toggleHidden(station: Station) {
        viewModelScope.launch {
            stationDao.toggleHidden(station.id, !station.isHidden)
        }
    }

    fun toggleShowHidden() {
        _showHidden.value = !_showHidden.value
    }

    fun playStation(station: Station) {
        viewModelScope.launch {
            app.preferences.setLastStationId(station.id)
            controller.play(station)
        }
    }

    fun toggleStar(station: Station) {
        viewModelScope.launch { stationDao.toggleStarred(station.id, !station.starred) }
    }

    fun deleteStation(station: Station) {
        viewModelScope.launch { stationDao.delete(station) }
    }

    fun addStation(name: String, url: String) {
        val currentTab = viewState.value.tabs.getOrNull(viewState.value.selectedTabIndex)
        val playlistId = currentTab?.playlist?.id
        viewModelScope.launch {
            stationDao.insert(Station(name = name, url = url, playlistId = playlistId))
        }
    }

    fun updateStation(station: Station, name: String, url: String) {
        viewModelScope.launch {
            stationDao.update(station.copy(name = name, url = url))
        }
    }

    fun addPlaylist(name: String) {
        viewModelScope.launch {
            playlistDao.insert(Playlist(name = name))
        }
    }

    fun deletePlaylist(playlist: Playlist) {
        if (playlist.isBuiltIn) return
        viewModelScope.launch {
            playlistDao.delete(playlist)
            if (_selectedTabIndex.value > 0) {
                _selectedTabIndex.value = 0
            }
        }
    }

    fun setQualityOverride(stationId: Long, qualityJson: String?) {
        viewModelScope.launch {
            val prefDao = app.database.stationPreferenceDao()
            if (qualityJson == null) {
                prefDao.deleteByStationId(stationId)
            } else {
                prefDao.upsert(StationPreference(stationId = stationId, qualityOverride = qualityJson))
            }
        }
    }

    fun getStreamsForStation(stationId: Long, callback: (List<StationStream>) -> Unit) {
        viewModelScope.launch {
            val streams = app.database.stationStreamDao().getStreamsForStation(stationId)
            callback(streams)
        }
    }

    fun getQualityOverrideForStation(stationId: Long, callback: (String?) -> Unit) {
        viewModelScope.launch {
            val pref = app.database.stationPreferenceDao().getByStationId(stationId)
            callback(pref?.qualityOverride)
        }
    }

    fun importFile(uri: Uri) {
        viewModelScope.launch(Dispatchers.IO) {
            val content = app.contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
                ?: return@launch
            val fileName = uri.lastPathSegment
                ?.substringAfterLast('/')
                ?.substringBeforeLast('.')
                ?: "Imported"
            val isM3u = content.trimStart().startsWith("#EXTM3U") ||
                uri.toString().endsWith(".m3u", ignoreCase = true)
            val parsed = if (isM3u) M3uParser.parse(content) else PlsParser.parse(content)
            if (parsed.isEmpty()) return@launch

            withContext(Dispatchers.Main) {
                _pendingImport.value = PendingImport(fileName, parsed)
            }
        }
    }

    fun confirmImport(name: String) {
        val pending = _pendingImport.value ?: return
        _pendingImport.value = null
        viewModelScope.launch(Dispatchers.IO) {
            val playlistId = playlistDao.insert(Playlist(name = name))
            for ((index, station) in pending.stations.withIndex()) {
                stationDao.insert(
                    Station(
                        name = station.name,
                        url = station.url,
                        playlistId = playlistId,
                        sortOrder = index,
                        defaultArtworkUrl = station.artworkUrl
                    )
                )
            }
            withContext(Dispatchers.Main) {
                val playlists = playlistsFlow.value
                val tabs = buildTabs(playlists)
                val newTabIndex = tabs.indexOfFirst { it.playlist?.id == playlistId }
                if (newTabIndex >= 0) {
                    _selectedTabIndex.value = newTabIndex
                }
            }
        }
    }

    fun cancelImport() {
        _pendingImport.value = null
    }

    fun reorderTabs(reorderedPlaylists: List<Playlist>) {
        val currentTab = viewState.value.tabs.getOrNull(viewState.value.selectedTabIndex)
        val selectedPlaylistId = currentTab?.playlist?.id
        viewModelScope.launch {
            for ((index, playlist) in reorderedPlaylists.withIndex()) {
                playlistDao.updateSortOrder(playlist.id, index)
            }
            if (selectedPlaylistId != null) {
                val playlists = playlistDao.getAllPlaylists().first()
                val tabs = buildTabs(playlists)
                val newIndex = tabs.indexOfFirst { it.playlist?.id == selectedPlaylistId }
                if (newIndex >= 0) _selectedTabIndex.value = newIndex
            }
        }
    }
}
