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

import android.content.res.Configuration
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowColumn
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import com.patrykandpatrick.vico.compose.axis.ChartShape
import com.patrykandpatrick.vico.compose.axis.axisGuidelineComponent
import com.patrykandpatrick.vico.compose.axis.axisLineComponent
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.column.columnChart
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
import com.patrykandpatrick.vico.compose.component.lineComponent
import com.patrykandpatrick.vico.compose.component.shapeComponent
import com.patrykandpatrick.vico.compose.component.textComponent
import com.patrykandpatrick.vico.core.axis.AxisPosition
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
import com.patrykandpatrick.vico.core.component.marker.MarkerComponent
import com.patrykandpatrick.vico.core.component.shape.LineComponent
import com.patrykandpatrick.vico.core.component.shape.Shapes.pillShape
import com.patrykandpatrick.vico.core.dimensions.MutableDimensions
import com.patrykandpatrick.vico.core.entry.ChartEntry
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
import com.patrykandpatrick.vico.core.entry.composed.ComposedChartEntryModelProducer
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
import com.patrykandpatrick.vico.core.scroll.InitialScroll
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
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.model.BleName
import llc.arma.ble.domain.usecase.ExportToXlsx
import llc.arma.ble.domain.usecase.GetBleBySerial
import llc.arma.ble.domain.usecase.GetBleNamesFlow
import llc.arma.ble.domain.usecase.GetHostHistoryBySerial
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.inject.Inject

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

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

}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HostHistory(
    ble: BleInfo,
    onDismiss: (() -> Unit)? = null,
) {

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

    LaunchedEffect(ble.serial) {
        viewModel.setEvent(HostHistoryContract.Event.OnStart(ble.name, ble.serial))
    }

    /*DisposableEffect("ble") {
        onDispose {
            viewModel.setEvent(AccelerometerHistoryContract.Event.StopMeasure)
        }
    }*/

    Column() {

        TopAppBar(
            navigationIcon = {
                onDismiss?.let {

                    IconButton(onClick = it) {
                        Icon(
                            imageVector = Icons.Rounded.ArrowBack,
                            contentDescription = null
                        )
                    }

                }
            },
            title = {
                val title = when(state){
                    is HostHistoryContract.State.Display -> {
                        when (state.loadingHistoryState) {
                            is ProgressState.Finished -> "Таблица (${state.loadingHistoryState.data.size})"
                            is ProgressState.Indeterminate -> "Таблица"
                            is ProgressState.Progress -> "Таблица"
                        }
                    }
                    HostHistoryContract.State.Exception -> "Таблица"
                }

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

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

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

        Box(modifier = Modifier) {

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

        }

    }

}

val dayFormatter = SimpleDateFormat("dd", Locale.getDefault())
val dateFormatter = SimpleDateFormat("dd.MM", Locale.getDefault())
val timeFormatter = SimpleDateFormat("HH:mm", Locale.getDefault())

val colorsStack = listOf(
    Color(0xff2f4f4f), Color(0xff7f0000), Color(0xFFFF0000), Color(0xffffd700),
    Color(0xffa9a9a9), Color(0xff00fa9a), Color(0xff00ffff), Color(0xfff0e68c),
    Color(0xff00bfff), Color(0xff0000ff), Color(0xfff08080), Color(0xffadff2f),
    Color(0xffff00ff), Color(0xff4169e1), Color(0xffff1493), Color(0xffee82ee),
)

val startAxisValueFormatter =
    AxisValueFormatter<AxisPosition.Vertical.Start> { value, chartValues ->
        " "
    }

val axisValueFormatter =
    AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
        val first = (chartValues.chartEntryModel.entries.firstOrNull()?.firstOrNull() as? HostEntry)
        val last = (chartValues.chartEntryModel.entries.firstOrNull()?.lastOrNull() as? HostEntry)
        val previous = (chartValues.chartEntryModel.entries.firstOrNull()?.getOrNull(value.toInt() - 1) as? HostEntry)
        val current = (chartValues.chartEntryModel.entries.firstOrNull()?.getOrNull(value.toInt()) as? HostEntry)

        if(current != null) {
            if (first == current || last == current) {
                dateFormatter.format(Date(current.localDate))
            } else {
                if(previous != null && dayFormatter.format(previous.localDate) != dayFormatter.format(current.localDate)){
                    dateFormatter.format(Date(current.localDate))
                }else{
                    timeFormatter.format(Date(current.localDate))
                }
            }.orEmpty()
        } else {
            " "
        }
    }

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun Display(
    state: HostHistoryContract.State.Display
) {

    val configuration = LocalConfiguration.current

    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 allSerials = remember(state) { state.loadingHistoryState.data.flatMap { it.value }.distinct() }

                    val colors = remember(allSerials) {
                        allSerials.mapIndexed { index, s ->
                            Pair(s, colorsStack[index])
                        }.toMap()
                    }

                    var selectedSerials by rememberSaveable {
                        mutableStateOf(allSerials.take(1))
                    }

                    val serials = remember(selectedSerials) { allSerials.filter { selectedSerials.contains(it) } }

                    val entries = remember(serials, state) {

                        serials.map { serial ->

                            ChartEntryModelProducer(
                                state.loadingHistoryState.data.mapIndexed { index, historyPoint ->
                                    if(historyPoint.value.contains(serial)) {
                                        HostEntry(historyPoint.date, index.toFloat(), 1f)
                                    } else {
                                        HostEntry(historyPoint.date, index.toFloat(), 0.0001f)
                                    }
                                }
                            )

                        }

                    }

                    val producer = remember(entries) { ComposedChartEntryModelProducer(entries) }

                    val chart = columnChart(
                        persistentMarkers = state.loadingHistoryState.data.mapIndexedNotNull { index, historyPoint ->
                            if(historyPoint.hit) {
                                Pair(
                                    index.toFloat(),
                                    MarkerComponent(
                                        label = textComponent(
                                            textSize = 0.sp,
                                            padding = MutableDimensions(10f, 10f, 10f, 10f),
                                            margins = MutableDimensions(10f, 10f, 10f, 10f),
                                            color = Color.Red,
                                            background = shapeComponent(
                                                color = Color.Red,
                                                shape = pillShape
                                            )
                                        ),
                                        indicator = null,
                                        guideline = axisLineComponent(
                                            thickness = 0.dp
                                        )
                                    )
                                )
                            }else{
                                null
                            }
                        }.toMap(),
                        innerSpacing = 2.dp,
                        columns = serials.map { LineComponent(color = colors[it]!!.toArgb(), thicknessDp = 7f, shape= pillShape) },
                        spacing = 8.dp,
                    )

                    @Composable
                    fun LegendItem(s: String) {

                        FilterChip(
                            selected = selectedSerials.contains(s),
                            onClick = {
                                selectedSerials = if(selectedSerials.contains(s)){
                                    selectedSerials.toMutableList().apply {
                                        remove(s)
                                    }
                                }else{
                                    selectedSerials.toMutableList().apply {
                                        add(s)
                                    }
                                }
                            },
                            leadingIcon = {
                                Surface(
                                    shape = CircleShape,
                                    color = colors[s]!!,
                                    modifier = Modifier.size(28.dp)
                                ) {}
                            },
                            label = { Column {
                                Text(text = state.bleNames.firstOrNull { it.serial == s }?.name ?: s)
                                Text(
                                    style = MaterialTheme.typography.bodySmall.copy(
                                        color = LocalTextStyle.current.color.copy(
                                            alpha = ContentAlpha.medium
                                        )
                                    ),
                                    text = s
                                )
                            }}
                        )

                    }

                    when (configuration.orientation) {
                        Configuration.ORIENTATION_LANDSCAPE -> {
                            Row(
                                horizontalArrangement = Arrangement.spacedBy(12.dp)
                            ) {

                                Chart(
                                    chart = chart,
                                    chartModelProducer = producer,
                                    bottomAxis = bottomAxis(
                                        labelRotationDegrees = -90f,
                                        valueFormatter = axisValueFormatter,
                                        tickLength = 0.dp,
                                    ),
                                    startAxis = startAxis(
                                        valueFormatter = startAxisValueFormatter
                                    ),
                                    modifier = Modifier
                                        .fillMaxHeight()
                                        .weight(1f),
                                    autoScaleUp = AutoScaleUp.None,
                                    diffAnimationSpec = tween(0),
                                    chartScrollSpec = rememberChartScrollSpec(
                                        initialScroll = InitialScroll.End,
                                        autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
                                        autoScrollAnimationSpec = tween(0)
                                    )
                                )

                                VerticalDivider()

                                FlowRow(
                                    maxItemsInEachRow = 1,
                                    verticalArrangement = Arrangement.spacedBy(4.dp),
                                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                                    modifier = Modifier.verticalScroll(rememberScrollState())
                                ) {

                                    allSerials.mapIndexed { index, s ->
                                        LegendItem(s = s)
                                    }

                                }

                            }
                        }
                        else -> {
                            Column(
                                verticalArrangement = Arrangement.spacedBy(12.dp)
                            ) {

                                FlowColumn(
                                    maxItemsInEachColumn = 2,
                                    verticalArrangement = Arrangement.spacedBy(4.dp),
                                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                                    modifier = Modifier.horizontalScroll(rememberScrollState())
                                ) {

                                    allSerials.mapIndexed { index, s ->
                                        LegendItem(s = s)
                                    }

                                }

                                HorizontalDivider()

                                Chart(

                                    chart = chart,
                                    chartModelProducer = producer,
                                    bottomAxis = bottomAxis(
                                        labelRotationDegrees = -90f,
                                        valueFormatter = axisValueFormatter,
                                        tickLength = 0.dp,
                                    ),
                                    modifier = Modifier
                                        .fillMaxWidth()
                                        .weight(1f),
                                    autoScaleUp = AutoScaleUp.None,
                                    diffAnimationSpec = tween(0),
                                    chartScrollSpec = rememberChartScrollSpec(
                                        initialScroll = InitialScroll.End,
                                        autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
                                        autoScrollAnimationSpec = tween(0)
                                    )
                                )

                            }
                        }
                    }

                }

            }
            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 HostHistoryContract {

    sealed class Event : ViewEvent {

        data object StopMeasure : Event()

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

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

        data class OnExportHistory(
            val serial: String,
        ) : Event()

    }

    sealed class State : ViewState {

        data class Display(
            val bleName: String,
            val bleNames: List<BleName>,
            val loadingHistoryState : ProgressState<List<Ble.Host.HistoryPoint>>
        ) : State()

        data object Exception : State()

    }

    sealed class Effect : ViewSideEffect {

    }

}



