package de.tsschulz.tt_tagebuch.app.stats

import de.tsschulz.tt_tagebuch.shared.api.models.TrainingStatsDay
import de.tsschulz.tt_tagebuch.shared.api.models.TrainingStatsMember
import de.tsschulz.tt_tagebuch.shared.api.models.TrainingStatsMemberDistribution
import de.tsschulz.tt_tagebuch.shared.api.models.TrainingStatsMonthlyTrend
import de.tsschulz.tt_tagebuch.shared.api.models.TrainingStatsWeekdayBucket
import java.time.DayOfWeek
import java.time.LocalDate
import java.util.Locale

data class FilteredOverview(
    val totalParticipants: Int,
    val averageParticipants: Double,
    val attendanceRate: Double,
    val bestTrainingDay: TrainingStatsDay?,
)

data class GroupPerformanceRow(
    val name: String,
    val memberCount: Int,
    val averageParticipations12Months: Double,
    val participationRate: Double,
)

data class AgeGroupStatRow(
    val label: String,
    val memberCount: Int,
    val averageParticipations12Months: Double,
)

private data class GroupAgg(
    val name: String,
    var memberCount: Int = 0,
    var totalParticipations12Months: Int = 0,
    var participationRateSum: Double = 0.0,
)

private val weekdayDe = listOf("Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag")

object TrainingStatsDerived {

    fun parseLocalDate(dateStr: String?): LocalDate? {
        if (dateStr.isNullOrBlank()) return null
        val prefix = dateStr.trim().take(10)
        return runCatching { LocalDate.parse(prefix) }.getOrNull()
    }

    fun jsWeekdayIndex(dateStr: String): Int {
        val ld = parseLocalDate(dateStr) ?: return 0
        return when (ld.dayOfWeek) {
            DayOfWeek.SUNDAY -> 0
            DayOfWeek.MONDAY -> 1
            DayOfWeek.TUESDAY -> 2
            DayOfWeek.WEDNESDAY -> 3
            DayOfWeek.THURSDAY -> 4
            DayOfWeek.FRIDAY -> 5
            DayOfWeek.SATURDAY -> 6
        }
    }

    fun formatDateGerman(dateStr: String?): String {
        val ld = parseLocalDate(dateStr) ?: return "-"
        return String.format(Locale.GERMAN, "%02d.%02d.%d", ld.dayOfMonth, ld.monthValue, ld.year)
    }

    fun weekdayGerman(dateStr: String): String = weekdayDe.getOrElse(jsWeekdayIndex(dateStr)) { "" }

    fun ageFromBirthDate(birthDate: String?): Int? {
        val bd = parseLocalDate(birthDate) ?: return null
        val now = LocalDate.now()
        var age = now.year - bd.year
        if (now.monthValue < bd.monthValue || (now.monthValue == bd.monthValue && now.dayOfMonth < bd.dayOfMonth)) {
            age--
        }
        return age
    }

    fun trainingDaysByWeekday(trainingDays: List<TrainingStatsDay>, selectedWeekday: String): List<TrainingStatsDay> {
        if (selectedWeekday == "all") return trainingDays
        val w = selectedWeekday.toIntOrNull() ?: return trainingDays
        return trainingDays.filter { jsWeekdayIndex(it.date) == w }
    }

    /** Wie Vue: Teilnehmermenge für den gewählten Trainingstag aus der vollen `trainingDays`-Liste. */
    fun trainingDayParticipantIds(allTrainingDays: List<TrainingStatsDay>, trainingDayId: String): Set<Int>? {
        if (trainingDayId == "all") return null
        val day = allTrainingDays.find { it.id.toString() == trainingDayId } ?: return emptySet()
        return day.participants.map { it.id }.toSet()
    }

    fun filteredMembers(
        members: List<TrainingStatsMember>,
        selectedTrainingGroup: String,
        participantIds: Set<Int>?,
    ): List<TrainingStatsMember> {
        var m = members
        if (selectedTrainingGroup != "all") {
            m = m.filter { mem -> mem.trainingGroups.any { it.id.toString() == selectedTrainingGroup } }
        }
        if (participantIds != null) {
            m = m.filter { participantIds.contains(it.id) }
        }
        return m
    }

    fun filteredTrainingDays(
        daysByWeekday: List<TrainingStatsDay>,
        selectedTrainingDay: String,
    ): List<TrainingStatsDay> {
        if (selectedTrainingDay == "all") return daysByWeekday
        return daysByWeekday.filter { it.id.toString() == selectedTrainingDay }
    }

