package eu.mhsl.marianum.mobile.client.widgets

import org.json.JSONException
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone

// Mirror of lib/widget_data/widget_data.dart — JSON keys + enum names
// must stay in sync.
enum class WidgetLessonStatus {
    REGULAR, ONGOING, PAST, CANCELLED, IRREGULAR, TEACHER_CHANGED, EVENT;

    companion object {
        fun fromWire(raw: String?): WidgetLessonStatus = when (raw) {
            "regular" -> REGULAR
            "ongoing" -> ONGOING
            "past" -> PAST
            "cancelled" -> CANCELLED
            "irregular" -> IRREGULAR
            "teacherChanged" -> TEACHER_CHANGED
            "event" -> EVENT
            else -> REGULAR
        }
    }
}

data class WidgetLesson(
    val start: Date,
    val end: Date,
    val subjectShort: String,
    val subjectLong: String?,
    val room: String?,
    // On teacher accounts this carries the class label ("7a") instead of the
    // teacher short name (originalTeacher is null then) — mapped in Dart.
    val teacher: String?,
    val originalTeacher: String?,
    val status: WidgetLessonStatus,
    val customColor: String?,
    val siblingCount: Int,
)

data class WidgetPeriod(
    val name: String,
    val startMinutes: Int,
    val endMinutes: Int,
    val virtualStartMinutes: Int,
    val virtualEndMinutes: Int,
)

data class WidgetTimetableData(
    val fetchedAt: Date,
    val anchorDate: Date,
    val lessons: List<WidgetLesson>,
    val periods: List<WidgetPeriod>,
    val isHoliday: Boolean,
    val holidayName: String?,
)

object WidgetDataParser {
    private val isoFormat: SimpleDateFormat
        get() = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ROOT).apply {
            timeZone = TimeZone.getDefault()
        }

    /// Dart's toIso8601String() ships microseconds (6 digits) when non-zero;
    /// SimpleDateFormat only parses 3 → strip the extra digits. Local time
    /// without Z is the default, so removeSuffix("Z") makes the parser
    /// tolerate both shapes.
    private fun parseDate(raw: String?): Date? {
        if (raw.isNullOrEmpty()) return null
        val cleaned = raw
            .replace(Regex("([.,]\\d{3})\\d+"), "$1")
            .removeSuffix("Z")
        return try {
            isoFormat.parse(cleaned)
        } catch (_: Exception) {
            null
        }
    }

    fun parse(json: String?): WidgetTimetableData? {
        if (json.isNullOrEmpty()) return null
        return try {
            val root = JSONObject(json)
            val lessonsArray = root.optJSONArray("lessons")
            val lessons = mutableListOf<WidgetLesson>()
            if (lessonsArray != null) {
                for (i in 0 until lessonsArray.length()) {
                    val obj = lessonsArray.optJSONObject(i) ?: continue
                    val start = parseDate(obj.stringOrNull("start")) ?: continue
                    val end = parseDate(obj.stringOrNull("end")) ?: continue
                    lessons += WidgetLesson(
                        start = start,
                        end = end,
                        subjectShort = obj.stringOrNull("subjectShort") ?: "",
                        subjectLong = obj.stringOrNull("subjectLong"),
                        room = obj.stringOrNull("room"),
                        teacher = obj.stringOrNull("teacher"),
                        originalTeacher = obj.stringOrNull("originalTeacher"),
                        status = WidgetLessonStatus.fromWire(obj.stringOrNull("status")),
                        customColor = obj.stringOrNull("customColor"),
                        siblingCount = obj.optInt("siblingCount", 0),
                    )
                }
            }
            val periodsArray = root.optJSONArray("periods")
            val periods = mutableListOf<WidgetPeriod>()
            if (periodsArray != null) {
                for (i in 0 until periodsArray.length()) {
                    val obj = periodsArray.optJSONObject(i) ?: continue
                    periods += WidgetPeriod(
                        name = obj.stringOrNull("name") ?: "",
                        startMinutes = obj.optInt("startMinutes", 0),
                        endMinutes = obj.optInt("endMinutes", 0),
                        virtualStartMinutes = obj.optInt("virtualStartMinutes", 0),
                        virtualEndMinutes = obj.optInt("virtualEndMinutes", 0),
                    )
                }
            }
            WidgetTimetableData(
                fetchedAt = parseDate(root.stringOrNull("fetchedAt")) ?: Date(),
                anchorDate = parseDate(root.stringOrNull("anchorDate")) ?: Date(),
                lessons = lessons,
                periods = periods,
                isHoliday = root.optBoolean("isHoliday", false),
                holidayName = root.stringOrNull("holidayName"),
            )
        } catch (_: JSONException) {
            null
        }
    }

    private fun JSONObject.stringOrNull(key: String): String? {
        if (!has(key) || isNull(key)) return null
        val raw = optString(key, "")
        return if (raw.isEmpty()) null else raw
    }
}

object WidgetDateUtils {
    fun startOfDay(date: Date): Date {
        val cal = Calendar.getInstance().apply { time = date }
        cal.set(Calendar.HOUR_OF_DAY, 0)
        cal.set(Calendar.MINUTE, 0)
        cal.set(Calendar.SECOND, 0)
        cal.set(Calendar.MILLISECOND, 0)
        return cal.time
    }

    fun isSameDay(a: Date, b: Date): Boolean {
        val ca = Calendar.getInstance().apply { time = a }
        val cb = Calendar.getInstance().apply { time = b }
        return ca.get(Calendar.YEAR) == cb.get(Calendar.YEAR) &&
            ca.get(Calendar.DAY_OF_YEAR) == cb.get(Calendar.DAY_OF_YEAR)
    }
}
