package ru.forbion.f7cloud.feature.calendar

import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.provider.CalendarContract
import ru.forbion.f7cloud.core.network.CalDavClient
import java.time.Instant
import java.time.ZoneId

data class DeviceCalendarInfo(
    val id: Long,
    val displayName: String,
    val accountName: String,
)

data class DeviceCalendarEvent(
    val eventId: Long,
    val calendarId: Long,
    val title: String,
    val description: String,
    val startEpochMilli: Long,
    val endEpochMilli: Long,
    val allDay: Boolean,
    val lastModifiedEpochMilli: Long,
    val ncUid: String?,
)

class DeviceCalendarClient(private val context: Context) {

    fun listWritableCalendars(): List<DeviceCalendarInfo> {
        val out = mutableListOf<DeviceCalendarInfo>()
        val uri = CalendarContract.Calendars.CONTENT_URI
        val projection = arrayOf(
            CalendarContract.Calendars._ID,
            CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
            CalendarContract.Calendars.ACCOUNT_NAME,
            CalendarContract.Calendars.VISIBLE,
            CalendarContract.Calendars.SYNC_EVENTS,
        )
        val selection = "${CalendarContract.Calendars.VISIBLE} = 1 AND ${CalendarContract.Calendars.SYNC_EVENTS} = 1"
        context.contentResolver.query(uri, projection, selection, null, null)?.use { cursor ->
            val idIdx = cursor.getColumnIndex(CalendarContract.Calendars._ID)
            val nameIdx = cursor.getColumnIndex(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME)
            val accountIdx = cursor.getColumnIndex(CalendarContract.Calendars.ACCOUNT_NAME)
            while (cursor.moveToNext()) {
                if (idIdx < 0 || nameIdx < 0) continue
                val id = cursor.getLong(idIdx)
                val name = cursor.getString(nameIdx).orEmpty().ifBlank { "Календарь" }
                val account = if (accountIdx >= 0) cursor.getString(accountIdx).orEmpty() else ""
                out += DeviceCalendarInfo(id, name, account)
            }
        }
        return out.distinctBy { it.id }
    }

    fun queryEvents(
        calendarId: Long,
        rangeStartEpochMilli: Long,
        rangeEndEpochMilli: Long,
    ): List<DeviceCalendarEvent> {
        val builder = CalendarContract.Instances.CONTENT_URI.buildUpon()
        ContentUris.appendId(builder, rangeStartEpochMilli)
        ContentUris.appendId(builder, rangeEndEpochMilli)
        val uri = builder.build()
        val projection = arrayOf(
            CalendarContract.Instances.EVENT_ID,
            CalendarContract.Instances.CALENDAR_ID,
            CalendarContract.Instances.TITLE,
            CalendarContract.Instances.DESCRIPTION,
            CalendarContract.Instances.BEGIN,
            CalendarContract.Instances.END,
            CalendarContract.Instances.ALL_DAY,
            CalendarContract.Instances.LAST_DATE,
        )
        val selection = "${CalendarContract.Instances.CALENDAR_ID} = ?"
        val args = arrayOf(calendarId.toString())
        val out = mutableListOf<DeviceCalendarEvent>()
        context.contentResolver.query(uri, projection, selection, args, null)?.use { cursor ->
            val eventIdIdx = cursor.getColumnIndex(CalendarContract.Instances.EVENT_ID)
            val calIdx = cursor.getColumnIndex(CalendarContract.Instances.CALENDAR_ID)
            val titleIdx = cursor.getColumnIndex(CalendarContract.Instances.TITLE)
            val descIdx = cursor.getColumnIndex(CalendarContract.Instances.DESCRIPTION)
            val beginIdx = cursor.getColumnIndex(CalendarContract.Instances.BEGIN)
            val endIdx = cursor.getColumnIndex(CalendarContract.Instances.END)
            val allDayIdx = cursor.getColumnIndex(CalendarContract.Instances.ALL_DAY)
            val lastIdx = cursor.getColumnIndex(CalendarContract.Instances.LAST_DATE)
            while (cursor.moveToNext()) {
                if (eventIdIdx < 0 || beginIdx < 0 || endIdx < 0) continue
                val description = if (descIdx >= 0) cursor.getString(descIdx).orEmpty() else ""
                out += DeviceCalendarEvent(
                    eventId = cursor.getLong(eventIdIdx),
                    calendarId = if (calIdx >= 0) cursor.getLong(calIdx) else calendarId,
                    title = if (titleIdx >= 0) cursor.getString(titleIdx).orEmpty() else "",
                    description = description,
                    startEpochMilli = cursor.getLong(beginIdx),
                    endEpochMilli = cursor.getLong(endIdx),
                    allDay = allDayIdx >= 0 && cursor.getInt(allDayIdx) == 1,
                    lastModifiedEpochMilli = if (lastIdx >= 0) cursor.getLong(lastIdx) else cursor.getLong(beginIdx),
                    ncUid = parseNcUid(description),
                )
            }
        }
        return out.distinctBy { it.eventId }
    }

