package dev.castarr.tv.data

import android.util.Log
import android.util.Xml
import org.xmlpull.v1.XmlPullParser
import java.io.InputStream
import java.text.SimpleDateFormat
import java.util.Locale

data class Programme(val start: Long, val stop: Long, val title: String)

/** Provider EPGs pad gaps with placeholder programmes — treat as no data. */
fun isEpgPlaceholder(title: String): Boolean {
    val t = title.lowercase()
    return "kein programm" in t || "keine information" in t || "no information" in t
}

data class NowNext(val now: Programme?, val next: Programme?)

/**
 * Minimal XMLTV parser: programme start/stop/title per channel id. Display
 * names are also collected so channels can be matched by name when the M3U
 * carries no tvg-id.
 */
object XmltvParser {

    private const val TAG = "XmltvParser"
    private const val MAX_PROGRAMMES = 200_000

    data class Result(
        val programmes: Map<String, List<Programme>>,
        val displayNameToId: Map<String, String>,
    )

    fun parse(input: InputStream): Result {
        val programmes = HashMap<String, MutableList<Programme>>()
        val displayNames = HashMap<String, String>()
        var count = 0

        val parser = Xml.newPullParser()
        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
        parser.setInput(input, null)

        var currentChannelId: String? = null
        var programmeChannel: String? = null
        var programmeStart = 0L
        var programmeStop = 0L
        var programmeTitle = ""
        var inTitle = false
        var inDisplayName = false

        var event = parser.eventType
        while (event != XmlPullParser.END_DOCUMENT && count < MAX_PROGRAMMES) {
            when (event) {
                XmlPullParser.START_TAG -> when (parser.name) {
                    "channel" -> currentChannelId = parser.getAttributeValue(null, "id")
                    "display-name" -> inDisplayName = true
                    "programme" -> {
                        programmeChannel = parser.getAttributeValue(null, "channel")
                        programmeStart = parseTime(parser.getAttributeValue(null, "start"))
                        programmeStop = parseTime(parser.getAttributeValue(null, "stop"))
                        programmeTitle = ""
                    }
                    "title" -> inTitle = programmeChannel != null
                }
                XmlPullParser.TEXT -> {
                    if (inTitle && programmeTitle.isEmpty()) programmeTitle = parser.text.trim()
                    if (inDisplayName) {
                        val id = currentChannelId
                        val name = parser.text.trim()
                        if (id != null && name.isNotEmpty()) displayNames.putIfAbsent(name.lowercase(), id)
                    }
                }
                XmlPullParser.END_TAG -> when (parser.name) {
                    "display-name" -> inDisplayName = false
                    "title" -> inTitle = false
                    "channel" -> currentChannelId = null
                    "programme" -> {
                        val channel = programmeChannel
                        if (channel != null && programmeStart > 0 && programmeStop > programmeStart) {
                            programmes.getOrPut(channel) { mutableListOf() }
                                .add(Programme(programmeStart, programmeStop, programmeTitle))
                            count++
                        }
                        programmeChannel = null
                    }
                }
            }
            event = parser.next()
        }
        programmes.values.forEach { it.sortBy(Programme::start) }
        Log.i(TAG, "parsed $count programmes for ${programmes.size} channels")
        return Result(programmes, displayNames)
    }

    /** XMLTV time: `yyyyMMddHHmmss Z` (zone part optional). */
    private fun parseTime(raw: String?): Long {
        if (raw.isNullOrBlank()) return 0
        val normalized = raw.trim()
        val format = if (normalized.contains(' ')) "yyyyMMddHHmmss Z" else "yyyyMMddHHmmss"
        return try {
            SimpleDateFormat(format, Locale.US).parse(normalized)?.time ?: 0
        } catch (e: java.text.ParseException) {
            0
        }
    }

    /** Programmes overlapping [from]..[to], in broadcast order. */
    fun programmesIn(
        programmes: List<Programme>?,
        from: Long,
        to: Long,
    ): List<Programme> =
        programmes.orEmpty().filter { it.stop > from && it.start < to }

    fun nowNext(programmes: List<Programme>?, at: Long = System.currentTimeMillis()): NowNext {
        if (programmes.isNullOrEmpty()) return NowNext(null, null)
        val index = programmes.indexOfFirst { at < it.stop }
        if (index < 0) return NowNext(null, null)
        val candidate = programmes[index]
        return if (at >= candidate.start) {
            NowNext(candidate, programmes.getOrNull(index + 1))
        } else {
            NowNext(null, candidate)
        }
    }
}
