package com.applemand.zmanim.core.calsync

import com.applemand.zmanim.core.jewcal.Holiday
import com.applemand.zmanim.core.zmanim.ZmanKey
import com.applemand.zmanim.core.zmanim.ZmanimSnapshot
import java.io.Writer
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlinx.datetime.LocalDate

/**
 * Writes RFC 5545 iCalendar objects.
 * Only the subset needed for zmanim + holiday export is implemented.
 */
class IcsWriter(private val out: Writer) {

    private val utcStamp: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'")
        .withZone(ZoneId.of("UTC"))
    private val zonedStamp: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss")
    private val dateStamp: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")

    fun writeCalendar(
        productId: String = "-//Luach//Zmanim//EN",
        tzid: String,
        events: List<IcsEvent>,
    ) {
        out.line("BEGIN:VCALENDAR")
        out.line("VERSION:2.0")
        out.line("PRODID:$productId")
        out.line("CALSCALE:GREGORIAN")
        out.line("METHOD:PUBLISH")
        out.line("X-WR-CALNAME:Luach Zmanim")
        out.line("X-WR-TIMEZONE:$tzid")
        events.forEach { writeEvent(it, tzid) }
        out.line("END:VCALENDAR")
        out.flush()
    }

    private fun writeEvent(e: IcsEvent, tzid: String) {
        out.line("BEGIN:VEVENT")
        out.line("UID:${e.uid}")
        out.line("DTSTAMP:${utcStamp.format(Instant.now())}")
        if (e.allDay) {
            val date = e.start.atZone(ZoneId.of(tzid)).toLocalDate()
            out.line("DTSTART;VALUE=DATE:${dateStamp.format(date)}")
            out.line("DTEND;VALUE=DATE:${dateStamp.format(date.plusDays(1))}")
        } else {
            out.line("DTSTART;TZID=$tzid:${zonedStamp.format(e.start.atZone(ZoneId.of(tzid)))}")
            val end = e.end ?: e.start.plusSeconds(60)
            out.line("DTEND;TZID=$tzid:${zonedStamp.format(end.atZone(ZoneId.of(tzid)))}")
        }
        out.line("SUMMARY:${escape(e.summary)}")
        if (!e.description.isNullOrEmpty()) out.line("DESCRIPTION:${escape(e.description)}")
        if (!e.category.isNullOrEmpty()) out.line("CATEGORIES:${e.category}")
        out.line("END:VEVENT")
    }

    private fun Writer.line(s: String) { write(s); write("\r\n") }
    private fun escape(s: String) = s
        .replace("\\", "\\\\")
        .replace(",", "\\,")
        .replace(";", "\\;")
        .replace("\n", "\\n")
}

data class IcsEvent(
    val uid: String,
    val summary: String,
    val start: Instant,
    val end: Instant? = null,
    val allDay: Boolean = false,
    val description: String? = null,
    val category: String? = null,
)

/**
 * Convenience builder: maps a range of zmanim snapshots + holidays to a VEVENT list.
 */
class ZmanimIcsBuilder {
    fun build(
        snapshots: List<ZmanimSnapshot>,
        holidays: Map<LocalDate, List<Holiday>>,
        visibleZmanim: Set<ZmanKey>,
    ): List<IcsEvent> = buildList {
        snapshots.forEach { snap ->
            val d = snap.date
            visibleZmanim.forEach { zk ->
                val ms = snap[zk] ?: return@forEach
                add(
                    IcsEvent(
                        uid = "luach-${d.toEpochDays()}-${zk.keyId}@local",
                        summary = zk.nameEn,
                        start = Instant.ofEpochMilli(ms),
                        category = "ZMANIM",
                    )
                )
            }
            holidays[d].orEmpty().forEach { h ->
                add(
                    IcsEvent(
                        uid = "luach-${d.toEpochDays()}-holiday-${h.key.name}@local",
                        summary = h.nameEn,
                        start = d.atStartOfDayUtc(),
                        allDay = true,
                        category = "HOLIDAY",
                    )
                )
            }
        }
    }

    private fun LocalDate.atStartOfDayUtc(): Instant =
        java.time.LocalDate.of(year, monthNumber, dayOfMonth).atStartOfDay(ZoneId.of("UTC")).toInstant()
}
