package xyz.kusoft.tradebotmonitor

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.view.View
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.max
import kotlin.math.min

class CandleChartView(context: Context) : View(context) {
    var candles: List<Candle> = emptyList()
        set(value) {
            field = value.takeLast(90)
            invalidate()
        }

    var palette: AppPalette = AppPalette.dark()
        set(value) {
            field = value
            invalidate()
        }

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        textSize = 11f * resources.displayMetrics.scaledDensity
        typeface = android.graphics.Typeface.create("sans", android.graphics.Typeface.NORMAL)
    }
    private val dateFormat = SimpleDateFormat("dd.MM HH:mm", Locale("ru", "RU"))

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawColor(palette.chartBg)
        val rows = candles.filter { it.high > 0.0 && it.low > 0.0 }
        if (rows.size < 2) {
            drawEmpty(canvas)
            return
        }

        val left = dp(8).toFloat()
        val right = width - dp(62).toFloat()
        val top = dp(12).toFloat()
        val bottom = height - dp(24).toFloat()
        val volumeHeight = max(dp(44).toFloat(), height * 0.17f)
        val chartBottom = bottom - volumeHeight - dp(8)
        val chartHeight = max(dp(120).toFloat(), chartBottom - top)
        val plotWidth = right - left

        val values = mutableListOf<Double>()
        rows.forEach {
            values += it.high
            values += it.low
            it.ema50?.let(values::add)
            it.ema200?.let(values::add)
        }
        var minPrice = values.minOrNull() ?: 0.0
        var maxPrice = values.maxOrNull() ?: 1.0
        val rawRange = max(maxPrice - minPrice, maxPrice * 0.0001)
        minPrice -= rawRange * 0.08
        maxPrice += rawRange * 0.08
        val range = max(maxPrice - minPrice, 1e-9)

        fun y(value: Double): Float = (top + ((maxPrice - value) / range).toFloat() * chartHeight)
        fun x(index: Int): Float {
            val step = plotWidth / rows.size
            return left + index * step + step / 2f
        }

        drawGrid(canvas, left, right, top, chartHeight, minPrice, maxPrice)
        drawVolumes(canvas, rows, left, plotWidth, chartBottom + dp(8), volumeHeight - dp(4))
        val bodyWidth = (plotWidth / rows.size * 0.62f).coerceIn(dp(3).toFloat(), dp(10).toFloat())
        rows.forEachIndexed { index, candle -> drawCandle(canvas, candle, x(index), bodyWidth, ::y) }
        drawAverageLine(canvas, rows, { it.ema50 }, ::x, ::y, android.graphics.Color.rgb(147, 197, 253))
        drawAverageLine(canvas, rows, { it.ema200 }, ::x, ::y, android.graphics.Color.rgb(245, 158, 11))
        drawLastPrice(canvas, rows.last(), left, right, ::y)
        drawTimeAxis(canvas, rows, left, plotWidth, height - dp(7).toFloat())
    }

    private fun drawEmpty(canvas: Canvas) {
        textPaint.color = android.graphics.Color.rgb(148, 163, 184)
        textPaint.textAlign = Paint.Align.LEFT
        canvas.drawText("Нет свечей для графика", dp(14).toFloat(), dp(30).toFloat(), textPaint)
    }

    private fun drawGrid(
        canvas: Canvas,
        left: Float,
        right: Float,
        top: Float,
        chartHeight: Float,
        minPrice: Double,
        maxPrice: Double,
    ) {
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 1f
        paint.color = palette.chartGrid
        textPaint.color = android.graphics.Color.rgb(154, 167, 183)
        textPaint.textAlign = Paint.Align.RIGHT
        for (i in 0..5) {
            val ratio = i / 5f
            val yy = top + chartHeight * ratio
            canvas.drawLine(left, yy, right + dp(4), yy, paint)
            val price = maxPrice - (maxPrice - minPrice) * ratio
            canvas.drawText(compactPrice(price), width - dp(5).toFloat(), yy + dp(4), textPaint)
        }
    }

    private fun drawVolumes(
        canvas: Canvas,
        rows: List<Candle>,
        left: Float,
        plotWidth: Float,
        top: Float,
        height: Float,
    ) {
        val maxVolume = max(rows.maxOf { it.volume }, 1.0)
        val step = plotWidth / rows.size
        val barWidth = (step * 0.56f).coerceIn(dp(2).toFloat(), dp(8).toFloat())
        paint.style = Paint.Style.FILL
        rows.forEachIndexed { index, candle ->
            val up = candle.close >= candle.open
            paint.color = if (up) withAlpha(palette.green, 90) else withAlpha(palette.red, 90)
            val barHeight = max(1f, (candle.volume / maxVolume).toFloat() * height)
            val x = left + index * step + step / 2f - barWidth / 2f
            canvas.drawRect(x, top + height - barHeight, x + barWidth, top + height, paint)
        }
    }

    private fun drawCandle(
        canvas: Canvas,
        candle: Candle,
        x: Float,
        bodyWidth: Float,
        y: (Double) -> Float,
    ) {
        val up = candle.close >= candle.open
        val color = if (up) palette.green else palette.red
        paint.color = color
        paint.strokeWidth = 1.1f
        paint.style = Paint.Style.STROKE
        canvas.drawLine(x, y(candle.high), x, y(candle.low), paint)

        val top = y(max(candle.open, candle.close))
        val bottom = y(min(candle.open, candle.close))
        paint.style = Paint.Style.FILL
        val bodyHeight = max(1.5f, bottom - top)
        canvas.drawRect(RectF(x - bodyWidth / 2f, top, x + bodyWidth / 2f, top + bodyHeight), paint)
    }

    private fun drawAverageLine(
        canvas: Canvas,
        rows: List<Candle>,
        getter: (Candle) -> Double?,
        x: (Int) -> Float,
        y: (Double) -> Float,
        color: Int,
    ) {
        paint.color = color
        paint.strokeWidth = 1.35f
        paint.style = Paint.Style.STROKE
        var started = false
        rows.forEachIndexed { index, candle ->
            val value = getter(candle) ?: return@forEachIndexed
            if (value <= 0.0) return@forEachIndexed
            if (!started) {
                canvas.save()
                paint.pathEffect = null
                canvas.restore()
                started = true
                val xx = x(index)
                val yy = y(value)
                canvas.drawPoint(xx, yy, paint)
                paint.strokeWidth = 1.35f
                android.graphics.Path().also { path ->
                    path.moveTo(xx, yy)
                    for (next in index + 1 until rows.size) {
                        val nextValue = getter(rows[next]) ?: continue
                        if (nextValue > 0.0) path.lineTo(x(next), y(nextValue))
                    }
                    canvas.drawPath(path, paint)
                }
                return
            }
        }
    }

    private fun drawLastPrice(canvas: Canvas, candle: Candle, left: Float, right: Float, y: (Double) -> Float) {
        val yy = y(candle.close)
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 1f
        paint.color = android.graphics.Color.rgb(229, 231, 235)
        paint.pathEffect = android.graphics.DashPathEffect(floatArrayOf(8f, 6f), 0f)
        canvas.drawLine(left, yy, right + dp(4), yy, paint)
        paint.pathEffect = null

        textPaint.color = android.graphics.Color.rgb(229, 231, 235)
        textPaint.textAlign = Paint.Align.RIGHT
        canvas.drawText(compactPrice(candle.close), width - dp(5).toFloat(), yy - dp(3), textPaint)
    }

    private fun drawTimeAxis(canvas: Canvas, rows: List<Candle>, left: Float, plotWidth: Float, baseline: Float) {
        textPaint.color = android.graphics.Color.rgb(154, 167, 183)
        textPaint.textAlign = Paint.Align.CENTER
        val ticks = min(5, rows.size)
        for (i in 0 until ticks) {
            val index = ((rows.size - 1) * (i / max(1f, (ticks - 1).toFloat()))).toInt().coerceIn(0, rows.lastIndex)
            val step = plotWidth / rows.size
            val xx = left + index * step + step / 2f
            val timestamp = rows[index].timestamp
            val date = if (timestamp > 100_000_000_000L) Date(timestamp) else Date(timestamp * 1000L)
            canvas.drawText(dateFormat.format(date), xx, baseline, textPaint)
        }
    }

    private fun compactPrice(value: Double): String =
        when {
            value >= 1000.0 -> "%,.0f".format(Locale.US, value)
            value >= 1.0 -> "%,.3f".format(Locale.US, value)
            else -> "%.8f".format(Locale.US, value).trimEnd('0').trimEnd('.')
        }

    private fun withAlpha(color: Int, alpha: Int): Int =
        android.graphics.Color.argb(alpha, android.graphics.Color.red(color), android.graphics.Color.green(color), android.graphics.Color.blue(color))

    private fun dp(value: Int): Int =
        (value * resources.displayMetrics.density).toInt()
}
