package com.anypreta.bountyboard.ui.developer

import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Analytics
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.anypreta.bountyboard.data.AppContainer
import com.anypreta.bountyboard.di.LocalAppContainer
import com.anypreta.bountyboard.di.containerVMFactory
import com.anypreta.bountyboard.ui.components.BBCard
import com.anypreta.bountyboard.ui.components.BountyChip
import com.anypreta.bountyboard.ui.components.EmptyState
import com.anypreta.bountyboard.ui.components.Kicker
import com.anypreta.bountyboard.ui.components.ScreenState
import com.anypreta.bountyboard.ui.theme.BB
import com.anypreta.bountyboard.ui.theme.Fraunces
import com.anypreta.bountyboard.ui.theme.SplineMono
import com.anypreta.bountyboard.ui.util.formatMinutes
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import kotlinx.coroutines.launch
import kotlin.math.roundToInt

class DeveloperMetricsViewModel(private val container: AppContainer) : ViewModel() {

    var loading by mutableStateOf(true)
        private set
    var error by mutableStateOf<String?>(null)
        private set
    var metrics by mutableStateOf<JsonObject?>(null)
        private set

    init {
        load()
    }

    fun load() {
        loading = true; error = null
        viewModelScope.launch {
            try {
                metrics = container.repo.call { developerMetrics() }
            } catch (e: Exception) {
                error = e.message
            } finally {
                loading = false
            }
        }
    }
}

// ---- Defensive JSON helpers ----

private fun JsonObject.numberOrNull(key: String): Double? =
    get(key)?.takeIf { it.isJsonPrimitive && (it.asJsonPrimitive.isNumber || it.asJsonPrimitive.asString.toDoubleOrNull() != null) }
        ?.let { runCatching { it.asDouble }.getOrNull() }

private fun JsonObject.arrayOrNull(key: String): JsonArray? =
    get(key)?.takeIf { it.isJsonArray }?.asJsonArray

private fun JsonObject.stringOrNull(key: String): String? =
    get(key)?.takeIf { it.isJsonPrimitive }?.let { runCatching { it.asString }.getOrNull() }

private fun humanize(key: String): String {
    val spaced = key
        .replace(Regex("([a-z])([A-Z])"), "$1 $2")
        .replace('_', ' ')
        .trim()
    return spaced.replaceFirstChar { it.uppercase() }
}

private data class StatCard(val label: String, val value: String)

