package com.korancrew.brizzi.hce

import android.nfc.cardemulation.HostApduService
import android.os.Bundle
import android.os.SystemClock
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.util.Log
import java.text.SimpleDateFormat
import java.io.File
import java.util.Locale


/**
 * Entry point resmi HCE (Host Card Emulation service).
 *
 * Flow di level service:
 * 1) validasi input APDU (null/ukuran),
 * 2) rate-limit anti-flood,
 * 3) delegasi parsing+routing ke [BrizziApduRouter],
 * 4) pencatatan metrik + incident,
 * 5) persist snapshot health ke file internal.
 */
class BrizziHostApduService : HostApduService() {
    private lateinit var router: BrizziApduRouter
    private lateinit var storage: BrizziSecureStorage
    private lateinit var metrics: BrizziSecurityMetrics
    private var commandWindowStartMs: Long = 0L
    private var commandCountInWindow: Int = 0
    private var lastCommandMs: Long = 0L
    private var activeTapStartedAtWallClockMs: Long = 0L
    private var commandCountInTap: Int = 0
    private var activeTapLastSelectAid: String = ""
    private var lastIncidentLogMs: Long = 0L
    private var isTapSessionActive: Boolean = false
    private var currentTapId: Int = 0
    private val preferences: SharedPreferences by lazy {
        getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
    }
    private fun shouldLogApdu() = isDebuggable() && LOG_VERBOSE

