package com.jrz.nfcapp.util

import android.nfc.NdefMessage
import android.nfc.NdefRecord
import android.nfc.Tag
import android.nfc.tech.*
import com.jrz.nfcapp.model.TagDump
import com.jrz.nfcapp.model.toHex
import java.io.IOException

object NfcHelper {

    // Standard MIFARE Classic default keys
    private val MIFARE_DEFAULT_KEYS = listOf(
        byteArrayOf(0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte()),
        byteArrayOf(0xA0.toByte(), 0xA1.toByte(), 0xA2.toByte(), 0xA3.toByte(), 0xA4.toByte(), 0xA5.toByte()),
        byteArrayOf(0xD3.toByte(), 0xF7.toByte(), 0xD3.toByte(), 0xF7.toByte(), 0xD3.toByte(), 0xF7.toByte()),
        byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
        byteArrayOf(0xB0.toByte(), 0xB1.toByte(), 0xB2.toByte(), 0xB3.toByte(), 0xB4.toByte(), 0xB5.toByte()),
        byteArrayOf(0x4D.toByte(), 0x3A.toByte(), 0x99.toByte(), 0xC3.toByte(), 0x51.toByte(), 0xDD.toByte()),
        byteArrayOf(0x1A.toByte(), 0x98.toByte(), 0x2C.toByte(), 0x7E.toByte(), 0x45.toByte(), 0x9A.toByte()),
        byteArrayOf(0xAA.toByte(), 0xBB.toByte(), 0xCC.toByte(), 0xDD.toByte(), 0xEE.toByte(), 0xFF.toByte())
    )

