package com.pixelized.headache.repository.event

import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.net.Uri
import android.provider.CalendarContract
import com.pixelized.headache.repository.calendar.GoogleCalendarIdRepository
import com.pixelized.headache.repository.event.factory.EventFactory
import com.pixelized.headache.utils.extention.event
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class EventRepository @Inject constructor(
    @param:ApplicationContext private val context: Context,
    private val calendarIdRepository: GoogleCalendarIdRepository,
    private val factory: EventFactory,
) {
    private val timeZone = TimeZone.getTimeZone("UTC")
    private val scope = CoroutineScope(Dispatchers.IO + Job())

    // Main flow of event. Store event in a map by Id
    private val eventFlow = MutableStateFlow(mapOf<Long, Event>())

    // Collectable data. Provide events in a sorted list by dates.
    private val eventListFlow: StateFlow<Collection<Event>> = eventFlow
        .map { events -> events.values.sortedBy { it.date } }
        .stateIn(
            scope = scope,
            started = SharingStarted.Lazily,
            initialValue = emptyList(),
        )

    // Collectable data. Provide event data in a nested map struct of year, month, day.
    private val eventMapFlow: StateFlow<Map<Int, Map<Int, Map<Int, List<Event>>>>> = eventListFlow
        .map { events ->
            events.fold(initial = hashMapOf<Int, HashMap<Int, HashMap<Int, MutableList<Event>>>>()) { acc, event ->
                acc.also {
                    val years = it.getOrPut(key = event.date.year) {
                        hashMapOf(
                            Calendar.JANUARY to hashMapOf(),
                            Calendar.FEBRUARY to hashMapOf(),
                            Calendar.MARCH to hashMapOf(),
                            Calendar.APRIL to hashMapOf(),
                            Calendar.MAY to hashMapOf(),
                            Calendar.JUNE to hashMapOf(),
                            Calendar.JULY to hashMapOf(),
                            Calendar.AUGUST to hashMapOf(),
                            Calendar.SEPTEMBER to hashMapOf(),
                            Calendar.OCTOBER to hashMapOf(),
                            Calendar.NOVEMBER to hashMapOf(),
                            Calendar.DECEMBER to hashMapOf(),
                        )
                    }
                    val months = years.getOrPut(key = event.date.month) { hashMapOf() }
                    val events = months.getOrPut(key = event.date.day) { mutableListOf() }
                    events.add(event)
                }
            }
        }
        .stateIn(
            scope = scope,
            started = SharingStarted.Lazily,
            initialValue = emptyMap(),
        )

    private val maxPillAmountPerMonth: StateFlow<Int> = eventMapFlow
        .map { events ->
            events.values.maxOf { yearEntry: Map<Int, Map<Int, List<Event>>> ->
                yearEntry.values.maxOf { monthEntry ->
                    monthEntry.values.sumOf { events -> events.sumOf { it.pills.size } }
                }
            }
        }
        .stateIn(
            scope = scope,
            started = SharingStarted.Lazily,
            initialValue = 0,
        )

    init {
        calendarIdRepository.calendarId
            .onEach { id ->
                fetchEvents(
                    calendarId = id,
                    startDate = Event.Date(day = 1, month = Calendar.JANUARY, year = 2023),
                    endDate = Calendar.getInstance().apply { add(Calendar.DAY_OF_MONTH, 1) }.event,
                )
            }
            .launchIn(scope = scope)
    }

    fun eventsListFlow(): StateFlow<Collection<Event>> = eventListFlow

    fun eventsMapFlow(): StateFlow<Map<Int, Map<Int, Map<Int, List<Event>>>>> = eventMapFlow

    fun maxPillAmountPerMonthFlow(): StateFlow<Int> = maxPillAmountPerMonth

    fun event(id: Long): Event? = eventFlow.value.get(key = id)

    fun createEvent(
        calendarId: Long = calendarIdRepository.calendarId.value,
        title: String,
        description: String,
        year: Int,
        month: Int,
        day: Int,
    ): Boolean {
        val (startMillis, endMillis) = Calendar.getInstance(timeZone).apply {
            set(year, month, day, 0, 0, 0)
            set(Calendar.MILLISECOND, 0)
        }.let {
            val startMillis = it.timeInMillis
            it.add(Calendar.DAY_OF_MONTH, 1)
            val endMillis = it.timeInMillis
            startMillis to endMillis
        }

        val values = ContentValues().apply {
            put(CalendarContract.Events.CALENDAR_ID, calendarId)
            put(CalendarContract.Events.TITLE, title)
            put(CalendarContract.Events.DESCRIPTION, description)
            put(CalendarContract.Events.DTSTART, startMillis)
            put(CalendarContract.Events.DTEND, endMillis)
            put(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_FREE)
            put(CalendarContract.Events.ALL_DAY, 1)
            put(CalendarContract.Events.EVENT_TIMEZONE, "UTC")
        }

        return context.contentResolver.insert(
            CalendarContract.Events.CONTENT_URI,
            values
        ) != null
    }

    fun updateEvent(
        id: Long,
        title: String? = null,
        description: String? = null,
        year: Int,
        month: Int,
        day: Int,
    ): Boolean {
        val (startMillis, endMillis) = Calendar.getInstance(timeZone).apply {
            set(year, month, day, 0, 0, 0)
            set(Calendar.MILLISECOND, 0)
        }.let {
            val startMillis = it.timeInMillis
            it.add(Calendar.DAY_OF_MONTH, 1)
            val endMillis = it.timeInMillis
            startMillis to endMillis
        }

        val values = ContentValues().apply {
            title?.let { put(CalendarContract.Events.TITLE, it) }
            description?.let { put(CalendarContract.Events.DESCRIPTION, it) }
            put(CalendarContract.Events.DTSTART, startMillis)
            put(CalendarContract.Events.DTEND, endMillis)
        }

        val updateUri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, id)
        val rows: Int = context.contentResolver.update(updateUri, values, null, null)

        return rows > 0
    }

    fun deleteEvent(
        eventId: Long,
    ): Boolean {
        val deleteUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId)
        val success = context.contentResolver.delete(deleteUri, null, null) > 0
        if (success) {
            eventFlow.value = eventFlow.value.toMutableMap().also {
                it.remove(eventId)
            }
        }
        return success
    }

    suspend fun fetchEvents(
        calendarId: Long = calendarIdRepository.calendarId.value,
        startDate: Event.Date,
        endDate: Event.Date,
    ) = withContext(Dispatchers.IO) {
        val startCalendar = Calendar.getInstance().apply { this.event = startDate }
        val endCalendar = Calendar.getInstance().apply { this.event = endDate }

        val projection = arrayOf(
            CalendarContract.Events._ID,
            CalendarContract.Events.TITLE,
            CalendarContract.Events.DTSTART,
            CalendarContract.Events.DESCRIPTION
        )

        val selection = ("${CalendarContract.Events.CALENDAR_ID} = ? AND " +
                "${CalendarContract.Events.DTSTART} >= ? AND " +
                "${CalendarContract.Events.DTSTART} <= ?")

        val selectionArgs = arrayOf(
            calendarId.toString(),
            startCalendar.timeInMillis.toString(),
            endCalendar.timeInMillis.toString()
        )

        val cursor = context.contentResolver.query(
            CalendarContract.Events.CONTENT_URI,
            projection,
            selection,
            selectionArgs,
            "${CalendarContract.Events.DTSTART} ASC"
        )

        eventFlow.value = eventFlow.value.toMutableMap().also { events ->
            cursor?.use {
                val idIdx = it.getColumnIndex(CalendarContract.Events._ID)
                val titleIdx = it.getColumnIndex(CalendarContract.Events.TITLE)
                val descIdx = it.getColumnIndex(CalendarContract.Events.DESCRIPTION)
                val startIdx = it.getColumnIndex(CalendarContract.Events.DTSTART)

                while (it.moveToNext()) {
                    val event = factory.parseEvent(
                        id = it.getLong(idIdx),
                        title = it.getString(titleIdx) ?: "",
                        description = it.getString(descIdx) ?: "",
                        start = it.getLong(startIdx),
                    )
                    events[event.id] = event
                }
            }
        }
    }
}