package com.korancrew.brizzi.hce

/**
 * Router utama untuk semua command APDU yang masuk.
 *
 * Menjaga flow:
 * - sesi AID (AID1, AID3),
 * - fase autentikasi,
 * - transaksi debit/credit/log/last transaction,
 * - commit/abort,
 * - serta proteksi keamanan (timeout, replay, malformed validation).
 */
class BrizziApduRouter(
    private val routingAidHex: String,
    private var card: BrizziCard = BrizziCard(),
    private val session: BrizziSession = BrizziSession(),
    private val onCardUpdated: ((BrizziCard) -> Unit)? = null,
    private val onSecurityEvent: ((String) -> Unit)? = null,
) {
    companion object {
        private const val MAX_APDU_HEX_CHARS = 2048
        private const val ALLOWED_MAX_TRANSACTION_AMOUNT = 9_900_000
        private const val ALLOWED_MAX_BALANCE = 9_999_999
        private const val SESSION_TIMEOUT_MS = 120_000L
        private const val REPLAY_WINDOW_MS = 800L
        private const val READER_AID1_HEX = "A0000000180F0000018001"
        private const val READER_AID1_ALT_HEX = "D4100000030001"
        private const val READER_AID3_HEX = "D3600000030003"
        private const val PPSE_AID_HEX = "325041592E5359532E4444463031"
        private const val AID1_HEX = "5A00000301000000"
        private const val AID3_HEX = "5A00000303000000"

        private val BALANCE_READ_DATA_TAGS = setOf(
            "9F7900",
            "9F7902",
            "9F7904",
            "9F7906",
            "9F7908",
            "5F36",
            "5A",
            "9F79",
        )

        private val HISTORY_READ_DATA_TAGS = setOf(
            "9F4F00",
            "9F4F",
            "8A",
            "8C",
            "57",
            "9F6F",
            "9F6A",
            "FF",
            "57",
            "8F",
        )
    }

    private val routingAidHexes = routingAidHex
        .split(",")
        .map { it.trim().uppercase() }
        .filter { it.isNotEmpty() }

    fun snapshot(): BrizziCardSnapshot = BrizziCardSnapshot(
        balance = currentBalance(),
        lastTransactionDateHex = currentLastTransactionDateHex(),
        akumDebetHex = currentAkumDebetHex(),
        logs = card.logs,
        selectedAid = session.selectedAid,
        authenticated = session.authenticated,
        pendingTransaction = session.pendingTransaction,
    )

    /**
     * Proses 1 command secara berurutan:
     * 1) cek timeout sesi,
     * 2) parse APDU,
     * 3) cek replay untuk command sensitif,
     * 4) jalankan command handler.
     */
    @Synchronized
    fun handle(commandApdu: ByteArray): ByteArray {
        val now = System.currentTimeMillis()
        if (session.isTimedOut(now, SESSION_TIMEOUT_MS)) {
            session.reset()
            onSecurityEvent?.invoke("SESSION_TIMEOUT")
        }
        session.touch(now)
        if (commandApdu.isEmpty()) {
            onSecurityEvent?.invoke("APDU_EMPTY")
            return BrizziResponse.wrongLength
        }
        if (commandApdu.size > 1024) {
            onSecurityEvent?.invoke("APDU_TOO_LARGE")
            return BrizziResponse.wrongLength
        }
        val parsed = try {
            ApduParser.parse(commandApdu)
        } catch (_: ApduParseException) {
            onSecurityEvent?.invoke("APDU_PARSE_FAILED")
            return BrizziResponse.wrongLength
        }
        val hex = parsed.rawHex
        if (hex.length > MAX_APDU_HEX_CHARS) {
            onSecurityEvent?.invoke("APDU_HEX_TOO_LONG")
            return BrizziResponse.wrongLength
        }
        if (isReplaySensitiveHex(hex) && session.isReplay(REPLAY_WINDOW_MS, hex, now)) {
            onSecurityEvent?.invoke("REPLAY_DETECTED")
            return BrizziResponse.conditionsNotSatisfied
        }
        return try {
            handleParsed(parsed)
        } catch (_: IndexOutOfBoundsException) {
            onSecurityEvent?.invoke("APDU_HANDLE_INDEX_ERROR")
            BrizziResponse.wrongLength
        } catch (_: ConditionsNotSatisfiedException) {
            onSecurityEvent?.invoke("CONDITIONS_NOT_SATISFIED")
            BrizziResponse.conditionsNotSatisfied
        } catch (_: IllegalArgumentException) {
            onSecurityEvent?.invoke("APDU_ARGUMENT_INVALID")
            BrizziResponse.wrongLength
        }
    }

    /** Reset hanya sesi transaksi/state, tidak menghapus data kartu. */
    fun resetSession() = session.reset()

    /**
     * Ringkasan diagnostik parsing APDU (untuk log debug), tanpa throw.
     */
    fun parseApduSummary(hex: String): String {
        return try {
            val parsed = ApduParser.parse(hex.hexToBytes())
            val cla = parsed.cla.toString(16).uppercase().padStart(2, '0')
            val ins = parsed.ins.toString(16).uppercase().padStart(2, '0')
            val p1 = parsed.p1.toString(16).uppercase().padStart(2, '0')
            val p2 = parsed.p2.toString(16).uppercase().padStart(2, '0')
            val lc = parsed.lc?.toString(16)?.uppercase()?.padStart(2, '0') ?: "??"
            val dataLen = parsed.data.size
            "CLA=$cla INS=$ins P1=$p1 P2=$p2 Lc=$lc data-len=$dataLen le=${parsed.le ?: -1}"
        } catch (_: Exception) {
            if (hex.length < 8) return "len=${hex.length / 2} incomplete"
            "invalid-apdu"
        }
    }

    private fun handleParsed(parsed: ParsedApdu): ByteArray {
        val hex = parsed.rawHex
        if (isIsoSelectMalformed(hex)) {
            onSecurityEvent?.invoke("ISO_SELECT_MALFORMED")
            return BrizziResponse.wrongLength
        }

        if (isGetProcessingOptions(parsed, hex)) {
            onSecurityEvent?.invoke("GET_PROCESSING_OPTIONS")
            // Respons sederhana yang tetap valid untuk NFC framework agar melanjutkan alur baca data.
            // Diberikan tanpa struktur AIP/AFL penuh agar tidak mengubah flow existing.
            return if (hex.startsWith("90")) {
                BrizziResponse.iso("80020000")
            } else {
                BrizziResponse.native("80020000")
            }
        }

        if (isIsoSelectPrefix(hex)) {
            session.reset()
            val selectedAid = parseIsoSelectAid(hex.uppercase())
            onSecurityEvent?.invoke("ISO_SELECT_OK")
            if (selectedAid == null) {
                onSecurityEvent?.invoke("ISO_SELECT_PARSE_FAIL")
                return BrizziResponse.fileNotFound
            }

            // Jika AID diketahui, gunakan FCI yang sesuai.
            // Jika tidak dikenali, balas 6A82 agar reader berhenti berputar di daftar probe AID.
            val normalizedAid = selectedAid.uppercase()
            val aidResponse = if (normalizedAid == PPSE_AID_HEX) {
                buildPpseResponse(READER_AID1_HEX, "4252495A5A49")
            } else if (
                isAidMatchPrefix(selectedAid, READER_AID1_HEX) ||
                isAidMatchPrefix(selectedAid, READER_AID1_ALT_HEX) ||
                selectedAid.startsWith(AID1_HEX) ||
                isAidMatchPrefix(selectedAid, AID1_HEX)
            ) {
                session.selectAid1()
                onSecurityEvent?.invoke("SELECT_AID1")
                aidFciResponse(normalizedAid, "4252495A5A492042414C")
            } else if (
                isAidMatchPrefix(selectedAid, READER_AID3_HEX) ||
                selectedAid.startsWith(AID3_HEX) ||
                isAidMatchPrefix(selectedAid, AID3_HEX)
            ) {
                session.selectAid3()
                onSecurityEvent?.invoke("SELECT_AID3")
                aidFciResponse(normalizedAid, "4252495A5A492048495354")
            } else {
                onSecurityEvent?.invoke("ISO_SELECT_UNKNOWN_AID")
                return BrizziResponse.fileNotFound
            }

            return BrizziResponse.iso7816(aidResponse)
        }

        if (hex == BrizziCommandCatalog.SELECT_AID_1_ISO) {
            session.reset()
            session.selectAid1()
            onSecurityEvent?.invoke("SELECT_AID1_WRAPPED")
            return BrizziResponse.isoSuccess
        }

        if (hex == BrizziCommandCatalog.SELECT_AID_3_ISO) {
            session.reset()
            session.selectAid3()
            onSecurityEvent?.invoke("SELECT_AID3_WRAPPED")
            return BrizziResponse.isoSuccess
        }

        if (hex.startsWith("00A4010002")) {
            onSecurityEvent?.invoke("ISO_SELECT_FILE_OK")
            return BrizziResponse.iso7816()
        }

        if (isIsoReadBinary0200(parsed, hex)) {
            onSecurityEvent?.invoke("ISO_READ_BINARY_0200")
            val payload = buildIsoBinary0200(parsed.le)
            return BrizziResponse.iso7816(payload)
        }

        if (hex == "9060000000") {
            onSecurityEvent?.invoke("GET_VERSION")
            session.continuationFrames = mutableListOf(
                card.versionFrame1Hex,
                card.versionFrame2Hex,
                card.versionFrame3Hex,
            )
            val first = session.continuationFrames.removeFirst()
            return BrizziResponse.isoAdditionalFrame(first)
        }

        if (hex == "90BB0000070100000000000000" || hex == "BB01000000000000") {
            onSecurityEvent?.invoke("GET_LOG")
            if (card.logs.isEmpty()) {
                return if (hex.startsWith("90")) BrizziResponse.isoNoLog() else BrizziResponse.nativeNoLog()
            }
            val records = card.logs.map { it.toHex() }
            val first = records.first()
            val remaining = records.drop(1)
            session.continuationFrames = (remaining + "").toMutableList()
            return if (hex.startsWith("90")) {
                BrizziResponse.isoAdditionalFrame(first)
            } else {
                BrizziResponse.native(first)
            }
        }

        if (isReadableDataCommand(parsed, hex)) {
            if (!session.isReadyForReadOnlyCommand()) {
                onSecurityEvent?.invoke("GET_DATA_WITHOUT_SELECTION")
                session.selectUnknownAid()
            }
            if (isBalanceDataTag(parsed, hex)) {
                onSecurityEvent?.invoke("GET_BALANCE")
                session.readableFallbackStep = 0
                val payload = intToReversedHex(currentBalance(), 4)
                return if (hex.startsWith("90")) BrizziResponse.iso(payload) else BrizziResponse.native(payload)
            }
            if (isHistoryDataTag(parsed, hex)) {
                onSecurityEvent?.invoke("GET_HISTORY")
                session.readableFallbackStep = 0
                return readTransactionHistory(hex)
            }
            val fallback = resolveUnknownReadableAsFallback(hex)
            if (fallback != null) {
                return fallback
            }
            onSecurityEvent?.invoke("GET_DATA_UNKNOWN")
            return BrizziResponse.fileNotFound
        }

        if (
            hex.startsWith("90BD000007BD0000000017") ||
            hex.startsWith("90BD0000070000000017") ||
            hex == "BD00000000170000"
        ) {
            if (session.selectedAid != SelectedAid.AID1) {
                onSecurityEvent?.invoke("CARD_INFO_ACCESS_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("GET_CARD_INFO")
            val payload = "425249${card.cardNumberHex}${card.persoDateHex}${card.issuerCodeHex}${card.cardInfoTailHex}"
            return if (hex.startsWith("90")) BrizziResponse.iso(payload) else BrizziResponse.native(payload)
        }

        if (
            hex.startsWith("90BD0000070100000020") ||
            hex == "BD0100000200000"
        ) {
            if (session.selectedAid != SelectedAid.AID1) {
                onSecurityEvent?.invoke("CARD_STATUS_ACCESS_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("GET_CARD_STATUS")
            val payload = buildString {
                append(card.persoDateHex)
                append(card.cardStatusHex)
                append("00".repeat(27))
            }
            return if (hex.startsWith("90")) BrizziResponse.iso(payload) else BrizziResponse.native(payload)
        }

        if (hex == "900A0000010000" || hex == "0A00") {
            if (session.selectedAid != SelectedAid.AID3) {
                onSecurityEvent?.invoke("KEY_CARD_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("GET_KEY_CARD_00")
            return if (hex.startsWith("90")) {
                BrizziResponse.isoAdditionalFrame(card.keyCard00Hex)
            } else {
                BrizziResponse.native(card.keyCard00Hex)
            }
        }

        if (hex == "900A0000010100" || hex == "0A01") {
            if (session.selectedAid != SelectedAid.AID3) {
                onSecurityEvent?.invoke("KEY_CARD_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("GET_KEY_CARD_01")
            return if (hex.startsWith("90")) {
                BrizziResponse.isoAdditionalFrame(card.keyCard01Hex)
            } else {
                BrizziResponse.native(card.keyCard01Hex)
            }
        }

        if (hex == "FFCA000000") {
            return BrizziResponse.iso(card.uidHex)
        }

        if (isAuthenticateCommand(hex)) {
            if (session.phase != SessionPhase.AID3_SELECTED && session.phase != SessionPhase.AID3_AUTHENTICATED) {
                onSecurityEvent?.invoke("AUTH_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("AUTH_SUCCESS")
            session.authenticate()
            return if (hex.startsWith("90")) BrizziResponse.iso(card.randomNumberHex) else BrizziResponse.native(card.randomNumberHex)
        }

        if (
            hex.startsWith("90BD0000070300000007") ||
            hex == "BD03000000070000"
        ) {
            if (session.selectedAid != SelectedAid.AID3 &&
                session.selectedAid != SelectedAid.OTHER
            ) {
                onSecurityEvent?.invoke("BALANCE_AKUM_WRONG_AID")
                return BrizziResponse.conditionsNotSatisfied
            }
            if (!session.isReadyForReadOnlyCommand()) {
                onSecurityEvent?.invoke("BALANCE_AKUM_ACCESS_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("GET_LAST_TXN_AND_AKUM")
            val payload = currentLastTransactionDateHex() + currentAkumDebetHex()
            return if (hex.startsWith("90")) BrizziResponse.iso(payload) else BrizziResponse.native(payload)
        }

        if (isGetBalanceCommand(parsed, hex)) {
            if (!session.isReadyForReadOnlyCommand()) {
                onSecurityEvent?.invoke("GET_BALANCE_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            if (session.selectedAid != SelectedAid.AID3 &&
                session.selectedAid != SelectedAid.OTHER
            ) {
                onSecurityEvent?.invoke("GET_BALANCE_WRONG_AID")
                return BrizziResponse.conditionsNotSatisfied
            }
            if (!session.isReadyForCommandWithAuth()) {
                onSecurityEvent?.invoke("GET_BALANCE_NO_AUTH_BYPASS")
            }
            onSecurityEvent?.invoke("GET_BALANCE")
            session.readableFallbackStep = 0
            val payload = intToReversedHex(currentBalance(), 4)
            return if (hex.startsWith("90")) BrizziResponse.iso(payload) else BrizziResponse.native(payload)
        }

        if (isReadHistoryRecord(parsed, hex)) {
            if (!session.isReadyForReadOnlyCommand()) {
                onSecurityEvent?.invoke("GET_HISTORY_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            if (session.selectedAid != SelectedAid.AID3 &&
                session.selectedAid != SelectedAid.OTHER
            ) {
                onSecurityEvent?.invoke("GET_HISTORY_WRONG_AID")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("GET_HISTORY_B2")
            session.readableFallbackStep = 0
            return readTransactionHistory(hex)
        }

        if (isGetHistoryCommand(parsed, hex)) {
            if (!session.isReadyForReadOnlyCommand()) {
                onSecurityEvent?.invoke("GET_HISTORY_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            if (session.selectedAid != SelectedAid.AID3 &&
                session.selectedAid != SelectedAid.OTHER
            ) {
                onSecurityEvent?.invoke("GET_HISTORY_WRONG_AID")
                return BrizziResponse.conditionsNotSatisfied
            }
            if (!session.isReadyForCommandWithAuth()) {
                onSecurityEvent?.invoke("GET_HISTORY_NO_AUTH_BYPASS")
            }
            onSecurityEvent?.invoke("GET_HISTORY")
            session.readableFallbackStep = 0
            return readTransactionHistory(hex)
        }

        if (hex == "AF" || hex == "90AF000000") {
            val next = session.continuationFrames.removeFirstOrNull()
                ?: run {
                    onSecurityEvent?.invoke("GET_LOG_CONTINUATION_MISSING")
                    return BrizziResponse.conditionsNotSatisfied
                }
            onSecurityEvent?.invoke("GET_LOG_CONTINUATION")
            return if (hex.startsWith("90")) {
                if (next.isEmpty()) {
                    BrizziResponse.iso()
                } else if (session.continuationFrames.isEmpty()) {
                    BrizziResponse.iso(next)
                } else {
                    BrizziResponse.isoAdditionalFrame(next)
                }
            } else {
                BrizziResponse.native(next)
            }
        }

        if (isNativeDebit(hex) || isWrappedDebit(hex)) {
            if (!session.isReadyForNewTransaction()) {
                onSecurityEvent?.invoke("DEBIT_REJECTED")
                throw ConditionsNotSatisfiedException
            }
            val amount = validateTransactionAmount(extractAmount(hex))
            val before = currentBalance()
            if (amount > before) {
                onSecurityEvent?.invoke("DEBIT_INSUFFICIENT_FUNDS")
                return BrizziResponse.conditionsNotSatisfied
            }
            val after = (before - amount).coerceAtLeast(0)
            onSecurityEvent?.invoke("DEBIT_PREPARED")
            session.startDebitTransaction()
            session.pendingTransaction = PendingTransaction(TransactionKind.DEBIT, amount, before, after)
            val payload = intToReversedHex(after, 4)
            return if (hex.startsWith("90")) BrizziResponse.iso(payload) else BrizziResponse.native(payload)
        }

        if (isNativeCredit(hex) || isWrappedCredit(hex)) {
            if (!session.isReadyForNewTransaction()) {
                onSecurityEvent?.invoke("CREDIT_REJECTED")
                throw ConditionsNotSatisfiedException
            }
            val amount = validateTransactionAmount(extractAmount(hex))
            val before = currentBalance()
            if (before + amount > ALLOWED_MAX_BALANCE) {
                onSecurityEvent?.invoke("CREDIT_MAX_BALANCE_EXCEEDED")
                return BrizziResponse.conditionsNotSatisfied
            }
            val after = before + amount
            onSecurityEvent?.invoke("CREDIT_PREPARED")
            session.startCreditTransaction()
            session.pendingTransaction = PendingTransaction(TransactionKind.CREDIT, amount, before, after)
            val payload = intToReversedHex(after, 4)
            return if (hex.startsWith("90")) BrizziResponse.iso(payload) else BrizziResponse.native(payload)
        }

        if (isWriteLogCommand(hex)) {
            if (!isValidWriteLogPayloadShape(hex)) {
                onSecurityEvent?.invoke("WRITE_LOG_PAYLOAD_INVALID")
                return BrizziResponse.wrongLength
            }
            if (!session.isReadyForCommandWithAuth()) {
                onSecurityEvent?.invoke("WRITE_LOG_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            if (session.hasPendingTransactionState()) {
                onSecurityEvent?.invoke("WRITE_LOG_REJECTED_PENDING")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("UPDATE_LOG")
            session.startLogUpdate()
            val payload = extractWriteLogPayload(hex)
            session.pendingLogHex = payload
            return if (hex.startsWith("90")) BrizziResponse.iso() else BrizziResponse.native()
        }

        if (isLastTransactionCommand(hex)) {
            if (!isValidLastTransactionPayloadShape(hex)) {
                onSecurityEvent?.invoke("LAST_TXN_PAYLOAD_INVALID")
                return BrizziResponse.wrongLength
            }
            if (!session.isReadyForCommandWithAuth()) {
                onSecurityEvent?.invoke("LAST_TXN_DENIED")
                return BrizziResponse.conditionsNotSatisfied
            }
            if (session.hasPendingTransactionState()) {
                onSecurityEvent?.invoke("LAST_TXN_REJECTED_PENDING")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("UPDATE_LAST_TXN")
            session.startLastTxnUpdate()
            val (dateHex, akumDebetHex) = extractLastTransactionPayload(hex)
            session.pendingLastTransactionDateHex = dateHex
            session.pendingAkumDebetHex = akumDebetHex
            return if (hex.startsWith("90")) BrizziResponse.iso() else BrizziResponse.native()
        }

        if (hex == "C7" || hex == "90C7000000") {
            if (!session.hasPendingTransactionState()) {
                onSecurityEvent?.invoke("COMMIT_REJECTED")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("COMMIT_OK")
            commitPendingState()
            session.clearReplayWindow()
            session.phase = SessionPhase.AID3_AUTHENTICATED
            return if (hex.startsWith("90")) BrizziResponse.iso() else BrizziResponse.native()
        }

        if (hex == "A7" || hex == "90A7000000") {
            if (!session.hasPendingTransactionState()) {
                onSecurityEvent?.invoke("ABORT_REJECTED")
                return BrizziResponse.conditionsNotSatisfied
            }
            onSecurityEvent?.invoke("ABORT_OK")
            session.clearPendingTransactionState()
            session.clearReplayWindow()
            if (session.phase == SessionPhase.AID3_AUTHENTICATED) {
                session.phase = SessionPhase.AID3_AUTHENTICATED
            }
            return if (hex.startsWith("90")) BrizziResponse.iso() else BrizziResponse.native()
        }

        onSecurityEvent?.invoke("UNSUPPORTED_INSTRUCTION")
        return BrizziResponse.unsupportedInstruction
    }

    private fun isIsoSelectAid(hex: String): Boolean {
        val normalized = hex.uppercase()
        if (!isIsoSelectPrefix(normalized)) return false
        val aid = parseIsoSelectAid(normalized) ?: return false
        return routingAidHexes.any { routingAid ->
            routingAid.startsWith(aid)
                || isAidMatchPrefix(aid, routingAid)
        }
    }

    private fun isAidMatchPrefix(selectedAid: String, configuredAid: String): Boolean {
        if (configuredAid.isEmpty()) return false
        return if (selectedAid.length >= configuredAid.length) {
            selectedAid.startsWith(configuredAid)
        } else {
            configuredAid.startsWith(selectedAid)
        }
    }

    private fun isIsoSelectMalformed(hex: String): Boolean {
        val normalized = hex.uppercase()
        if (!isIsoSelectPrefix(normalized)) return false
        if (normalized.length < 10) return true
        val lc = normalized.substring(8, 10).toIntOrNull(16) ?: return true
        val dataLength = lc * 2
        val expectedDataLength = 10 + dataLength
        if (normalized.length < expectedDataLength) return true
        return normalized.length !in expectedDataLength..expectedDataLength + 2
    }

    private fun isIsoSelectPrefix(hex: String): Boolean {
        return hex.startsWith("00A404") || hex.startsWith("90A404") || hex.startsWith("80A404")
    }

    private fun isReplaySensitiveHex(hex: String): Boolean {
        return when {
            isNativeDebit(hex) || isWrappedDebit(hex) -> true
            isNativeCredit(hex) || isWrappedCredit(hex) -> true
            isCommitOrAbort(hex) -> true
            isWriteLogCommand(hex) -> true
            isLastTransactionCommand(hex) -> true
            else -> false
        }
    }

    private fun isGetBalanceCommand(parsed: ParsedApdu, hex: String): Boolean {
        val normalized = hex.uppercase()
        val tagHint = getReadableDataTag(parsed, normalized)
        return when {
            parsed.ins == 0xCA && parsed.p1 == 0x9F && parsed.p2 in 0x79..0x79 -> true
            tagHint.isNotBlank() && containsAnyTag(tagHint, BALANCE_READ_DATA_TAGS) -> true
            normalized.startsWith("906C0000010000") -> true
            parsed.ins == 0xCA && containsAnyTag(normalized, BALANCE_READ_DATA_TAGS) -> true
            parsed.ins == 0x00CA && containsAnyTag(normalized, BALANCE_READ_DATA_TAGS) -> true
            parsed.ins == 0x80CA && containsAnyTag(normalized, BALANCE_READ_DATA_TAGS) -> true
            else -> false
        }
    }

    private fun isGetProcessingOptions(parsed: ParsedApdu, hex: String): Boolean {
        val normalized = hex.uppercase()
        return parsed.ins == 0xA8 ||
            normalized.startsWith("80A8") ||
            normalized.startsWith("00A8") ||
            normalized.startsWith("90A8")
    }

    private fun isReadableDataCommand(parsed: ParsedApdu, hex: String): Boolean {
        return when (parsed.ins) {
            0xCA, 0xB2, 0xBB -> true
            else -> false
        }
    }

    private fun isBalanceDataTag(parsed: ParsedApdu, hex: String): Boolean {
        val normalized = hex.uppercase()
        val tag = getReadableDataTag(parsed, normalized)
        if (tag.isBlank()) return isKnownBalanceTag(normalized)
        return containsAnyTag(tag, BALANCE_READ_DATA_TAGS) || isKnownBalanceTag(normalized)
    }

    private fun isHistoryDataTag(parsed: ParsedApdu, hex: String): Boolean {
        val normalized = hex.uppercase()
        val tag = getReadableDataTag(parsed, normalized)
        if (tag.isBlank()) return isKnownHistoryTag(normalized)
        return containsAnyTag(tag, HISTORY_READ_DATA_TAGS) || isKnownHistoryTag(normalized)
    }

    private fun getReadableDataTag(parsed: ParsedApdu, normalized: String): String {
        if (parsed.ins == 0xCA) {
            val p1 = normalized.substringOrEmpty(4, 6).padEnd(2, '0')
            val p2 = normalized.substringOrEmpty(6, 8).padEnd(2, '0')
            return "$p1$p2"
        }
        if (parsed.data.isNotEmpty()) {
            return parsed.data.toHex().uppercase().substringOrEmpty(0, 4)
        }
        return ""
    }

    private fun isKnownBalanceTag(normalized: String): Boolean {
        return containsAnyTag(normalized, BALANCE_READ_DATA_TAGS)
    }

    private fun isKnownHistoryTag(normalized: String): Boolean {
        return containsAnyTag(normalized, HISTORY_READ_DATA_TAGS)
    }

    private fun String.substringOrEmpty(start: Int, end: Int): String {
        return if (start >= this.length || start >= end) {
            ""
        } else {
            this.substring(start, end.coerceAtMost(this.length))
        }
    }

    private fun aidFciResponse(aidHex: String, labelHex: String): String {
        return buildFciForAid(aidHex, labelHex)
    }

    private fun buildPpseResponse(aidHex: String, labelHex: String): String {
        val entry = buildTlv("61", buildTlv("4F", aidHex) + buildTlv("50", labelHex))
        val issuerDiscretionary = buildTlv("BF0C", entry)
        val fciPayload = buildTlv("84", PPSE_AID_HEX) + buildTlv("A5", issuerDiscretionary)
        return buildTlv("6F", fciPayload)
    }

    private fun buildFciForAid(aidHex: String, labelHex: String): String {
        val aid = aidHex.uppercase()
        val proprietaryTemplate = buildTlv("50", labelHex) + buildTlv("9F38", "")
        val fciPayload = buildTlv("84", aid) + buildTlv("A5", proprietaryTemplate)
        return buildTlv("6F", fciPayload)
    }

    private fun buildTlv(tagHex: String, payloadHex: String): String {
        val normalizedPayload = payloadHex.uppercase()
        return "$tagHex${encodeTlvLength(normalizedPayload.length / 2)}$normalizedPayload"
    }

    private fun encodeTlvLength(length: Int): String {
        return when {
            length <= 0x7F -> length.toString(16).uppercase().padStart(2, '0')
            length <= 0xFF -> "81" + length.toString(16).uppercase().padStart(2, '0')
            else -> "82" + length.toString(16).uppercase().padStart(4, '0')
        }
    }

    private fun isReadHistoryRecord(parsed: ParsedApdu, hex: String): Boolean {
        return when {
            parsed.ins == 0xB2 -> true
            parsed.ins == 0xBB -> true
            else -> false
        } && isReadyToLogOrHistory(hex.uppercase())
    }

    private fun isGetHistoryCommand(parsed: ParsedApdu, hex: String): Boolean {
        val normalized = hex.uppercase()
        return when (parsed.ins) {
            0xB2 -> true
            0xBB -> true
            0xCA -> normalized.startsWith("90CA") || normalized.startsWith("00CA") || normalized.startsWith("80CA")
            else -> false
        } && isReadyToLogOrHistory(normalized)
    }

    private fun isIsoReadBinary0200(parsed: ParsedApdu, hex: String): Boolean {
        val normalized = hex.uppercase()
        return parsed.ins == 0xB0 &&
            parsed.p1 == 0x81 &&
            parsed.p2 == 0x00 &&
            normalized.startsWith("00B08100")
    }

    private fun buildIsoBinary0200(le: Int?): String {
        val fileHex = buildString {
            append("425249")
            append(card.cardNumberHex)
            append(card.persoDateHex)
            append(card.issuerCodeHex)
            append(card.cardInfoTailHex)
            append(card.persoDateHex)
            append(card.cardStatusHex)
            append(card.uidHex)
            append(card.keyCard00Hex)
            append(card.randomNumberHex)
            append(card.lastTransactionDateHex)
            append(card.akumDebetHex.take(8))
            append(intToReversedHex(card.balance, 4))
            append(card.logs.joinToString("") { it.toHex() })
        }
        val targetBytes = le ?: (fileHex.length / 2)
        val normalized = if (fileHex.length / 2 >= targetBytes) {
            fileHex.take(targetBytes * 2)
        } else {
            fileHex.padEnd(targetBytes * 2, '0')
        }
        return normalized.uppercase()
    }

    private fun resolveUnknownReadableAsFallback(hex: String): ByteArray? {
        return when (session.readableFallbackStep) {
            0 -> {
                onSecurityEvent?.invoke("GET_BALANCE_FALLBACK")
                session.readableFallbackStep = 1
                val payload = intToReversedHex(currentBalance(), 4)
                if (hex.startsWith("90")) BrizziResponse.iso(payload) else BrizziResponse.native(payload)
            }
            1 -> {
                onSecurityEvent?.invoke("GET_HISTORY_FALLBACK")
                session.readableFallbackStep = 2
                readTransactionHistory(hex)
            }
            else -> {
                onSecurityEvent?.invoke("GET_READABLE_FALLBACK_DONE")
                if (hex.startsWith("90")) {
                    BrizziResponse.isoNoLog()
                } else {
                    BrizziResponse.nativeNoLog()
                }
            }
        }
    }

    private fun containsAnyTag(normalized: String, tags: Set<String>): Boolean {
        return tags.any { tag -> normalized.contains(tag) }
    }

    private fun isReadyToLogOrHistory(normalized: String): Boolean {
        return when {
            normalized.startsWith("90AF") || normalized == "AF" -> true
            normalized.startsWith("C7") || normalized.startsWith("A7") -> false
            else -> true
        }
    }

    private fun readTransactionHistory(hex: String): ByteArray {
        if (card.logs.isEmpty()) {
            return if (hex.startsWith("90")) BrizziResponse.isoNoLog() else BrizziResponse.nativeNoLog()
        }
        val records = card.logs.map { it.toHex() }
        val first = records.first()
        val remaining = records.drop(1)
        session.continuationFrames = remaining.toMutableList()
        return if (hex.startsWith("90")) {
            if (remaining.isEmpty()) BrizziResponse.iso(first) else BrizziResponse.isoAdditionalFrame(first)
        } else {
            BrizziResponse.native(first)
        }
    }

    private fun parseIsoSelectAid(hex: String): String? {
        if (hex.length < 10) return null
        val lc = hex.substring(8, 10).toIntOrNull(16) ?: return null
        val start = 10
        val end = start + lc * 2
        if (hex.length < end) return null
        return hex.substring(start, end)
    }

    private fun isAuthenticateCommand(hex: String): Boolean {
        val normalized = hex.uppercase()
        return when {
            normalized.startsWith("90AF000010") && normalized.length >= 28 && normalized.endsWith("00") -> true
            normalized.startsWith("AF") && normalized.length == 34 -> true
            else -> false
        }
    }

    private fun isNativeDebit(hex: String): Boolean {
        val normalized = hex.uppercase()
        return normalized.startsWith("DC000004") &&
            normalized.length == 16 &&
            normalized.endsWith("00")
    }

    private fun validateTransactionAmount(amount: Int): Int {
        require(amount > 0) { "Transaction amount must be positive" }
        require(amount <= ALLOWED_MAX_TRANSACTION_AMOUNT) { "Transaction amount too large" }
        return amount
    }

    private fun isWrappedDebit(hex: String): Boolean {
        val normalized = hex.uppercase()
        return normalized.startsWith("90DC000004") &&
            normalized.length == 20
    }

    private fun isNativeCredit(hex: String): Boolean {
        val normalized = hex.uppercase()
        return normalized.startsWith("0C000004") &&
            normalized.length == 16 &&
            normalized.endsWith("00")
    }

    private fun isWrappedCredit(hex: String): Boolean {
        val normalized = hex.uppercase()
        return normalized.startsWith("900C000004") &&
            normalized.length == 20
    }

    private fun isCommitOrAbort(hex: String): Boolean {
        return hex == "90C7000000" || hex == "C7" || hex == "90A7000000" || hex == "A7"
    }

    private fun isWriteLogCommand(hex: String): Boolean {
        return hex.startsWith("3B01") || hex.startsWith("903B01")
    }

    private fun isLastTransactionCommand(hex: String): Boolean {
        return hex.startsWith("3D03") || hex.startsWith("903D03")
    }

    private fun isValidWriteLogPayloadShape(hex: String): Boolean {
        return when {
            hex.startsWith("3B01") -> hex.length >= 18
            hex.startsWith("903B01") -> hex.length >= 20
            else -> false
        }
    }

    private fun isValidLastTransactionPayloadShape(hex: String): Boolean {
        return when {
            hex.startsWith("3D03") -> hex.length >= 20
            hex.startsWith("903D03") -> hex.length >= 28
            else -> false
        }
    }

    private fun extractAmount(hex: String): Int {
        val normalized = hex.uppercase()
        val amountHex = when {
            normalized.startsWith("90DC000004") || normalized.startsWith("900C000004") -> {
                safeAmountRange(normalized, 10, 16)
            }
            normalized.startsWith("DC000004") || normalized.startsWith("0C000004") -> {
                safeAmountRange(normalized, 8, 14)
            }
            else -> return 0
        }
        return reversedHexToInt(amountHex)
    }

    private fun safeAmountRange(hex: String, start: Int, end: Int): String {
        if (start < 0 || end < 0 || start >= end || end > hex.length) {
            throw IllegalArgumentException("Invalid transaction command length")
        }
        if (start + 6 > end) {
            throw IllegalArgumentException("Invalid transaction command length")
        }
        return hex.substring(start, end)
    }

    private fun reversedHexToInt(reversedHex: String): Int {
        val bytes = reversedHex.hexToBytes().reversedArray()
        return bytes.toHex().toInt(16)
    }

    private fun intToReversedHex(value: Int, byteCount: Int): String {
        val bigEndian = value.toString(16).uppercase().padStart(byteCount * 2, '0')
        return bigEndian.chunked(2).reversed().joinToString("")
    }

    private fun currentBalance(): Int = session.pendingTransaction?.balanceAfter ?: card.balance

    private fun currentLastTransactionDateHex(): String =
        session.pendingLastTransactionDateHex ?: card.lastTransactionDateHex

    private fun currentAkumDebetHex(): String =
        (session.pendingAkumDebetHex ?: card.akumDebetHex).take(8)

    private fun requireAid3Authenticated() {
        if (!session.isReadyForCommandWithAuth()) {
            throw ConditionsNotSatisfiedException
        }
    }

    private fun extractWriteLogPayload(hex: String): String =
        when {
            hex.startsWith("3B01") -> {
                val payload = safeSlice(hex, "3B01000000200000".length)
                payload.take(64)
            }
            hex.startsWith("903B") && hex.length >= 20 -> {
                val payload = safeSliceRange(hex, 12, hex.length - 2)
                payload.take(64)
            }
            else -> ""
        }

    private fun extractLastTransactionPayload(hex: String): Pair<String, String> =
        when {
            hex.startsWith("3D03") -> {
                val payload = safeSlice(hex, "3D03000000070000".length)
                if (payload.length < 14) {
                    "000000" to "00000000"
                } else {
                    payload.substring(0, 6) to payload.substring(6, 14)
                }
            }
            hex.startsWith("903D") && hex.length >= 28 -> {
                val payload = safeSliceRange(hex, 12, hex.length - 2)
                if (payload.length < 14) {
                    "000000" to "00000000"
                } else {
                    payload.substring(0, 6) to payload.substring(6, 14)
                }
            }
            else -> "000000" to "00000000"
        }

    private fun commitPendingState() {
        val nextLogs = session.pendingLogHex?.let { listOf(parseBrizziLogRecord(it)) + card.logs } ?: card.logs
        val nextBalance = session.pendingTransaction?.balanceAfter ?: card.balance
        val nextLastDate = session.pendingLastTransactionDateHex ?: card.lastTransactionDateHex
        val nextAkumDebet = session.pendingAkumDebetHex ?: card.akumDebetHex
        card = card.copy(
            balance = nextBalance,
            logs = nextLogs.take(12),
            lastTransactionDateHex = nextLastDate,
            akumDebetHex = nextAkumDebet,
        )
        onCardUpdated?.invoke(card)
        session.clearPendingTransactionState()
        session.phase = SessionPhase.AID3_AUTHENTICATED
    }

    private fun safeSlice(input: String, start: Int): String =
        if (start >= input.length) "" else input.substring(start)

    private fun safeSliceRange(input: String, start: Int, end: Int): String {
        if (start < 0 || end < 0 || start >= end || start >= input.length) return ""
        val safeEnd = end.coerceAtMost(input.length)
        return input.substring(start, safeEnd)
    }

    private object ConditionsNotSatisfiedException : IllegalStateException()
}
