package com.applemand.zmanim.core.zmanim

import com.kosherjava.zmanim.ComplexZmanimCalendar
import com.kosherjava.zmanim.util.GeoLocation
import kotlinx.datetime.LocalDate
import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
import java.util.TimeZone
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.time.Duration.Companion.seconds

/**
 * Location input for zmanim calculation.  elevationMeters is optional but affects sunrise/sunset
 * accuracy in hilly terrain; default 0 matches KosherJava's sea-level call chain.
 */
data class ZmanimLocation(
    val lat: Double,
    val lng: Double,
    val tzid: String,
    val elevationMeters: Double = 0.0,
    val name: String = "",
    /**
     * When true, sunrise/sunset/candle-lighting honor the location's elevation
     * (KosherJava `sunrise`/`sunset` methods). When false, the traditional
     * sea-level horizon (`seaLevelSunrise`/`seaLevelSunset`) is used.
     * Halachic opinion varies; see user_settings.proto for the default.
     */
    val useElevation: Boolean = false,
)

/**
 * Full zmanim snapshot.  Values may be null at polar latitudes on specific dates
 * where the sun does not cross relevant altitudes.
 */
data class ZmanimSnapshot(
    val date: LocalDate,
    val location: ZmanimLocation,
    val candleLightingMinutes: Int,
    val values: Map<ZmanKey, Long?>,    // epoch millis
) {
    operator fun get(key: ZmanKey): Long? = values[key]
}

@Singleton
class ZmanimCalculator @Inject constructor() {

    /**
     * Compute the 13 zmanim for the given date + location.  Zero network, zero I/O.
     * Mirrors webapp src/lib/hebrewDateUtils.js fetchZmanim.
     */
    fun compute(
        date: LocalDate,
        location: ZmanimLocation,
        candleLightingMinutes: Int,
    ): ZmanimSnapshot {
        val tz = runCatching { TimeZone.getTimeZone(location.tzid) }
            .getOrElse { TimeZone.getDefault() }
        val gc = GregorianCalendar(tz).apply {
            set(Calendar.YEAR, date.year)
            set(Calendar.MONTH, date.monthNumber - 1)
            set(Calendar.DAY_OF_MONTH, date.dayOfMonth)
            set(Calendar.HOUR_OF_DAY, 12)
            set(Calendar.MINUTE, 0); set(Calendar.SECOND, 0); set(Calendar.MILLISECOND, 0)
        }
        val geo = GeoLocation(
            location.name, location.lat, location.lng, location.elevationMeters, tz
        )
        val czc = ComplexZmanimCalendar(geo).also {
            it.calendar = gc
            it.candleLightingOffset = candleLightingMinutes.toDouble()
        }

        fun Date?.ms(): Long? = this?.time

        val sunrise = if (location.useElevation) czc.sunrise?.time else czc.seaLevelSunrise?.time
        val sunset = if (location.useElevation) czc.sunset?.time else czc.seaLevelSunset?.time
        val candleLighting = sunset?.minus(candleLightingMinutes.toLong() * 60_000L)

        val values = mapOf(
            ZmanKey.ALOT_HASHACHAR to (czc.alos72?.time ?: czc.alosHashachar?.time),
            ZmanKey.ZMAN_TZITZIT to czc.misheyakir10Point2Degrees?.time,
            ZmanKey.SUNRISE to sunrise,
            ZmanKey.SOF_SHMA_GRA to czc.sofZmanShmaGRA.ms(),
            ZmanKey.SOF_SHMA_MA to czc.sofZmanShmaMGA.ms(),
            ZmanKey.SOF_TFILA to czc.sofZmanTfilaGRA.ms(),
            ZmanKey.MIDDAY to czc.chatzos.ms(),
            ZmanKey.MINCHA_GEDOLAH to czc.minchaGedola.ms(),
            ZmanKey.PLAG_HAMINCHA to czc.plagHamincha.ms(),
            ZmanKey.SUNSET to sunset,
            ZmanKey.CANDLE_LIGHTING to candleLighting,
            ZmanKey.TZEIT_KOCHAVIM to czc.tzaisGeonim8Point5Degrees?.time,
            ZmanKey.RABBEINU_TAM to czc.tzais72?.time,
        )

        return ZmanimSnapshot(
            date = date,
            location = location,
            candleLightingMinutes = candleLightingMinutes,
            values = values,
        )
    }

    /** Batch helper — one pass, no redundant calendar creation. */
    fun computeRange(
        dates: List<LocalDate>,
        location: ZmanimLocation,
        candleLightingMinutes: Int,
    ): List<ZmanimSnapshot> = dates.map { compute(it, location, candleLightingMinutes) }
}

/** Minutes between now and a zman; negative = already passed. */
fun ZmanimSnapshot.minutesUntil(key: ZmanKey, nowMs: Long): Long? =
    get(key)?.let { ((it - nowMs) / 60_000).seconds.inWholeMinutes }