    fun readTag(tag: Tag): TagDump {
        val uid = tag.id.toHex()
        val techList = tag.techList.toList()
        var ndefMsg: NdefMessage? = null
        var ndefCapacity = 0
        var ndefMaxSize = 0
        var ndefWritable = false
        val rawPages = mutableListOf<ByteArray>()
        var tagType = detectTagType(techList)
        val log = StringBuilder()

        log.appendLine("UID: $uid")
        log.appendLine("Technologien: ${techList.map { it.substringAfterLast('.') }}")

        // --- NDEF lesen ---
        if (techList.contains(Ndef::class.java.name)) {
            log.appendLine("→ Versuche NDEF lesen…")
            val ndef = Ndef.get(tag)
            try {
                ndef.connect()
                ndefMsg = ndef.cachedNdefMessage
                if (ndefMsg == null) {
                    log.appendLine("  cachedNdefMessage null, lese live…")
                    ndefMsg = ndef.ndefMessage
                }
                ndefMaxSize = ndef.maxSize
                ndefWritable = ndef.isWritable
                ndefCapacity = ndef.maxSize
                tagType = ndef.type ?: tagType
                if (ndefMsg != null) {
                    log.appendLine("  NDEF OK: ${ndefMsg.records.size} Record(s), ${ndefMsg.byteArrayLength} Bytes")
                } else {
                    log.appendLine("  NDEF leer (kein Inhalt)")
                }
            } catch (e: Exception) {
                log.appendLine("  NDEF Fehler: ${e.javaClass.simpleName}: ${e.message}")
            } finally {
                try { ndef.close() } catch (_: Exception) {}
            }
        }

        // --- MIFARE Ultralight lesen ---
        if (techList.contains(MifareUltralight::class.java.name)) {
            log.appendLine("→ Versuche MifareUltralight lesen…")
            val ul = MifareUltralight.get(tag)
            try {
                ul.connect()
                val pageCount = when (ul.type) {
                    MifareUltralight.TYPE_ULTRALIGHT -> 16
                    MifareUltralight.TYPE_ULTRALIGHT_C -> 48
                    else -> 45  // NTAG213=45, NTAG215=135, NTAG216=231 — start conservative
                }
                log.appendLine("  Typ: ${ul.type}, lese $pageCount Pages…")
                var readCount = 0
                for (page in 0 until pageCount) {
                    try {
                        rawPages.add(ul.readPages(page).copyOfRange(0, 4))
                        readCount++
                    } catch (e: IOException) {
                        log.appendLine("  Stop bei Page $page: ${e.message}")
                        break
                    }
                }
                log.appendLine("  $readCount Pages gelesen")
            } catch (e: Exception) {
                log.appendLine("  Ultralight Fehler: ${e.javaClass.simpleName}: ${e.message}")
            } finally {
                try { ul.close() } catch (_: Exception) {}
            }
        }

        // --- MIFARE Classic lesen (Default-Keys) ---
        if (techList.contains(MifareClassic::class.java.name)) {
            log.appendLine("→ Versuche MifareClassic mit Default-Keys…")
            val mc = MifareClassic.get(tag)
            try {
                mc.connect()
                val sectorCount = mc.sectorCount
                log.appendLine("  ${mc.type} — $sectorCount Sektoren, ${mc.blockCount} Blöcke")
                var unlockedSectors = 0
                for (sector in 0 until sectorCount) {
                    var authenticated = false
                    for (key in MIFARE_DEFAULT_KEYS) {
                        try {
                            if (mc.authenticateSectorWithKeyA(sector, key) ||
                                mc.authenticateSectorWithKeyB(sector, key)) {
                                authenticated = true
                                break
                            }
                        } catch (_: Exception) {}
                    }
                    if (authenticated) {
                        unlockedSectors++
                        val blockCount = mc.getBlockCountInSector(sector)
                        val firstBlock = mc.sectorToBlock(sector)
                        for (b in 0 until blockCount) {
                            try {
                                val blockData = mc.readBlock(firstBlock + b)
                                rawPages.add(blockData)
                            } catch (e: IOException) {
                                rawPages.add(ByteArray(16) { 0xFF.toByte() })
                            }
                        }
                    } else {
                        log.appendLine("  Sektor $sector: kein Default-Key passt (verschlüsselt)")
                        // Füge Platzhalt-Blöcke ein damit Sektornummern stimmen
                        val blockCount = mc.getBlockCountInSector(sector)
                        repeat(blockCount) { rawPages.add(ByteArray(16) { 0xEE.toByte() }) }
                    }
                }
                log.appendLine("  $unlockedSectors/$sectorCount Sektoren entsperrt")
            } catch (e: Exception) {
                log.appendLine("  MifareClassic Fehler: ${e.javaClass.simpleName}: ${e.message}")
            } finally {
                try { mc.close() } catch (_: Exception) {}
            }
        }

        // --- NfcA Diagnostik (ATQA/SAK) ---
        if (techList.contains(NfcA::class.java.name)) {
            val nfcA = NfcA.get(tag)
            try {
                nfcA.connect()
                val atqa = nfcA.atqa.toHex()
                val sak = "%02X".format(nfcA.sak)
                log.appendLine("→ NfcA: ATQA=$atqa  SAK=$sak")
            } catch (e: Exception) {
                log.appendLine("→ NfcA Diagnostik Fehler: ${e.message}")
            } finally {
                try { nfcA.close() } catch (_: Exception) {}
            }
        }

        // --- NFC-V (ISO 15693) lesen ---
        if (techList.contains(NfcV::class.java.name) && rawPages.isEmpty()) {
            log.appendLine("→ Versuche NFC-V (ISO 15693) lesen…")
            val nfcV = NfcV.get(tag)
            try {
                nfcV.connect()
                val dsfId = nfcV.dsfId
                val responseFlags = nfcV.responseFlags
                log.appendLine("  DSFID=$dsfId  ResponseFlags=$responseFlags")

                // READ_SINGLE_BLOCK: flags=0x02, cmd=0x20, blockNum
                // Flags 0x02 = high data rate, non-addressed mode
                var block = 0
                var consecutiveErrors = 0
                while (block < 256 && consecutiveErrors < 3) {
                    try {
                        val cmd = byteArrayOf(0x02, 0x20, block.toByte())
                        val response = nfcV.transceive(cmd)
                        if (response.isNotEmpty() && (response[0].toInt() and 0x01) == 0) {
                            // Erfolg: response[0] = flags, rest = block data
                            val blockData = response.copyOfRange(1, response.size)
                            rawPages.add(blockData)
                            consecutiveErrors = 0
                        } else {
                            consecutiveErrors++
                        }
                    } catch (e: IOException) {
                        consecutiveErrors++
                        if (block == 0) {
                            log.appendLine("  Block 0 Fehler: ${e.message} — versuche addressed mode…")
                            // Versuche addressed mode (flags=0x22 + UID)
                            try {
                                val uidBytes = tag.id
                                val cmd = byteArrayOf(0x22, 0x20) + uidBytes + byteArrayOf(0x00)
                                val response = nfcV.transceive(cmd)
                                if (response.isNotEmpty() && (response[0].toInt() and 0x01) == 0) {
                                    rawPages.add(response.copyOfRange(1, response.size))
                                    consecutiveErrors = 0
                                    log.appendLine("  Addressed mode erfolgreich")
                                }
                            } catch (_: Exception) {}
                        }
                    }
                    block++
                }
                log.appendLine("  ${rawPages.size} Blöcke gelesen")
            } catch (e: Exception) {
                log.appendLine("  NfcV Fehler: ${e.javaClass.simpleName}: ${e.message}")
            } finally {
                try { nfcV.close() } catch (_: Exception) {}
            }
        }

        // --- IsoDep / ISO 14443-4 ---
        if (techList.contains(IsoDep::class.java.name) && ndefMsg == null && rawPages.isEmpty()) {
            log.appendLine("→ IsoDep erkannt — kein automatisches Lesen möglich (proprietäres Protokoll)")
        }

        val rawBytes = when {
            rawPages.isNotEmpty() -> rawPages.flatMap { it.toList() }.toByteArray()
            ndefMsg != null -> ndefMsg.toByteArray()
            else -> ByteArray(0)
        }

        log.appendLine("─── Gesamt: ${rawBytes.size} Bytes ───")

        return TagDump(
            uid = uid,
            tagType = tagType,
            techList = techList,
            ndefMessage = ndefMsg,
            ndefCapacity = ndefCapacity,
            ndefMaxSize = ndefMaxSize,
            isNdefWritable = ndefWritable,
            rawPages = rawPages,
            rawBytes = rawBytes,
            debugLog = log.toString().trimEnd()
        )
    }

