package com.anypreta.bountyboard.ui.consultant

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.QueryStats
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.EmptyState
import com.anypreta.bountyboard.ui.components.Hairline
import com.anypreta.bountyboard.ui.components.ScreenState
import com.anypreta.bountyboard.ui.components.SectionLabel
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.formatBounty
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

// ---------------------------------------------------------------------------
// Defensive JsonObject readers
// ---------------------------------------------------------------------------

private fun JsonObject.num(key: String): Double? {
    val el = get(key) ?: return null
    if (el.isJsonNull) return null
    return try {
        if (el.isJsonPrimitive) el.asDouble else null
    } catch (_: Exception) {
        null
    }
}

private fun JsonObject.str(key: String): String? {
    val el = get(key) ?: return null
    if (el.isJsonNull) return null
    return try {
        if (el.isJsonPrimitive) el.asString else null
    } catch (_: Exception) {
        null
    }
}

private fun JsonObject.arr(key: String): JsonArray? {
    val el = get(key) ?: return null
    return if (el.isJsonArray) el.asJsonArray else null
}

/** Known scalar metric keys we render with bespoke labels/formatting, in priority order. */
private val KNOWN_KEYS = listOf(
    "totalBounty", "tasksCompleted", "approvalRate", "avgLeadTimeHours",
    "boardDepth", "timeLoggedMinutes",
)

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

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

private fun statFor(key: String, v: Double): Stat = when (key) {
    "totalBounty" -> Stat("Total bounty", "◈ ${formatBounty(v)}")
    "tasksCompleted" -> Stat("Tasks completed", v.roundToInt().toString())
    "approvalRate" -> {
        val pct = if (v <= 1.0) v * 100 else v
        Stat("Approval rate", "${formatBounty(pct)}%")
    }
    "avgLeadTimeHours" -> Stat("Avg lead time", "${formatBounty(v)}h")
    "boardDepth" -> Stat("Board depth", v.roundToInt().toString())
    "timeLoggedMinutes" -> Stat("Time logged", formatMinutes(v.roundToInt()))
    else -> Stat(humanize(key), if (v == v.toLong().toDouble()) v.toLong().toString() else formatBounty(v))
}

private data class Breakdown(val label: String, val totalBounty: Double, val tasksCompleted: Int)

private fun parseBreakdowns(arr: JsonArray?): List<Breakdown> {
    if (arr == null) return emptyList()
    return arr.mapNotNull { el ->
        if (!el.isJsonObject) return@mapNotNull null
        val o = el.asJsonObject
        val label = o.str("label") ?: o.str("key") ?: o.str("name") ?: return@mapNotNull null
        Breakdown(
            label = label,
            totalBounty = o.num("totalBounty") ?: 0.0,
            tasksCompleted = (o.num("tasksCompleted") ?: 0.0).roundToInt(),
        )
    }
}

private data class SeriesPoint(val bucket: String, val amount: Double)

private fun parseSeries(arr: JsonArray?): List<SeriesPoint> {
    if (arr == null) return emptyList()
    return arr.mapNotNull { el ->
        if (!el.isJsonObject) return@mapNotNull null
        val o = el.asJsonObject
        val bucket = o.str("bucket") ?: o.str("label") ?: o.str("date") ?: ""
        val amount = o.num("amount") ?: o.num("totalBounty") ?: o.num("count") ?: 0.0
        SeriesPoint(bucket, amount)
    }
}

// ---------------------------------------------------------------------------
// Screen
// ---------------------------------------------------------------------------

@Composable
fun ConsultantMetricsScreen() {
    val container = LocalAppContainer.current
    val vm: ConsultantMetricsViewModel =
        viewModel(factory = containerVMFactory(container) { ConsultantMetricsViewModel(it) })

    ScreenState(loading = vm.loading, error = vm.error, onRetry = { vm.load() }) {
        val data = vm.data
        if (data == null) {
            EmptyState(
                title = "No metrics yet",
                subtitle = "Aggregated bounty and throughput numbers appear once your projects have activity.",
                icon = Icons.Outlined.QueryStats,
            )
            return@ScreenState
        }

        // Collect numeric top-level fields (known first, then any extras), skipping
        // structural arrays / objects.
        val extraKeys = data.keySet().filter { it !in KNOWN_KEYS && data.num(it) != null }
        val statKeys = KNOWN_KEYS.filter { data.num(it) != null } + extraKeys
        val stats = statKeys.map { statFor(it, data.num(it)!!) }

        val byDeveloper = parseBreakdowns(data.arr("byDeveloper"))
        val byCustomer = parseBreakdowns(data.arr("byCustomer"))
        val series = parseSeries(data.arr("series"))

        if (stats.isEmpty() && byDeveloper.isEmpty() && byCustomer.isEmpty() && series.isEmpty()) {
            EmptyState(
                title = "No metrics yet",
                subtitle = "Aggregated bounty and throughput numbers appear once your projects have activity.",
                icon = Icons.Outlined.QueryStats,
            )
            return@ScreenState
        }

        Column(
            Modifier
                .fillMaxWidth()
                .verticalScroll(rememberScrollState())
                .padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            data.num("totalBounty")?.let { Hero(it) }

            // Drop totalBounty from the grid since it's in the hero.
            val gridStats = stats.filterNot { it.label == "Total bounty" }
            if (gridStats.isNotEmpty()) {
                StatGrid(gridStats)
            }

            if (series.isNotEmpty()) {
                SectionLabel("Over time")
                BarChart(series)
            }

            if (byDeveloper.isNotEmpty()) {
                SectionLabel("By developer")
                BreakdownList(byDeveloper)
            }

            if (byCustomer.isNotEmpty()) {
                SectionLabel("By project")
                BreakdownList(byCustomer)
            }
        }
    }
}