    private fun isDebuggable(): Boolean =
        (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "BrizziHostApduService onCreate")
        Log.i(TAG, "HCE test mode enabled: ${isTestMode()}")
        storage = BrizziSecureStorage(this)
        metrics = BrizziSecurityMetrics(this)
        router = BrizziApduRouter(
            routingAidHex = ROUTING_AID_HEX,
            card = storage.loadCard() ?: BrizziCard(),
            onCardUpdated = { updatedCard ->
                storage.saveCard(updatedCard)
                metrics.track("CARD_UPDATED")
            },
            onSecurityEvent = { event ->
                metrics.track(event)
            },
        )
        metrics.track("SERVICE_STARTED")
        Log.i(TAG, "BrizziHostApduService started")
        isTapSessionActive = false
        activeTapStartedAtWallClockMs = 0L
        lastIncidentLogMs = 0L
        currentTapId = 0
    }

    private fun toLogSafeApduHex(commandApdu: ByteArray, response: ByteArray): String {
        val code = commandApdu.firstOrNull()?.toInt()?.and(0xFF)?.toString(16)?.uppercase()?.padStart(2, '0')
        val responseHex = runCatching { response.toHex().uppercase() }.getOrElse { "" }
        val status = if (responseHex.length >= 4) responseHex.takeLast(4) else "N/A"
        return "len=${commandApdu.size}|code=$code|status=$status"
    }

    /**
     * Menerima APDU dari reader NFC.
     *
     * Setiap request selalu di-track minimal:
     * - COMMAND_RECEIVED
     * - COMMAND_TOTAL
     * dan diikuti event spesifik (success/fail/response code).
     */
    override fun processCommandApdu(commandApdu: ByteArray?, extras: Bundle?): ByteArray {
        Log.i(TAG, "processCommandApdu invoked")
        val commandReceiveAtMs = SystemClock.elapsedRealtime()
        val commandReceivedAtWallClockMs = System.currentTimeMillis()
        val commandHex = commandApdu.toHexOrNull() ?: "null"
        clearApduLogIfNeeded(commandReceivedAtWallClockMs, commandHex)
        if (isTestMode()) {
            Log.w(TAG, "TEST MODE active: short-circuit APDU handling")
            metrics.track("COMMAND_IN_TEST_MODE")
            val response = handleTestModeCommand(commandApdu)
            metrics.track("COMMAND_RESPONDED")
            logApduExchange(commandHex, response)
            reportIncidentsIfNeeded()
            persistHealthSnapshot("test-mode-command")
            return response
        }
        metrics.track("COMMAND_RECEIVED")
        metrics.track("COMMAND_TOTAL")
        if (commandApdu == null) {
            metrics.track("COMMAND_REJECTED_NULL")
            metrics.track("COMMAND_FAIL")
            val response = BrizziResponse.wrongLength
            logApduExchange("null", response)
            reportIncidentsIfNeeded()
            return response
        }
        if (commandApdu.isEmpty() || commandApdu.size > MAX_APDU_BYTES) {
            metrics.track("COMMAND_REJECTED_INVALID_SIZE")
            metrics.track("COMMAND_FAIL")
            val response = BrizziResponse.wrongLength
            logApduExchange(commandHex, response, "invalid-size")
            reportIncidentsIfNeeded()
            return response
        }
        val now = SystemClock.elapsedRealtime()
        val rateCheck = checkRateLimit(now, commandHex)
        if (rateCheck != null) {
            if (rateCheck.contentEquals(BrizziResponse.rateLimitExceeded)) {
                metrics.track("COMMAND_RATE_LIMIT")
                metrics.track("COMMAND_FAIL")
            }
            logApduExchange(commandHex, rateCheck, "rate-limit")
            reportIncidentsIfNeeded()
            return rateCheck
        }

        if (shouldLogApdu()) {
            val parsed = router.parseApduSummary(commandHex)
            Log.d(TAG, "APDU recv: $parsed")
            if (
                commandHex.startsWith("00A4") ||
                commandHex.startsWith("80A4") ||
                commandHex.startsWith("90A4") ||
                commandHex.startsWith("905A") ||
                commandHex.startsWith("90BD") ||
                commandHex.startsWith("900A")
            ) {
                Log.d(TAG, "APDU select raw: $commandHex")
            }
        }

        val response = router.handle(commandApdu)
        metrics.track("COMMAND_RESPONDED")
        when (response.toHex()) {
            BrizziResponse.wrongLength.toHex() -> metrics.track("RESPONSE_WRONG_LENGTH")
            BrizziResponse.conditionsNotSatisfied.toHex() -> metrics.track("RESPONSE_CONDITIONS_NOT_SATISFIED")
            BrizziResponse.unsupportedInstruction.toHex() -> metrics.track("RESPONSE_UNSUPPORTED")
            BrizziResponse.rateLimitExceeded.toHex() -> metrics.track("RESPONSE_RATE_LIMIT")
            BrizziResponse.securityStatusNotSatisfied.toHex() -> metrics.track("RESPONSE_SECURITY_STATUS")
        }
        if (response.toHex().endsWith("9100") || response.contentEquals(BrizziResponse.nativeSuccess)
            || response.toHex() == "BE" || response.toHex() == "91BE"
        ) {
            metrics.track("COMMAND_SUCCESS")
        } else {
            metrics.track("COMMAND_FAIL")
        }
        reportIncidentsIfNeeded()
        if (shouldLogApdu()) {
            Log.d(TAG, "APDU summary: ${toLogSafeApduHex(commandApdu, response)}")
        }
        logApduExchange(commandHex, response)
        if (isDebuggable()) {
            Log.d(TAG, "security metrics snapshot: ${metrics.snapshot()}")
        }
        persistHealthSnapshot("command")
        return response
    }

    private fun handleTestModeCommand(commandApdu: ByteArray?): ByteArray {
        if (commandApdu == null) {
            metrics.track("COMMAND_REJECTED_NULL")
            logApduExchange("null", BrizziResponse.wrongLength, "test-mode-invalid")
            return BrizziResponse.wrongLength
        }
        if (commandApdu.isEmpty() || commandApdu.size > MAX_APDU_BYTES) {
            metrics.track("COMMAND_REJECTED_INVALID_SIZE")
            logApduExchange(commandApdu.toHexOrNull() ?: "null", BrizziResponse.wrongLength, "test-mode-invalid-size")
            return BrizziResponse.wrongLength
        }
        val hex = commandApdu.toHex().uppercase()
        return when {
            hex.startsWith("00A4") || hex.startsWith("80A4") || hex.startsWith("90A4") -> {
                metrics.track("TEST_MODE_SELECT")
                Log.d(TAG, "TEST MODE SELECT command: $hex")
                BrizziResponse.iso("B2495A5A5A")
            }
            hex == "80CA9F7F00" || hex == "CA9F7F00" || hex == "00CA9F7F00" -> {
                metrics.track("TEST_MODE_HEALTH_PING")
                Log.d(TAG, "TEST MODE health ping: $hex")
                BrizziResponse.native("484953544F4D45")
            }
            else -> {
                metrics.track("TEST_MODE_DEFAULT_OK")
                Log.d(TAG, "TEST MODE default response for: $hex")
                BrizziResponse.nativeSuccess
            }
        }
    }

    /**
     * Dipanggil saat HCE dinonaktifkan (mis. kartu dijauhkan / sesi selesai).
     * Kita reset state di router supaya command berikutnya tidak lanjut dari state lama.
     */
    override fun onDeactivated(reason: Int) {
        Log.d(TAG, "HCE deactivated: $reason")
        router.resetSession()
        if (isDebuggable()) {
            Log.d(TAG, "security metrics: ${metrics.snapshot()}")
            Log.d(TAG, "security health: ${metrics.exportHealthSnapshot()}")
        }
        appendTapFooter(reason)
        isTapSessionActive = false
        activeTapStartedAtWallClockMs = 0L
        persistHealthSnapshot("deactivated-$reason")
        metrics.track("SERVICE_DEACTIVATED")
        commandWindowStartMs = 0L
        commandCountInWindow = 0
        lastCommandMs = 0L
        lastIncidentLogMs = 0L
    }

    private fun persistHealthSnapshot(reason: String) {
        Log.d(TAG, "persistHealthSnapshot reason=$reason")
        runCatching {
            val file = File(filesDir, METRICS_EXPORT_FILE)
            file.appendText("${System.currentTimeMillis()},$reason,${metrics.exportHealthSnapshot()}\n")
        }
    }

    private fun reportIncidentsIfNeeded() {
        val now = SystemClock.elapsedRealtime()
        if (now - lastIncidentLogMs < INCIDENT_LOG_THROTTLE_MS) return
        val incidents = metrics.activeIncidents()
        if (incidents.isEmpty()) return
        lastIncidentLogMs = now
        Log.w(TAG, "security incidents: ${metrics.incidentSummary()}")
    }

    private fun checkRateLimit(nowMs: Long, commandHex: String): ByteArray? {
        if (commandWindowStartMs == 0L) {
            commandWindowStartMs = nowMs
        }
        if (nowMs - commandWindowStartMs >= RATE_WINDOW_MS) {
            commandWindowStartMs = nowMs
            commandCountInWindow = 0
        }
        commandCountInWindow += 1
        if (commandCountInWindow > MAX_COMMANDS_PER_WINDOW) {
            return BrizziResponse.rateLimitExceeded
        }
        val minInterval = if (isDebuggable()) DEBUG_MIN_COMMAND_INTERVAL_MS else MIN_COMMAND_INTERVAL_MS
        if (isAggressiveBurstSensitive(commandHex) &&
            lastCommandMs != 0L &&
            nowMs - lastCommandMs < minInterval
        ) {
            return BrizziResponse.rateLimitExceeded
        }
        lastCommandMs = nowMs
        return null
    }

    private fun isAggressiveBurstSensitive(commandHex: String): Boolean {
        val normalized = commandHex.uppercase()
        return when {
            normalized.startsWith("00A404") -> true
            normalized == "6C00" -> true
            normalized.contains("9F7900") && normalized.substring(2).isNotEmpty() && normalized.substring(2, 4) == "CA" -> true
            else -> false
        }
    }

    private fun isTestMode(): Boolean {
        if (!isDebuggable()) return false
        return preferences.getBoolean(KEY_TEST_MODE_ENABLED, false)
    }

    private fun logApduExchange(
        commandHex: String,
        response: ByteArray,
        extra: String? = null
    ) {
        if (!isDebuggable()) return
        runCatching {
            val timestamp = LOG_TIME_FORMAT.format(System.currentTimeMillis())
            val commandIndex = ++commandCountInTap
            val reason = extra?.let { " | $it" } ?: ""
            val line = "$timestamp | TAP=$currentTapId CMD#$commandIndex | REQ=$commandHex | RSP=${response.toHex().uppercase()}$reason"
            val file = File(filesDir, APDU_EXCHANGE_LOG_FILE)
            val lines = if (file.exists()) file.readLines() else emptyList()
            val trimmedLines = if (lines.size >= APDU_LOG_MAX_LINES) {
                lines.takeLast(APDU_LOG_MAX_LINES - 1)
            } else {
                lines
            }
            file.writeText((trimmedLines + line).joinToString(System.lineSeparator()) + System.lineSeparator())
        }.onFailure {
            Log.w(TAG, "Failed to write APDU exchange log", it)
        }
    }

    private fun clearApduLogIfNeeded(nowWallClockMs: Long, commandHex: String) {
        if (!isDebuggable()) return
        val normalized = commandHex.uppercase()
        val isTapStart = isTapStartCommand(normalized)
        val currentAid = extractSelectAid(normalized)
        val isAidChangedTapStart = isTapStart &&
            activeTapLastSelectAid.isNotBlank() &&
            currentAid != null &&
            currentAid.isNotBlank() &&
            currentAid != activeTapLastSelectAid
        val isTimedOut = isTapSessionActive &&
            activeTapStartedAtWallClockMs != 0L &&
            nowWallClockMs - activeTapStartedAtWallClockMs > TAP_CLEAR_IDLE_MS

        if (!isTapSessionActive || isTimedOut || isAidChangedTapStart || (isTapStart && activeTapLastSelectAid.isBlank())) {
            startNewTap(nowWallClockMs, normalized)
            return
        }

        if (isTapSessionActive) {
            activeTapStartedAtWallClockMs = nowWallClockMs
            return
        }
    }

    private fun clearTapExchangeLog() {
        runCatching {
            File(filesDir, APDU_EXCHANGE_LOG_FILE).delete()
        }.onFailure {
            Log.w(TAG, "Failed to clear APDU exchange log", it)
        }
    }

    private fun startNewTap(nowWallClockMs: Long, commandHex: String) {
        currentTapId += 1
        clearTapExchangeLog()
        isTapSessionActive = true
        commandCountInTap = 0
        activeTapStartedAtWallClockMs = nowWallClockMs
        activeTapLastSelectAid = extractSelectAid(commandHex) ?: ""
        appendTapHeader(commandHex)
    }

    private fun appendTapHeader(normalizedCommandHex: String) {
        if (!isDebuggable()) return
        runCatching {
            val file = File(filesDir, APDU_EXCHANGE_LOG_FILE)
            val timestamp = LOG_TIME_FORMAT.format(System.currentTimeMillis())
            val headerAid = if (activeTapLastSelectAid.isBlank()) "none" else activeTapLastSelectAid
            val header = "$timestamp | TAP=$currentTapId START | CMD=${normalizedCommandHex.take(24)} | AID=$headerAid"
            val lines = if (file.exists()) file.readLines() else emptyList()
            val trimmed = if (lines.size >= APDU_LOG_MAX_LINES) {
                lines.takeLast(APDU_LOG_MAX_LINES - 1)
            } else {
                lines
            }
            file.writeText((trimmed + header).joinToString(System.lineSeparator()) + System.lineSeparator())
        }.onFailure {
            Log.w(TAG, "Failed to write tap header", it)
        }
    }

    private fun appendTapFooter(reason: Int) {
        if (!isDebuggable() || !isTapSessionActive) return
        runCatching {
            val file = File(filesDir, APDU_EXCHANGE_LOG_FILE)
            val timestamp = LOG_TIME_FORMAT.format(System.currentTimeMillis())
            val footer = "$timestamp | TAP=$currentTapId END | reason=$reason | cmds=${commandCountInTap}"
            val lines = if (file.exists()) file.readLines() else emptyList()
            val trimmed = if (lines.size >= APDU_LOG_MAX_LINES) {
                lines.takeLast(APDU_LOG_MAX_LINES - 1)
            } else {
                lines
            }
            file.writeText((trimmed + footer).joinToString(System.lineSeparator()) + System.lineSeparator())
        }.onFailure {
            Log.w(TAG, "Failed to write tap footer", it)
        }
    }

    private fun isTapStartCommand(normalizedCommandHex: String): Boolean {
        return normalizedCommandHex.startsWith("00A4") ||
            normalizedCommandHex.startsWith("90A4") ||
            normalizedCommandHex.startsWith("80A4")
    }

    private fun extractSelectAid(normalizedCommandHex: String): String? {
        if (!isTapStartCommand(normalizedCommandHex) || normalizedCommandHex.length < 10) return null
        val lc = normalizedCommandHex.substring(8, 10).toIntOrNull(16) ?: return null
        if (lc == 0) return ""
        val start = 10
        val end = start + (lc * 2)
        if (end > normalizedCommandHex.length) return null
        return normalizedCommandHex.substring(start, end)
    }

    private fun ByteArray?.toHexOrNull(): String? = this?.toHex()?.uppercase()

    companion object {
        private const val TAG = "BrizziHce"
        /** Debug logging hex APDU aktif/inaktif. Jika true, service log ke logcat. */
        private const val LOG_VERBOSE = true
        private const val MAX_APDU_BYTES = 1024
        /** Jendela sliding 1 detik untuk hitung rate-limiting. */
        private const val RATE_WINDOW_MS = 1000L
        /** Maksimum request dalam [RATE_WINDOW_MS]. */
        private const val MAX_COMMANDS_PER_WINDOW = 80
        /** Jarak minimum antar request agar tidak dianggap burst. */
        private const val MIN_COMMAND_INTERVAL_MS = 30L
        private const val DEBUG_MIN_COMMAND_INTERVAL_MS = 0L
        private const val INCIDENT_LOG_THROTTLE_MS = 60_000L
        /** File internal yang menyimpan line-by-line snapshot kesehatan. */
        private const val METRICS_EXPORT_FILE = "security_metrics_report.txt"
        private const val PREFS_NAME = "brizzi_hce_config"
        private const val KEY_TEST_MODE_ENABLED = "hce_test_mode_enabled"
        // Keep this as primary routing AID for emulator activation.
        // Additional routing AIDs can be passed as a comma-separated list via this string if needed.
        private const val ROUTING_AID_HEX =
            "A0000000180F0000018001,D4100000030001,D3600000030003,5A00000301000000,5A00000303000000"
        private const val APDU_EXCHANGE_LOG_FILE = "apdu_exchange_log.txt"
        private const val APDU_LOG_MAX_LINES = 200
        private const val TAP_CLEAR_IDLE_MS = 1_200L
        private val LOG_TIME_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
    }
}
