package de.tsschulz.timeclock.ui.booking

import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import de.tsschulz.timeclock.data.api.CalendarDto
import de.tsschulz.timeclock.data.api.SickEntryDto
import de.tsschulz.timeclock.data.api.SickTypeDto
import de.tsschulz.timeclock.data.api.TimeClockApiClient
import de.tsschulz.timeclock.data.api.TimefixDto
import de.tsschulz.timeclock.data.api.VacationDto
import de.tsschulz.timeclock.data.api.WorkdaysDto
import de.tsschulz.timeclock.data.api.WorklogEntryDto
import de.tsschulz.timeclock.data.auth.TokenStore
import de.tsschulz.timeclock.data.booking.BookingRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.time.LocalDate
import java.time.YearMonth

data class BookingUiState(
    val loading: Boolean = false,
    val error: String? = null,
    val vacationLoading: Boolean = false,
    val vacationError: String? = null,
    val sickLoading: Boolean = false,
    val sickError: String? = null,
    val timefixLoading: Boolean = false,
    val timefixError: String? = null,
    val workdaysLoading: Boolean = false,
    val workdaysError: String? = null,
    val calendarLoading: Boolean = false,
    val calendarError: String? = null,
    val vacations: List<VacationDto> = emptyList(),
    val sickEntries: List<SickEntryDto> = emptyList(),
    val sickTypes: List<SickTypeDto> = emptyList(),
    val timefixes: List<TimefixDto> = emptyList(),
    val worklogEntries: List<WorklogEntryDto> = emptyList(),
    val timefixDate: String = LocalDate.now().toString(),
    val workdaysYear: Int = LocalDate.now().year,
    val workdays: WorkdaysDto? = null,
    val calendarYear: Int = LocalDate.now().year,
    val calendarMonth: Int = LocalDate.now().monthValue,
    val calendar: CalendarDto? = null,
)

class BookingViewModel(
    private val repository: BookingRepository,
) : ViewModel() {
    private val _uiState = MutableStateFlow(BookingUiState())
    val uiState: StateFlow<BookingUiState> = _uiState.asStateFlow()

    fun loadPhase4() {
        loadVacations()
    }

    fun loadVacations() = launchSection(
        start = { copy(vacationLoading = true, vacationError = null) },
        success = { vacations -> copy(vacationLoading = false, vacationError = null, vacations = vacations) },
        failure = { message -> copy(vacationLoading = false, vacationError = message) },
        block = { repository.getVacations() },
    )
    fun createVacation(type: Int, start: String, end: String?) = launchMutation {
        repository.createVacation(type, start, end)
        loadVacations()
    }
    fun deleteVacation(id: String) = launchMutation {
        repository.deleteVacation(id)
        loadVacations()
    }

    fun loadSick() = launchSection(
        start = { copy(sickLoading = true, sickError = null) },
        success = { result -> copy(sickLoading = false, sickError = null, sickEntries = result.first, sickTypes = result.second) },
        failure = { message -> copy(sickLoading = false, sickError = message) },
        block = { repository.getSickEntries() to repository.getSickTypes() },
    )
    fun createSick(typeId: String, start: String, end: String?) = launchMutation {
        repository.createSick(typeId, start, end)
        loadSick()
    }
    fun deleteSick(id: String) = launchMutation {
        repository.deleteSick(id)
        loadSick()
    }

    fun setTimefixDate(date: String) {
        _uiState.update { it.copy(timefixDate = date) }
        loadTimefix(date)
    }

    fun loadTimefix(date: String = _uiState.value.timefixDate) = launchSection(
        start = { copy(timefixLoading = true, timefixError = null) },
        success = { result -> copy(timefixLoading = false, timefixError = null, timefixes = result.first, worklogEntries = result.second) },
        failure = { message -> copy(timefixLoading = false, timefixError = message) },
        block = { repository.getTimefixes() to repository.getWorklogEntries(date) },
    )
    fun createTimefix(worklogId: String, date: String, time: String, action: String) = launchMutation {
        repository.createTimefix(worklogId, date, time, action)
        loadTimefix(date)
    }
    fun deleteTimefix(id: String) = launchMutation {
        repository.deleteTimefix(id)
        loadTimefix()
    }

    fun loadWorkdays(year: Int) = launchSection(
        start = { copy(workdaysLoading = true, workdaysError = null, workdaysYear = year) },
        success = { workdays -> copy(workdaysLoading = false, workdaysError = null, workdays = workdays) },
        failure = { message -> copy(workdaysLoading = false, workdaysError = message) },
        block = { repository.getWorkdays(year) },
    )

    fun changeCalendarMonth(delta: Int) {
        val current = YearMonth.of(_uiState.value.calendarYear, _uiState.value.calendarMonth).plusMonths(delta.toLong())
        loadCalendar(current.year, current.monthValue)
    }

    fun loadCalendar(year: Int, month: Int) = launchSection(
        start = { copy(calendarLoading = true, calendarError = null, calendarYear = year, calendarMonth = month) },
        success = { calendar -> copy(calendarLoading = false, calendarError = null, calendar = calendar) },
        failure = { message -> copy(calendarLoading = false, calendarError = message) },
        block = { repository.getCalendar(year, month) },
    )

    private fun <T> launchSection(
        start: BookingUiState.() -> BookingUiState,
        success: BookingUiState.(T) -> BookingUiState,
        failure: BookingUiState.(String) -> BookingUiState,
        block: suspend () -> T,
    ) {
        viewModelScope.launch {
            _uiState.update { it.start().copy(error = null) }
            runCatching { block() }
                .onSuccess { value -> _uiState.update { it.success(value) } }
                .onFailure { e ->
                    val message = e.message ?: "Daten konnten nicht geladen werden"
                    _uiState.update { it.failure(message) }
                }
        }
    }

    private fun launchMutation(block: suspend () -> Unit) {
        viewModelScope.launch {
            _uiState.update { it.copy(loading = true, error = null) }
            runCatching { block() }
                .onFailure { e -> _uiState.update { it.copy(error = e.message ?: "Aktion fehlgeschlagen") } }
            _uiState.update { it.copy(loading = false) }
        }
    }

    class Factory(private val application: Application) : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            val tokenStore = TokenStore(application)
            return BookingViewModel(BookingRepository(TimeClockApiClient(tokenStore))) as T
        }
    }
}