    fun writeNdef(tag: Tag, message: NdefMessage) {
        if (tag.techList.contains(Ndef::class.java.name)) {
            val ndef = Ndef.get(tag)
            ndef.connect()
            try {
                if (!ndef.isWritable) throw IOException("Tag ist schreibgeschützt")
                if (ndef.maxSize < message.byteArrayLength) {
                    throw IOException("Tag zu klein: ${ndef.maxSize} Bytes verfügbar, ${message.byteArrayLength} benötigt")
                }
                ndef.writeNdefMessage(message)
            } finally {
                ndef.close()
            }
        } else if (tag.techList.contains(NdefFormatable::class.java.name)) {
            val formatable = NdefFormatable.get(tag)
            formatable.connect()
            try {
                formatable.format(message)
            } finally {
                formatable.close()
            }
        } else {
            throw IOException("Tag unterstützt kein NDEF-Schreiben")
        }
    }

    fun writeUltralight(tag: Tag, pages: List<ByteArray>) {
        val ul = MifareUltralight.get(tag) ?: throw IOException("Kein Ultralight-Tag")
        ul.connect()
        try {
            pages.forEachIndexed { i, page ->
                ul.writePage(i, page)
            }
        } finally {
            ul.close()
        }
    }

    fun writeNfcV(tag: Tag, blocks: List<ByteArray>) {
        val nfcV = NfcV.get(tag) ?: throw IOException("Kein NFC-V Tag")
        nfcV.connect()
        try {
            blocks.forEachIndexed { blockNum, data ->
                // WRITE_SINGLE_BLOCK: Flags=0x02, Cmd=0x21, BlockNum, Data
                val cmd = byteArrayOf(0x02, 0x21, blockNum.toByte()) + data
                val response = nfcV.transceive(cmd)
                if (response.isEmpty() || (response[0].toInt() and 0x01) != 0) {
                    val errCode = if (response.size >= 2) "%02X".format(response[1]) else "?"
                    throw IOException("Block $blockNum Fehler (ISO 15693 Error $errCode)")
                }
            }
        } finally {
            try { nfcV.close() } catch (_: Exception) {}
        }
    }

    fun detectWriteMethod(tag: Tag): WriteMethod {
        val techs = tag.techList.toList()
        return when {
            techs.contains(NfcV::class.java.name) -> WriteMethod.NFC_V
            techs.contains(MifareUltralight::class.java.name) -> WriteMethod.ULTRALIGHT
            techs.contains(MifareClassic::class.java.name) -> WriteMethod.MIFARE_CLASSIC
            techs.contains(Ndef::class.java.name) -> WriteMethod.NDEF
            techs.contains(NdefFormatable::class.java.name) -> WriteMethod.NDEF_FORMATABLE
            else -> WriteMethod.UNSUPPORTED
        }
    }

    enum class WriteMethod { NDEF, NDEF_FORMATABLE, ULTRALIGHT, NFC_V, MIFARE_CLASSIC, UNSUPPORTED }

    fun buildTextNdef(text: String, locale: String = "de"): NdefMessage {
        val langBytes = locale.toByteArray(Charsets.US_ASCII)
        val textBytes = text.toByteArray(Charsets.UTF_8)
        val payload = ByteArray(1 + langBytes.size + textBytes.size)
        payload[0] = langBytes.size.toByte()
        langBytes.copyInto(payload, 1)
        textBytes.copyInto(payload, 1 + langBytes.size)
        val record = NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, ByteArray(0), payload)
        return NdefMessage(arrayOf(record))
    }

    fun buildUriNdef(uri: String): NdefMessage {
        val record = NdefRecord.createUri(android.net.Uri.parse(uri))
        return NdefMessage(arrayOf(record))
    }

    fun buildRawNdef(hexPayload: String): NdefMessage {
        val bytes = hexPayload.replace(" ", "").replace(":", "")
            .chunked(2).map { it.toInt(16).toByte() }.toByteArray()
        val record = NdefRecord(NdefRecord.TNF_UNKNOWN, ByteArray(0), ByteArray(0), bytes)
        return NdefMessage(arrayOf(record))
    }

    private fun detectTagType(techList: List<String>): String = when {
        techList.contains(MifareUltralight::class.java.name) -> "MIFARE Ultralight / NTAG"
        techList.contains(MifareClassic::class.java.name) -> "MIFARE Classic"
        techList.contains(IsoDep::class.java.name) -> "ISO-DEP (ISO 14443-4)"
        techList.contains(NfcF::class.java.name) -> "NFC-F (FeliCa)"
        techList.contains(NfcV::class.java.name) -> "NFC-V (ISO 15693)"
        techList.contains(Ndef::class.java.name) -> "NDEF"
        techList.contains(NfcA::class.java.name) -> "NFC-A (ISO 14443-3A)"
        techList.contains(NfcB::class.java.name) -> "NFC-B (ISO 14443-3B)"
        else -> "Unbekannt"
    }
}
