package llc.arma.ble.app.ui.screen.inspection.thermometer.view

import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
import com.patrykandpatrick.vico.compose.axis.vertical.startAxis
import com.patrykandpatrick.vico.compose.chart.Chart
import com.patrykandpatrick.vico.compose.chart.line.lineChart
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState
import com.patrykandpatrick.vico.core.axis.AxisPosition
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
import com.patrykandpatrick.vico.core.entry.ChartEntry
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.BaseViewModel
import llc.arma.ble.app.ui.common.ViewEvent
import llc.arma.ble.app.ui.common.ViewSideEffect
import llc.arma.ble.app.ui.common.ViewState
import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.model.BleInfo
import llc.arma.ble.domain.usecase.GetTemperatureHistoryBySerial
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.inject.Inject

class TemperatureEntry(
    val localDate: Long,
    override val x: Float,
    override val y: Float,
) : ChartEntry {

    override fun withY(y: Float) = TemperatureEntry(localDate, x, y)

}



val formatter = SimpleDateFormat("dd.MM HH:mm", Locale.getDefault())

@Composable
fun TemperatureHistory(
    ble: BleInfo
) {

    val viewModel = hiltViewModel<TemperatureHistoryViewModel>()
    val state = viewModel.viewState.value

    LaunchedEffect(ble.serial) {
        viewModel.setEvent(TemperatureHistoryContract.Event.OnStart(ble.serial))
    }

    Column(
        modifier = Modifier.fillMaxHeight(0.9f)
    ) {

        Row(
            modifier = Modifier.padding(horizontal = 12.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {

            val title = when(state){
                is TemperatureHistoryContract.State.Display -> {
                    when (state.loadingHistoryState) {
                        is ProgressState.Finished -> "История измерений (${state.loadingHistoryState.data.size})"
                        is ProgressState.Indeterminate -> "История измерений"
                        is ProgressState.Progress -> "История измерений"
                    }
                }
                TemperatureHistoryContract.State.Exception -> "История измерений"
            }

            Text(
                modifier = Modifier.weight(1f),
                text = title,
                style = MaterialTheme.typography.titleLarge
            )

            IconButton(
                onClick = {
                    viewModel.setEvent(TemperatureHistoryContract.Event.OnRefreshHistory(ble.serial))
                },
                enabled = when(state){
                    is TemperatureHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
                    TemperatureHistoryContract.State.Exception -> true
                }
            ) {
                Icon(
                    imageVector = Icons.Rounded.Refresh,
                    contentDescription = null
                )
            }

        }

        Spacer(modifier = Modifier.height(16.dp))

        Box(modifier = Modifier) {

            when (state) {
                is TemperatureHistoryContract.State.Display -> Display(state = state)
                TemperatureHistoryContract.State.Exception -> Exception()
            }

        }

    }

}
    

@Composable
fun Display(
    state: TemperatureHistoryContract.State.Display
) {

    Box(modifier = Modifier
        .padding(8.dp)
        .fillMaxSize()
    ) {

        when (state.loadingHistoryState) {

            is ProgressState.Finished -> {

                if(state.loadingHistoryState.data.isEmpty()){

                    Text(
                        modifier = Modifier.align(Alignment.Center),
                        text = "Нет данных"
                    )

                } else {

                    val producer = remember(state.loadingHistoryState.data) {
                        state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
                            TemperatureEntry(measurePoint.date, index.toFloat(), measurePoint.value)
                        }.let {
                            ChartEntryModelProducer(it)
                        }
                    }

                    val axisValueFormatter =
                        AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
                            (chartValues.chartEntryModel.entries.firstOrNull()
                                ?.getOrNull(value.toInt()) as? TemperatureEntry)
                                ?.localDate
                                ?.let { formatter.format(Date(it)) }
                                .orEmpty()
                        }

                    val lineChart = lineChart()

                    val scrollState = rememberChartScrollState()

                    Chart(
                        chartScrollState = scrollState,
                        chart = lineChart,
                        chartModelProducer = producer,
                        startAxis = startAxis(),
                        bottomAxis = bottomAxis(
                            tickLength = 0.dp,
                            valueFormatter = axisValueFormatter,
                            labelRotationDegrees = -90f,
                        ),
                        modifier = Modifier.fillMaxSize(),
                    )

                    LaunchedEffect(scrollState.maxValue) {
                        scrollState.scrollBy(scrollState.maxValue)
                    }

                }

            }
            is ProgressState.Indeterminate -> {

                CircularProgressIndicator(
                    strokeCap = StrokeCap.Round,
                    modifier = Modifier.align(Alignment.Center)
                )

            }
            is ProgressState.Progress -> {

                val progressAnimDuration = 1500
                val progressAnimation by animateFloatAsState(
                    targetValue = state.loadingHistoryState.value,
                    animationSpec = tween(
                        durationMillis = progressAnimDuration,
                        easing = FastOutSlowInEasing
                    ), label = ""
                )

                CircularProgressIndicator(
                    strokeCap = StrokeCap.Round,
                    progress = progressAnimation,
                    modifier = Modifier.align(Alignment.Center)
                )

            }

        }

    }
}

