package com.radiola.data.repository

import com.radiola.data.local.LocalStationDataSource
import com.radiola.data.local.AppDatabase
import com.radiola.data.local.entity.StationEntity
import com.radiola.data.local.entity.TagEntity
import com.radiola.data.remote.RecordApi
import com.radiola.data.remote.RadiolaApi
import com.radiola.data.remote.ApiMapper.toDomain
import com.radiola.domain.model.Station
import com.radiola.domain.model.StreamQuality
import com.radiola.domain.repository.StationRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class StationRepositoryImpl @Inject constructor(
    private val api: RecordApi,
    private val radiolaApi: RadiolaApi,
    private val db: AppDatabase,
    private val localDataSource: LocalStationDataSource
) : StationRepository {

    private val _tags = MutableStateFlow<List<String>>(emptyList())

    // Теги-чипы, которые не показываем (сезонные/мусорные). «Новый год» —
    // межсезонный тег Record (Christmas Chill и т.п. остаются в разделе Radio Record).
    private val hiddenTags = setOf("Новый год")

    override fun getStations(): Flow<List<Station>> {
        return db.stationDao().getAll().map { entities ->
            entities.map { it.toDomain() }
        }
    }

    override suspend fun refreshStations(): Result<Unit> = withContext(Dispatchers.IO) {
        // Тяжёлый парс stations.json (~700) + сетевые вызовы + запись в Room —
        // на IO, а не на главном потоке (был риск jank/ANR при холодном старте).
        try {
            // 1. Load local stations from assets
            val localStations = localDataSource.loadStations()
            val localGroups = localDataSource.loadGroups()

            // 2. Try to enrich with Record API data (covers, streams, tags)
            val apiResponse = try { api.getStations() } catch (e: Exception) { null }
            val apiStations = apiResponse?.result?.stations ?: emptyList()
            val apiTags = apiResponse?.result?.tags?.map { it.name } ?: emptyList()

            // 3. Merge: local stations enriched with API data.
            // Локальные id (1,2,3…) не совпадают с id Record-каталога, и в ассетах
            // нет prefix — поэтому сопоставляем сначала по id, затем по названию
            // (стабильный общий ключ), иначе обложки/потоки не подтягиваются.
            // Индексы строим один раз (было O(n²): .find по ~700 на каждую из ~700).
            // asReversed+associateBy сохраняет ПЕРВЫЙ по имени (как делал .find).
            val apiById = apiStations.associateBy { it.id }
            val apiByName = apiStations.asReversed()
                .associateBy { it.name.trim().lowercase() }
            val merged = localStations.map { local ->
                // Обложки/потоки из Record API — только для станций сети Radio Record.
                // Иначе чужим сетям (DFM, HitFM и т.д.) цеплялись бы обложки Record.
                val apiStation = if (local.source == "record") {
                    apiById[local.id] ?: apiByName[local.name.trim().lowercase()]
                } else null
                if (apiStation != null) {
                    val domain = apiStation.toDomain()
                    local.copy(
                        coverUrl = domain.coverUrl,
                        streamUrl = domain.streamUrl,
                        genre = local.genre, // keep group name for filtering
                        tags = (local.tags + domain.tags).distinct(), // merge tags
                        prefix = domain.prefix,
                        source = "record"
                    )
                } else {
                    local
                }
            }

            // 4. Save to DB. Сохраняем текущие отметки «избранное», иначе REPLACE
            // в insertAll затрёт их при каждом пересоздании каталога (на старте).
            val favoriteIds = db.stationDao().getFavoriteIdsOnce().toSet()
            val entities = merged.mapIndexed { index, station ->
                StationEntity(
                    id = station.id,
                    name = station.name,
                    prefix = station.prefix,
                    streamUrl = station.streamUrl,
                    coverUrl = station.coverUrl,
                    genre = station.genre,
                    tags = station.tags.joinToString(","),
                    sortOrder = index,
                    source = station.source,
                    isFavorite = station.id in favoriteIds,
                    qualities = encodeQualities(station.qualities)
                )
            }
            db.stationDao().insertAll(entities)

            // 4b. Скрываем станции, которые бэкенд пометил оффлайн (мёртвые потоки).
            // Если бэкенд недоступен — оставляем как есть (фолбэк на статичный enabled).
            try {
                val offlineIds = radiolaApi.getOfflineStationIds()
                if (offlineIds.isNotEmpty()) {
                    db.stationDao().deleteByIds(offlineIds)
                }
            } catch (e: Exception) {
                android.util.Log.w("StationRepo", "Не удалось получить offline-id: ${e.message}")
            }

            // 5. Update tags: group names + API tags
            val groupNames = localGroups.map { it.name }.filter { it.isNotBlank() }
            val allTags = (groupNames + apiTags).distinct()
                .filterNot { it in hiddenTags }
                .sorted()
            db.tagDao().clearAll()
            db.tagDao().insertAll(allTags.map { TagEntity(it) })
            _tags.value = allTags

            Result.success(Unit)
        } catch (e: Exception) {
            android.util.Log.e("StationRepo", "refreshStations() failed", e)
            Result.failure(e)
        }
    }

    override fun getTags(): Flow<List<String>> {
        return _tags.asStateFlow()
    }

    override fun getStationById(id: Int): Flow<Station?> {
        return db.stationDao().getById(id).map { it?.toDomain() }
    }

    private fun StationEntity.toDomain(): Station = Station(
        id = id,
        name = name,
        prefix = prefix,
        streamUrl = streamUrl,
        coverUrl = coverUrl,
        genre = genre,
        tags = tags.split(",").filter { it.isNotBlank() },
        sortOrder = sortOrder,
        source = source,
        qualities = decodeQualities(qualities)
    )

    // Качества кодируем строкой "bitrate\ttype\turl" по строкам (URL может содержать ; и |,
    // но не \t/\n — поэтому такие разделители безопасны).
    private fun encodeQualities(list: List<StreamQuality>): String =
        list.joinToString("\n") { "${it.bitrate}\t${it.type}\t${it.url}" }

    private fun decodeQualities(raw: String): List<StreamQuality> {
        if (raw.isBlank()) return emptyList()
        return raw.split("\n").mapNotNull { line ->
            val parts = line.split("\t")
            if (parts.size != 3) return@mapNotNull null
            val br = parts[0].toIntOrNull() ?: return@mapNotNull null
            StreamQuality(br, parts[2], parts[1])
        }
    }
}
