package com.applemand.zmanim.core.calsync

import java.io.BufferedReader
import java.io.Reader
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter

/**
 * Minimal RFC 5545 reader.  Handles line unfolding, basic property/value split,
 * DTSTART/DTEND with optional TZID and VALUE=DATE forms, and VEVENT boundaries.
 * Only what we need to round-trip our own exports + typical third-party ICS files.
 */
class IcsParser {

    fun parse(reader: Reader): List<IcsEvent> {
        val lines = unfold(BufferedReader(reader).readLines())
        val events = mutableListOf<IcsEvent>()
        var inEvent = false
        var b = Builder()
        for (line in lines) {
            when {
                line == "BEGIN:VEVENT" -> { inEvent = true; b = Builder() }
                line == "END:VEVENT" -> {
                    if (inEvent) b.finish()?.let(events::add)
                    inEvent = false
                }
                inEvent -> b.consume(line)
            }
        }
        return events
    }

    private fun unfold(raw: List<String>): List<String> {
        val out = mutableListOf<String>()
        for (l in raw) {
            if (l.isEmpty()) continue
            if ((l.startsWith(" ") || l.startsWith("\t")) && out.isNotEmpty()) {
                out[out.size - 1] = out.last() + l.substring(1)
            } else {
                out += l
            }
        }
        return out
    }

    private class Builder {
        var uid: String? = null
        var summary: String = ""
        var description: String? = null
        var category: String? = null
        var start: Instant? = null
        var end: Instant? = null
        var allDay = false

        fun consume(line: String) {
            val (k, params, v) = splitLine(line) ?: return
            val key = k.uppercase()
            when (key) {
                "UID" -> uid = v
                "SUMMARY" -> summary = unescape(v)
                "DESCRIPTION" -> description = unescape(v)
                "CATEGORIES" -> category = v
                "DTSTART" -> {
                    val (ts, isDate) = parseTime(params, v); start = ts; if (isDate) allDay = true
                }
                "DTEND" -> {
                    val (ts, _) = parseTime(params, v); end = ts
                }
            }
        }

        fun finish(): IcsEvent? {
            val s = start ?: return null
            val id = uid ?: "imported-${s.toEpochMilli()}"
            return IcsEvent(
                uid = id,
                summary = summary.ifEmpty { "Untitled" },
                start = s,
                end = end,
                allDay = allDay,
                description = description,
                category = category,
            )
        }
    }
}

private fun splitLine(line: String): Triple<String, Map<String, String>, String>? {
    val colon = line.indexOf(':').takeIf { it > 0 } ?: return null
    val head = line.substring(0, colon)
    val value = line.substring(colon + 1)
    val parts = head.split(";")
    val key = parts[0]
    val params = parts.drop(1).mapNotNull {
        val eq = it.indexOf('='); if (eq < 0) null else it.substring(0, eq).uppercase() to it.substring(eq + 1)
    }.toMap()
    return Triple(key, params, value)
}

private val dtUtc = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").withZone(ZoneId.of("UTC"))
private val dtLocal = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss")
private val dtDate = DateTimeFormatter.ofPattern("yyyyMMdd")

private fun parseTime(params: Map<String, String>, v: String): Pair<Instant?, Boolean> {
    if (params["VALUE"]?.equals("DATE", ignoreCase = true) == true || v.length == 8) {
        val d = runCatching { LocalDate.parse(v, dtDate) }.getOrNull() ?: return null to false
        return d.atStartOfDay(ZoneId.of("UTC")).toInstant() to true
    }
    if (v.endsWith("Z")) {
        return runCatching { Instant.from(dtUtc.parse(v)) }.getOrNull() to false
    }
    val tz = params["TZID"]?.let { runCatching { ZoneId.of(it) }.getOrNull() } ?: ZoneId.systemDefault()
    val local = runCatching { LocalDateTime.parse(v, dtLocal) }.getOrNull() ?: return null to false
    return local.atZone(tz).toInstant() to false
}

private fun unescape(s: String): String = s
    .replace("\\n", "\n")
    .replace("\\,", ",")
    .replace("\\;", ";")
    .replace("\\\\", "\\")