    fun filteredOverview(
        filteredTrainingDays: List<TrainingStatsDay>,
        filteredMembers: List<TrainingStatsMember>,
    ): FilteredOverview {
        val totalParticipants = filteredTrainingDays.sumOf { it.participantCount }
        val averageParticipants = if (filteredTrainingDays.isNotEmpty()) {
            totalParticipants.toDouble() / filteredTrainingDays.size
        } else {
            0.0
        }
        val denom = filteredMembers.size
        val attendanceRate = if (denom > 0 && filteredTrainingDays.isNotEmpty()) {
            (totalParticipants.toDouble() / (denom * filteredTrainingDays.size)) * 100.0
        } else {
            0.0
        }
        val bestTrainingDay = filteredTrainingDays.maxByOrNull { it.participantCount }
        return FilteredOverview(
            totalParticipants = totalParticipants,
            averageParticipants = averageParticipants,
            attendanceRate = attendanceRate,
            bestTrainingDay = bestTrainingDay,
        )
    }

    fun filteredMonthlyTrend(filteredTrainingDays: List<TrainingStatsDay>): List<TrainingStatsMonthlyTrend> {
        data class Acc(var label: String, var trainingCount: Int, var participantCount: Int)
        val map = linkedMapOf<String, Acc>()
        for (day in filteredTrainingDays) {
            val ld = parseLocalDate(day.date) ?: continue
            val key = "${ld.year}-${ld.monthValue.toString().padStart(2, '0')}"
            val label = "${ld.monthValue.toString().padStart(2, '0')}.${ld.year}"
            val acc = map.getOrPut(key) { Acc(label, 0, 0) }
            acc.trainingCount += 1
            acc.participantCount += day.participantCount
        }
        return map.entries
            .sortedBy { it.key }
            .map { (key, acc) ->
                val avg = if (acc.trainingCount > 0) acc.participantCount.toDouble() / acc.trainingCount else 0.0
                TrainingStatsMonthlyTrend(
                    key = key,
                    label = acc.label,
                    trainingCount = acc.trainingCount,
                    participantCount = acc.participantCount,
                    averageParticipants = avg,
                )
            }
    }

    fun filteredWeekdayStats(filteredTrainingDays: List<TrainingStatsDay>): List<TrainingStatsWeekdayBucket> {
        val acc = mutableMapOf<Int, IntArray>()
        for (day in filteredTrainingDays) {
            val idx = jsWeekdayIndex(day.date)
            val a = acc.getOrPut(idx) { intArrayOf(0, 0) }
            a[0] += 1
            a[1] += day.participantCount
        }
        return acc.entries
            .sortedBy { it.key }
            .map { (idx, ar) ->
                val tc = ar[0]
                val pc = ar[1]
                TrainingStatsWeekdayBucket(
                    weekday = weekdayDe[idx],
                    weekdayIndex = idx,
                    trainingCount = tc,
                    participantCount = pc,
                    averageParticipants = if (tc > 0) pc.toDouble() / tc else 0.0,
                )
            }
    }

    fun filteredMemberDistribution(members: List<TrainingStatsMember>): TrainingStatsMemberDistribution =
        TrainingStatsMemberDistribution(
            highlyActive = members.count { it.participationRate12Months >= 0.75 },
            regular = members.count { it.participationRate12Months >= 0.4 && it.participationRate12Months < 0.75 },
            occasional = members.count { it.participationRate12Months > 0 && it.participationRate12Months < 0.4 },
            inactive = members.count { it.participation12Months == 0 },
        )

    fun groupPerformance(filteredMembers: List<TrainingStatsMember>): List<GroupPerformanceRow> {
        val groups = linkedMapOf<String, GroupAgg>()
        for (member in filteredMembers) {
            val memberGroups = member.trainingGroups
            if (memberGroups.isEmpty()) {
                val e = groups.getOrPut("ohne-gruppe") { GroupAgg("Ohne Trainingsgruppe") }
                e.memberCount += 1
                e.totalParticipations12Months += member.participation12Months
                e.participationRateSum += member.participationRate12Months
            } else {
                for (g in memberGroups) {
                    val key = g.id.toString()
                    val e = groups.getOrPut(key) { GroupAgg(g.name) }
                    e.memberCount += 1
                    e.totalParticipations12Months += member.participation12Months
                    e.participationRateSum += member.participationRate12Months
                }
            }
        }
        return groups.values
            .map { entry ->
                val mc = entry.memberCount
                GroupPerformanceRow(
                    name = entry.name,
                    memberCount = mc,
                    averageParticipations12Months = if (mc > 0) entry.totalParticipations12Months.toDouble() / mc else 0.0,
                    participationRate = if (mc > 0) (entry.participationRateSum / mc) * 100.0 else 0.0,
                )
            }
            .sortedByDescending { it.averageParticipations12Months }
    }