@Composable
private fun Exception() {
    Box(
        modifier = Modifier
            .padding(8.dp)
            .fillMaxWidth()
            .aspectRatio(2f),
    ){

        Text(
            textAlign = TextAlign.Center,
            text = "Во время загрузки произошла ошибка",
            modifier = Modifier.align(Alignment.Center)
        )

    }

}

class TemperatureHistoryContract {

    sealed class Event : ViewEvent {

        data class OnStart(
            val serial: String
        ) : Event()

        data class OnRefreshHistory(
            val serial: String
        ) : Event()

    }

    sealed class State : ViewState {

        data class Display(
            val loadingHistoryState : ProgressState<List<Ble.Thermometer.HistoryPoint>>
        ) : State()

        object Exception : State()

    }

    sealed class Effect : ViewSideEffect {

    }

}



@HiltViewModel
class TemperatureHistoryViewModel @Inject constructor(
    private val getTemperatureHistoryBySerial: GetTemperatureHistoryBySerial
) : BaseViewModel<TemperatureHistoryContract.State, TemperatureHistoryContract.Event, TemperatureHistoryContract.Effect>() {

    private var lastSerial: String? = null

    override fun setInitialState() = TemperatureHistoryContract.State.Display(
        ProgressState.Indeterminate
    )

    override fun handleEvents(event: TemperatureHistoryContract.Event) {
        when(event){
            is TemperatureHistoryContract.Event.OnStart -> reduce(viewState.value, event)
            is TemperatureHistoryContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
        }
    }

    private fun reduce(
        state: TemperatureHistoryContract.State,
        event: TemperatureHistoryContract.Event.OnStart
    ) {
        viewModelScope.launch {

            if(state is TemperatureHistoryContract.State.Display) {

                if(lastSerial != event.serial) {

                    lastSerial = event.serial

                    setState {
                        TemperatureHistoryContract.State.Display(ProgressState.Indeterminate)
                    }

                    getTemperatureHistoryBySerial(event.serial).onEach {
                        it.fold(
                            onSuccess = {
                                setState {
                                    TemperatureHistoryContract.State.Display(it)
                                }
                            },
                            onFailure = {
                                setState {
                                    TemperatureHistoryContract.State.Exception
                                }
                            }
                        )
                    }.launchIn(this)

                }

            }

        }

    }

    private fun reduce(
        state: TemperatureHistoryContract.State,
        event: TemperatureHistoryContract.Event.OnRefreshHistory
    ) {
        viewModelScope.launch {

            setState {
                TemperatureHistoryContract.State.Display(ProgressState.Indeterminate)
            }

            getTemperatureHistoryBySerial(event.serial).onEach {
                it.fold(
                    onSuccess = {
                        setState {
                            TemperatureHistoryContract.State.Display(it)
                        }
                    },
                    onFailure = {
                        setState {
                            TemperatureHistoryContract.State.Exception
                        }
                    }
                )
            }.launchIn(this)

        }
    }

}