@HiltViewModel
class HostHistoryViewModel @Inject constructor(
    private val getHostHistoryBySerial: GetHostHistoryBySerial,
    private val getBleBySerial: GetBleBySerial,
    private val getBleNamesFlow: GetBleNamesFlow,
    private val exportToXlsx: ExportToXlsx
) : BaseViewModel<HostHistoryContract.State, HostHistoryContract.Event, HostHistoryContract.Effect>() {

    var measureJob: Job? = null

    private var lastSerial: String? = null

    override fun setInitialState() = HostHistoryContract.State.Display(
        "",
        emptyList(),
        ProgressState.Indeterminate
    )

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

    private fun reduce(
        state: HostHistoryContract.State,
        event: HostHistoryContract.Event.OnExportHistory
    ) {

        if(state is HostHistoryContract.State.Display) {

            if(state.loadingHistoryState is ProgressState.Finished) {

                exportToXlsx.invoke(event.serial, state.loadingHistoryState.data)

            }

        }

    }

    private fun reduce(
        state: HostHistoryContract.State,
        event: HostHistoryContract.Event.StopMeasure
    ) {

        measureJob?.cancel()
        measureJob = null

        setState {
            HostHistoryContract.State.Exception
        }

    }

    private fun reduce(
        state: HostHistoryContract.State,
        event: HostHistoryContract.Event.OnStart
    ) {

        viewModelScope.launch {

            if(state is HostHistoryContract.State.Display) {

                if(lastSerial != event.serial) {

                    lastSerial = event.serial

                    setState {
                        HostHistoryContract.State.Display(event.bleName, emptyList(), ProgressState.Indeterminate)
                    }

                    measureJob?.cancel()
                    measureJob = null

                    val names = getBleNamesFlow().first()

                    measureJob = getHostHistoryBySerial(event.serial).onEach {
                        it.fold(
                            onSuccess = {
                                setState {
                                    HostHistoryContract.State.Display(event.bleName, names, it)
                                }
                            },
                            onFailure = {
                                setState {
                                    HostHistoryContract.State.Exception
                                }
                            }
                        )
                    }.launchIn(this)

                }

            }

        }

    }

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

            setState {

                HostHistoryContract.State.Display("", emptyList(),  ProgressState.Indeterminate)
            }

            measureJob?.cancel()
            measureJob = null

            val names = getBleNamesFlow().first()

            measureJob = getHostHistoryBySerial(event.serial).onEach {
                it.fold(
                    onSuccess = {
                        setState {
                            HostHistoryContract.State.Display(event.bleName, names, it)
                        }
                    },
                    onFailure = {
                        setState {
                            HostHistoryContract.State.Exception
                        }
                    }
                )
            }.launchIn(this)

        }
    }

}