@Composable
private fun Hero(totalBounty: Double) {
    val p = BB.colors
    BBCard(modifier = Modifier.fillMaxWidth(), topStrip = true) {
        Column(
            Modifier.fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(6.dp),
        ) {
            SectionLabel("Total bounty awarded")
            Text(
                "◈ ${formatBounty(totalBounty)}",
                fontFamily = Fraunces,
                fontWeight = FontWeight.Bold,
                color = p.accent,
                style = MaterialTheme.typography.displayLarge,
            )
        }
    }
}

@Composable
private fun StatGrid(stats: List<Stat>) {
    Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
        stats.chunked(2).forEach { row ->
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
                row.forEach { s ->
                    StatCard(s, modifier = Modifier.weight(1f))
                }
                if (row.size == 1) Box(Modifier.weight(1f))
            }
        }
    }
}

@Composable
private fun StatCard(stat: Stat, modifier: Modifier = Modifier) {
    val p = BB.colors
    BBCard(modifier = modifier) {
        Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
            SectionLabel(stat.label)
            Text(
                stat.value,
                fontFamily = Fraunces,
                fontWeight = FontWeight.Bold,
                color = p.text,
                style = MaterialTheme.typography.headlineMedium,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
        }
    }
}

@Composable
private fun BreakdownList(items: List<Breakdown>) {
    val p = BB.colors
    BBCard(modifier = Modifier.fillMaxWidth()) {
        Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
            items.forEachIndexed { i, b ->
                Row(
                    Modifier.fillMaxWidth(),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.SpaceBetween,
                ) {
                    Text(
                        b.label,
                        style = MaterialTheme.typography.bodyMedium,
                        color = p.text,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis,
                        modifier = Modifier.weight(1f),
                    )
                    Row(
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.spacedBy(12.dp),
                    ) {
                        Text(
                            "${b.tasksCompleted} done",
                            style = MaterialTheme.typography.labelSmall,
                            color = p.muted,
                        )
                        Text(
                            "◈ ${formatBounty(b.totalBounty)}",
                            fontFamily = SplineMono,
                            fontWeight = FontWeight.Bold,
                            style = MaterialTheme.typography.bodyMedium,
                            color = p.accent,
                        )
                    }
                }
                if (i < items.lastIndex) Hairline()
            }
        }
    }
}

@Composable
private fun BarChart(series: List<SeriesPoint>) {
    val p = BB.colors
    val max = series.maxOfOrNull { it.amount }?.takeIf { it > 0 } ?: 1.0
    BBCard(modifier = Modifier.fillMaxWidth()) {
        Row(
            Modifier.fillMaxWidth().height(140.dp),
            verticalAlignment = Alignment.Bottom,
            horizontalArrangement = Arrangement.spacedBy(6.dp),
        ) {
            series.takeLast(16).forEach { pt ->
                val frac = (pt.amount / max).toFloat().coerceIn(0.02f, 1f)
                Column(
                    Modifier.weight(1f),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.spacedBy(4.dp),
                ) {
                    Box(
                        Modifier.fillMaxWidth().weight(1f),
                        contentAlignment = Alignment.BottomCenter,
                    ) {
                        Box(
                            Modifier
                                .fillMaxWidth()
                                .fillMaxHeight(frac)
                                .clip(RoundedCornerShape(p.radius.dp))
                                .background(p.accent)
                                .border(1.dp, p.border, RoundedCornerShape(p.radius.dp)),
                        )
                    }
                    Text(
                        pt.bucket.takeLast(5),
                        style = MaterialTheme.typography.labelSmall,
                        color = p.muted,
                        maxLines = 1,
                        textAlign = TextAlign.Center,
                    )
                }
            }
        }
    }
}

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

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

    init {
        load()
    }

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