package com.github.nullptroma.wallenc.usecases

import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator
import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
import java.time.Duration
import java.time.Instant
import javax.crypto.spec.SecretKeySpec

data class TwoFaCodeState(
    val code: String,
    val secondsUntilRefresh: Int,
)

/** Доля прошедшего TOTP-периода [0f, 1f] для плавного progress bar. */
fun totpPeriodProgress(nowMillis: Long, periodSeconds: Int): Float {
    val period = periodSeconds.coerceAtLeast(1)
    val elapsedSec = (nowMillis / 1000.0) % period
    return (elapsedSec / period).toFloat().coerceIn(0f, 1f)
}

fun totpSecondsUntilRefresh(nowMillis: Long, periodSeconds: Int): Int {
    val period = periodSeconds.coerceAtLeast(1)
    return (period - ((nowMillis / 1000L) % period)).toInt().coerceAtLeast(0)
}

fun buildTwoFaCodeState(token: TwoFaTokenRecord, nowMillis: Long): TwoFaCodeState? {
    val period = token.periodSeconds.coerceAtLeast(1)
    val remaining = totpSecondsUntilRefresh(nowMillis, period)
    val code = generateTotpCode(
        secret = token.secret,
        nowMillis = nowMillis,
        digits = token.digits.coerceIn(6, 8),
        periodSeconds = token.periodSeconds.coerceAtLeast(1),
        algorithm = token.algorithm,
    ) ?: return null
    return TwoFaCodeState(
        code = code,
        secondsUntilRefresh = remaining,
    )
}

private fun generateTotpCode(
    secret: String,
    nowMillis: Long,
    digits: Int,
    periodSeconds: Int,
    algorithm: String,
): String? {
    val key = decodeBase32(secret) ?: return null
    val macAlgorithm = when (algorithm.trim().uppercase().replace("-", "").replace("_", "")) {
        "SHA256" -> "HmacSHA256"
        "SHA512" -> "HmacSHA512"
        else -> "HmacSHA1"
    }
    return runCatching {
        val generator = TimeBasedOneTimePasswordGenerator(
            Duration.ofSeconds(periodSeconds.toLong()),
            digits,
            macAlgorithm,
        )
        val secretKey = SecretKeySpec(key, macAlgorithm)
        val otp = generator.generateOneTimePassword(
            secretKey,
            Instant.ofEpochMilli(nowMillis),
        )
        otp.toString().padStart(digits, '0')
    }.getOrNull()
}

private fun decodeBase32(input: String): ByteArray? {
    val clean = input
        .trim()
        .replace(" ", "")
        .replace("-", "")
        .replace("=", "")
        .uppercase()
    if (clean.isEmpty()) return null

    var buffer = 0
    var bitsLeft = 0
    val out = ArrayList<Byte>(clean.length * 5 / 8)
    for (ch in clean) {
        val value = BASE32_ALPHABET.indexOf(ch)
        if (value < 0) return null
        buffer = (buffer shl 5) or value
        bitsLeft += 5
        if (bitsLeft >= 8) {
            out.add(((buffer shr (bitsLeft - 8)) and 0xFF).toByte())
            bitsLeft -= 8
        }
    }
    return out.toByteArray()
}

private const val BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