    fun insertEvent(
        calendarId: Long,
        title: String,
        description: String,
        startEpochMilli: Long,
        endEpochMilli: Long,
        allDay: Boolean,
        ncUid: String?,
    ): Long {
        val zone = ZoneId.systemDefault()
        val values = ContentValues().apply {
            put(CalendarContract.Events.CALENDAR_ID, calendarId)
            put(CalendarContract.Events.TITLE, title)
            put(CalendarContract.Events.DTSTART, startEpochMilli)
            put(CalendarContract.Events.DTEND, endEpochMilli)
            put(CalendarContract.Events.EVENT_TIMEZONE, zone.id)
            put(CalendarContract.Events.EVENT_END_TIMEZONE, zone.id)
            put(CalendarContract.Events.ALL_DAY, if (allDay) 1 else 0)
            put(CalendarContract.Events.DESCRIPTION, buildDescription(description, ncUid))
            put(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY)
        }
        val uri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, values)
            ?: error("Не удалось создать событие в календаре телефона")
        return ContentUris.parseId(uri)
    }

    fun updateEvent(
        eventId: Long,
        title: String,
        description: String,
        startEpochMilli: Long,
        endEpochMilli: Long,
        allDay: Boolean,
        ncUid: String?,
    ) {
        val uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId)
        val values = ContentValues().apply {
            put(CalendarContract.Events.TITLE, title)
            put(CalendarContract.Events.DTSTART, startEpochMilli)
            put(CalendarContract.Events.DTEND, endEpochMilli)
            put(CalendarContract.Events.ALL_DAY, if (allDay) 1 else 0)
            put(CalendarContract.Events.DESCRIPTION, buildDescription(description, ncUid))
        }
        context.contentResolver.update(uri, values, null, null)
    }

    fun deleteEvent(eventId: Long) {
        val uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId)
        context.contentResolver.delete(uri, null, null)
    }

    fun stampNcUid(eventId: Long, ncUid: String) {
        val uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId)
        val existing = readDescription(eventId)
        val values = ContentValues().apply {
            put(CalendarContract.Events.DESCRIPTION, buildDescription(existing, ncUid))
        }
        context.contentResolver.update(uri, values, null, null)
    }

    private fun readDescription(eventId: Long): String {
        val uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId)
        context.contentResolver.query(
            uri,
            arrayOf(CalendarContract.Events.DESCRIPTION),
            null,
            null,
            null,
        )?.use { cursor ->
            if (cursor.moveToFirst()) {
                val idx = cursor.getColumnIndex(CalendarContract.Events.DESCRIPTION)
                if (idx >= 0) return cursor.getString(idx).orEmpty()
            }
        }
        return ""
    }

    companion object {
        fun parseNcUid(description: String?): String? {
            val text = description.orEmpty()
            val prefix = CalDavClient.F7CLOUD_UID_MARKER_PREFIX
            val line = text.lines().firstOrNull { it.startsWith(prefix, ignoreCase = true) }
                ?: return null
            return line.removePrefix(prefix).trim().takeIf { it.isNotBlank() }
        }

        fun buildDescription(existing: String, ncUid: String?): String {
            val lines = existing.lines().filter {
                !it.startsWith(CalDavClient.F7CLOUD_UID_MARKER_PREFIX, ignoreCase = true)
            }
            val base = lines.joinToString("\n").trim()
            return if (ncUid.isNullOrBlank()) {
                base
            } else {
                listOf(base, "${CalDavClient.F7CLOUD_UID_MARKER_PREFIX}$ncUid")
                    .filter { it.isNotBlank() }
                    .joinToString("\n")
            }
        }
    }
}
