package com.jrz.nfcapp.service

import android.nfc.cardemulation.HostApduService
import android.os.Bundle
import android.util.Log
import com.jrz.nfcapp.model.toHex

/**
 * HCE-Dienst für NDEF-Tag-Emulation.
 *
 * Implementiert das NFC Forum Type 4 Tag Protokoll vereinfacht:
 *   1. SELECT Application (AID)
 *   2. SELECT Capability Container (CC)
 *   3. READ BINARY CC
 *   4. SELECT NDEF File
 *   5. READ BINARY NDEF
 *
 * Nur geeignet für Systeme, die AID/APDU-basiert kommunizieren.
 * UID-basierte Systeme (Zugangskontrolle) werden NICHT unterstützt.
 */
class HceService : HostApduService() {

    companion object {
        private const val TAG = "HceService"

        // Status words
        private val SW_OK = byteArrayOf(0x90.toByte(), 0x00)
        private val SW_NOT_FOUND = byteArrayOf(0x6A.toByte(), 0x82.toByte())
        private val SW_WRONG_LENGTH = byteArrayOf(0x67.toByte(), 0x00)
        private val SW_UNKNOWN = byteArrayOf(0x6F.toByte(), 0x00)

        // File IDs
        private val CC_FILE_ID = byteArrayOf(0xE1.toByte(), 0x03)
        private val NDEF_FILE_ID = byteArrayOf(0xE1.toByte(), 0x04)

        // Selected file state
        private const val SEL_NONE = 0
        private const val SEL_CC = 1
        private const val SEL_NDEF = 2

        // Singleton payload + log callback (set from HceFragment)
        @Volatile var ndefPayload: ByteArray = buildDefaultNdefPayload()
        var apduLogCallback: ((String) -> Unit)? = null

        fun buildDefaultNdefPayload(): ByteArray {
            // NDEF Text record: "NFC Tool"
            val text = "NFC Tool"
            val lang = "de"
            val langBytes = lang.toByteArray()
            val textBytes = text.toByteArray()
            val recordPayload = ByteArray(1 + langBytes.size + textBytes.size)
            recordPayload[0] = langBytes.size.toByte()
            langBytes.copyInto(recordPayload, 1)
            textBytes.copyInto(recordPayload, 1 + langBytes.size)

            // NDEF record header: MB=1 ME=1 SR=1 TNF=1 (Well-Known)
            val typeBytes = byteArrayOf(0x54) // 'T'
            val recordHeader = byteArrayOf(
                0xD1.toByte(),              // MB=1 ME=1 SR=1 TNF=0x01
                typeBytes.size.toByte(),    // type length
                recordPayload.size.toByte() // short record payload length
            )
            val ndefRecord = recordHeader + typeBytes + recordPayload

            // NDEF file: 2-byte length + record
            return byteArrayOf(
                0x00, ndefRecord.size.toByte()
            ) + ndefRecord
        }
    }

    private var selectedFile = SEL_NONE

    override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray {
        val hex = commandApdu.toHex()
        log("CMD: $hex")

        if (commandApdu.size < 4) {
            log("RSP: Wrong length")
            return SW_WRONG_LENGTH
        }

        val cla = commandApdu[0].toInt() and 0xFF
        val ins = commandApdu[1].toInt() and 0xFF
        val p1 = commandApdu[2].toInt() and 0xFF
        val p2 = commandApdu[3].toInt() and 0xFF

        return when {
            ins == 0xA4 -> handleSelect(commandApdu, p1, p2)
            ins == 0xB0 -> handleReadBinary(commandApdu, p1, p2)
            else -> {
                log("RSP: Unknown INS ${"%02X".format(ins)}")
                SW_UNKNOWN
            }
        }
    }

    private fun handleSelect(apdu: ByteArray, p1: Int, p2: Int): ByteArray {
        return when {
            // SELECT Application by AID (P1=0x04)
            p1 == 0x04 -> {
                log("RSP: SELECT Application -> OK")
                selectedFile = SEL_NONE
                SW_OK
            }
            // SELECT File by ID (P1=0x00 or 0x02)
            p1 == 0x00 || p1 == 0x02 -> {
                if (apdu.size < 7) return SW_WRONG_LENGTH
                val fileId = byteArrayOf(apdu[5], apdu[6])
                when {
                    fileId.contentEquals(CC_FILE_ID) -> {
                        selectedFile = SEL_CC
                        log("RSP: SELECT CC -> OK")
                        SW_OK
                    }
                    fileId.contentEquals(NDEF_FILE_ID) -> {
                        selectedFile = SEL_NDEF
                        log("RSP: SELECT NDEF -> OK")
                        SW_OK
                    }
                    else -> {
                        log("RSP: SELECT unknown file ${fileId.toHex()} -> NOT FOUND")
                        SW_NOT_FOUND
                    }
                }
            }
            else -> {
                log("RSP: SELECT unknown P1=${"%02X".format(p1)} -> NOT FOUND")
                SW_NOT_FOUND
            }
        }
    }

    private fun handleReadBinary(apdu: ByteArray, p1: Int, p2: Int): ByteArray {
        val offset = (p1 shl 8) or p2
        val length = if (apdu.size >= 5) apdu[4].toInt() and 0xFF else 15

        return when (selectedFile) {
            SEL_CC -> {
                val cc = buildCapabilityContainer()
                val end = minOf(offset + length, cc.size)
                if (offset >= cc.size) return SW_WRONG_LENGTH
                val data = cc.copyOfRange(offset, end)
                log("RSP: READ CC offset=$offset len=$length -> ${data.toHex()} 90 00")
                data + SW_OK
            }
            SEL_NDEF -> {
                val ndef = ndefPayload
                val end = minOf(offset + length, ndef.size)
                if (offset >= ndef.size) return SW_WRONG_LENGTH
                val data = ndef.copyOfRange(offset, end)
                log("RSP: READ NDEF offset=$offset len=$length -> ${data.toHex()} 90 00")
                data + SW_OK
            }
            else -> {
                log("RSP: READ without SELECT -> NOT FOUND")
                SW_NOT_FOUND
            }
        }
    }

    private fun buildCapabilityContainer(): ByteArray {
        val ndefSize = ndefPayload.size
        return byteArrayOf(
            0x00, 0x0F,             // CC length = 15 bytes
            0x20,                   // Mapping version 2.0
            0x00, 0x3B,             // MLe (max R-APDU): 59 bytes
            0x00, 0x34,             // MLc (max C-APDU): 52 bytes
            0x04,                   // NDEF File Control TLV tag
            0x06,                   // TLV length
            NDEF_FILE_ID[0], NDEF_FILE_ID[1], // File ID
            0x00, ndefSize.toByte(),// Max NDEF size
            0x00,                   // Read access: open
            0xFF.toByte()           // Write access: no write (read-only HCE)
        )
    }

    private fun log(msg: String) {
        Log.d(TAG, msg)
        apduLogCallback?.invoke(msg)
    }

    override fun onDeactivated(reason: Int) {
        val why = if (reason == DEACTIVATION_LINK_LOSS) "Link-Verlust" else "Neues Tag"
        log("--- Deaktiviert: $why ---")
        selectedFile = SEL_NONE
    }
}