@Composable
fun DeveloperMetricsScreen() {
    val container = LocalAppContainer.current
    val vm: DeveloperMetricsViewModel = viewModel(factory = containerVMFactory(container) { DeveloperMetricsViewModel(it) })
    val p = BB.colors

    ScreenState(loading = vm.loading, error = vm.error, onRetry = { vm.load() }) {
        val obj = vm.metrics
        if (obj == null || obj.entrySet().isEmpty()) {
            EmptyState(
                title = "No metrics yet",
                subtitle = "Complete a few bounties and your earnings and stats will show up here.",
                icon = Icons.Outlined.Analytics,
            )
            return@ScreenState
        }

        // Known fields
        val totalBounty = obj.numberOrNull("totalBounty")
        val knownKeys = mutableSetOf("totalBounty", "series", "byCustomer")

        val cards = mutableListOf<StatCard>()
        fun addKnown(key: String, label: String, format: (Double) -> String) {
            obj.numberOrNull(key)?.let { cards.add(StatCard(label, format(it))); knownKeys.add(key) }
            knownKeys.add(key)
        }
        addKnown("tasksCompleted", "Tasks completed") { it.roundToInt().toString() }
        addKnown("approvalRate", "Approval rate") { rate ->
            val pct = if (rate <= 1.0) rate * 100 else rate
            "${pct.roundToInt()}%"
        }
        (obj.numberOrNull("avgLeadTimeHours") ?: obj.numberOrNull("avgLeadTime"))?.let {
            cards.add(StatCard("Avg lead time", formatHours(it)))
        }
        knownKeys.add("avgLeadTimeHours"); knownKeys.add("avgLeadTime")
        obj.numberOrNull("timeLoggedMinutes")?.let {
            cards.add(StatCard("Time logged", formatMinutes(it.roundToInt())))
        }
        knownKeys.add("timeLoggedMinutes")
        addKnown("boardDepth", "Board depth") { it.roundToInt().toString() }

        // Any other top-level numeric fields
        obj.entrySet().forEach { (key, value) ->
            if (key in knownKeys) return@forEach
            if (value != null && value.isJsonPrimitive && value.asJsonPrimitive.isNumber) {
                val n = runCatching { value.asDouble }.getOrNull() ?: return@forEach
                val rendered = if (n == n.roundToInt().toDouble()) n.roundToInt().toString()
                else String.format(java.util.Locale.US, "%.2f", n)
                cards.add(StatCard(humanize(key), rendered))
            }
        }

        val series = parseSeries(obj.arrayOrNull("series"))
        val byCustomer = parseByCustomer(obj.arrayOrNull("byCustomer"))

        val nothing = totalBounty == null && cards.isEmpty() && series.isEmpty() && byCustomer.isEmpty()
        if (nothing) {
            EmptyState(
                title = "No metrics yet",
                subtitle = "Complete a few bounties and your earnings and stats will show up here.",
                icon = Icons.Outlined.Analytics,
            )
            return@ScreenState
        }

        LazyColumn(
            contentPadding = PaddingValues(16.dp),
            verticalArrangement = Arrangement.spacedBy(14.dp),
        ) {
            // Hero
            item(key = "hero") {
                BBCard(modifier = Modifier.fillMaxWidth(), topStrip = true) {
                    Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
                        Kicker("Total bounty earned")
                        BountyChip(totalBounty ?: 0.0, large = true)
                    }
                }
            }

            // Stat grid (2 columns)
            if (cards.isNotEmpty()) {
                val rows = cards.chunked(2).withIndex().toList()
                items(rows, key = { "row-${it.index}" }) { (_, rowCards) ->
                    Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(14.dp)) {
                        rowCards.forEach { card ->
                            StatCardView(card, Modifier.weight(1f))
                        }
                        if (rowCards.size == 1) Spacer(Modifier.weight(1f))
                    }
                }
            }

            // Bar chart
            if (series.isNotEmpty()) {
                item(key = "series") {
                    BBCard(modifier = Modifier.fillMaxWidth()) {
                        Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
                            Kicker("Earnings over time")
                            BarChart(series)
                        }
                    }
                }
            }

            // By customer
            if (byCustomer.isNotEmpty()) {
                item(key = "byCustomer") {
                    BBCard(modifier = Modifier.fillMaxWidth()) {
                        Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
                            Kicker("By project")
                            byCustomer.forEach { row ->
                                Row(
                                    Modifier.fillMaxWidth(),
                                    verticalAlignment = Alignment.CenterVertically,
                                    horizontalArrangement = Arrangement.spacedBy(12.dp),
                                ) {
                                    Text(
                                        row.label.ifBlank { "—" },
                                        style = MaterialTheme.typography.bodyMedium,
                                        color = p.text,
                                        maxLines = 1,
                                        overflow = TextOverflow.Ellipsis,
                                        modifier = Modifier.weight(1f),
                                    )
                                    BountyChip(row.totalBounty)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

@Composable
private fun StatCardView(card: StatCard, modifier: Modifier = Modifier) {
    val p = BB.colors
    BBCard(modifier = modifier) {
        Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
            Text(
                card.value,
                fontFamily = Fraunces,
                fontWeight = FontWeight.Bold,
                style = MaterialTheme.typography.displayMedium,
                color = p.text,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
            Text(
                card.label.uppercase(),
                style = MaterialTheme.typography.labelSmall,
                fontFamily = SplineMono,
                color = p.muted,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
            )
        }
    }
}

@Composable
private fun BarChart(series: List<SeriesPoint>) {
    val p = BB.colors
    val max = series.maxOf { it.amount }.coerceAtLeast(0.0001)
    val maxHeight = 120.dp
    Row(
        Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
        horizontalArrangement = Arrangement.spacedBy(10.dp),
        verticalAlignment = Alignment.Bottom,
    ) {
        series.forEach { point ->
            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(6.dp),
            ) {
                val frac = (point.amount / max).toFloat().coerceIn(0f, 1f)
                val barHeight = (maxHeight * frac).coerceAtLeast(4.dp)
                Box(
                    Modifier
                        .width(28.dp)
                        .height(barHeight)
                        .clip(RoundedCornerShape((p.radius).dp))
                        .background(p.accent),
                )
                Text(
                    point.bucket,
                    style = MaterialTheme.typography.labelSmall,
                    fontFamily = SplineMono,
                    color = p.muted,
                    textAlign = TextAlign.Center,
                    maxLines = 1,
                    modifier = Modifier.width(44.dp),
                )
            }
        }
    }
}

// ---- Parsing of nested shapes ----

private data class SeriesPoint(val bucket: String, val amount: Double, val count: Int)
private data class CustomerRow(val label: String, val totalBounty: Double)

private fun parseSeries(arr: JsonArray?): List<SeriesPoint> {
    if (arr == null) return emptyList()
    val out = mutableListOf<SeriesPoint>()
    arr.forEach { el ->
        val o = el.takeIf { it.isJsonObject }?.asJsonObject ?: return@forEach
        val bucket = o.stringOrNull("bucket") ?: o.stringOrNull("week") ?: ""
        val amount = o.numberOrNull("amount") ?: o.numberOrNull("total") ?: 0.0
        val count = o.numberOrNull("count")?.roundToInt() ?: 0
        out.add(SeriesPoint(bucket, amount, count))
    }
    return out
}

private fun parseByCustomer(arr: JsonArray?): List<CustomerRow> {
    if (arr == null) return emptyList()
    val out = mutableListOf<CustomerRow>()
    arr.forEach { el ->
        val o = el.takeIf { it.isJsonObject }?.asJsonObject ?: return@forEach
        val label = o.stringOrNull("label") ?: o.stringOrNull("key") ?: ""
        val total = o.numberOrNull("totalBounty") ?: 0.0
        out.add(CustomerRow(label, total))
    }
    return out
}

private fun formatHours(hours: Double): String {
    if (hours <= 0) return "0h"
    return if (hours == hours.roundToInt().toDouble()) "${hours.roundToInt()}h"
    else String.format(java.util.Locale.US, "%.1fh", hours)
}