    fun ageGroupStats(filteredMembers: List<TrainingStatsMember>): List<AgeGroupStatRow> {
        data class Bucket(val label: String, val match: (Int?) -> Boolean, var memberCount: Int = 0, var totalP12: Int = 0)
        val buckets = listOf(
            Bucket("U7", { a -> a != null && a <= 6 }),
            Bucket("U9", { a -> a != null && a in 7..8 }),
            Bucket("U11", { a -> a != null && a in 9..10 }),
            Bucket("U13", { a -> a != null && a in 11..12 }),
            Bucket("U15", { a -> a != null && a in 13..14 }),
            Bucket("U19", { a -> a != null && a in 15..18 }),
            Bucket("Erwachsene", { a -> a != null && a >= 19 }),
        )
        for (member in filteredMembers) {
            val age = ageFromBirthDate(member.birthDate)
            val bucket = buckets.find { it.match(age) } ?: continue
            bucket.memberCount += 1
            bucket.totalP12 += member.participation12Months
        }
        return buckets
            .map {
                val mc = it.memberCount
                AgeGroupStatRow(
                    label = it.label,
                    memberCount = mc,
                    averageParticipations12Months = if (mc > 0) it.totalP12.toDouble() / mc else 0.0,
                )
            }
    }

    fun trainingGroupOptions(members: List<TrainingStatsMember>): List<Pair<String, String>> {
        val map = linkedMapOf<String, String>()
        for (m in members) {
            for (g in m.trainingGroups) {
                map.putIfAbsent(g.id.toString(), g.name)
            }
        }
        return map.entries.map { it.key to it.value }.sortedWith(compareBy { it.second.lowercase(Locale.GERMAN) })
    }

    fun sortedMembers(
        members: List<TrainingStatsMember>,
        sortField: String,
        sortDirectionAsc: Boolean,
    ): List<TrainingStatsMember> =
        when (sortField) {
            "name" -> members.sortedWith { a, b ->
                val na = "${a.firstName} ${a.lastName}".lowercase(Locale.GERMAN)
                val nb = "${b.firstName} ${b.lastName}".lowercase(Locale.GERMAN)
                val c = na.compareTo(nb)
                if (sortDirectionAsc) c else -c
            }
            "participation12Months" -> numericSort(members, sortDirectionAsc) { it.participation12Months }
            "participation3Months" -> numericSort(members, sortDirectionAsc) { it.participation3Months }
            "participationTotal" -> numericSort(members, sortDirectionAsc) { it.participationTotal }
            "lastTrainingTs" -> numericSort(members, sortDirectionAsc) { it.lastTrainingTs }
            else -> members
        }

    private fun numericSort(
        members: List<TrainingStatsMember>,
        asc: Boolean,
        selector: (TrainingStatsMember) -> Number,
    ): List<TrainingStatsMember> =
        if (asc) members.sortedBy { selector(it).toDouble() } else members.sortedByDescending { selector(it).toDouble() }

    fun buildMembersCsv(members: List<TrainingStatsMember>): String {
        fun esc(s: String): String {
            val needs = s.contains(';') || s.contains('"') || s.contains('\n') || s.contains('\r')
            if (!needs) return s
            return "\"" + s.replace("\"", "\"\"") + "\""
        }
        val header = listOf(
            "Vorname", "Nachname", "TTR", "QTTR", "Geburtsdatum", "Teilnahmen 12M", "Teilnahmen 3M", "Teilnahmen gesamt",
            "Quote 12M", "Letztes Training", "Trainingsgruppen",
        ).joinToString(";")
        val lines = members.map { m ->
            val groups = m.trainingGroups.joinToString(", ") { it.name }
            listOf(
                esc(m.firstName),
                esc(m.lastName),
                m.ttr?.toString() ?: "",
                m.qttr?.toString() ?: "",
                formatDateGerman(m.birthDate).takeUnless { it == "-" } ?: "",
                m.participation12Months.toString(),
                m.participation3Months.toString(),
                m.participationTotal.toString(),
                String.format(Locale.GERMAN, "%.4f", m.participationRate12Months),
                formatDateGerman(m.lastTraining).takeUnless { it == "-" } ?: "",
                esc(groups),
            ).joinToString(";")
        }
        return (sequenceOf(header) + lines.asSequence()).joinToString("\n")
    }
}
