package de.softwareapp_hb.privateqrscanner.util

import at.bitfire.vcard4android.Contact
import java.io.StringReader
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone

data class ParsedContact(
    val fullName: String? = null,
    val givenName: String? = null,
    val familyName: String? = null,
    val organization: String? = null,
    val title: String? = null,
    val phones: List<String> = emptyList(),
    val emails: List<String> = emptyList(),
    val address: String? = null,
    val note: String? = null
)

data class ParsedCalendarEvent(
    val title: String? = null,
    val description: String? = null,
    val location: String? = null,
    val startMillis: Long? = null,
    val endMillis: Long? = null,
    val allDay: Boolean = false
)

data class ParsedWifiNetwork(
    val ssid: String? = null,
    val security: String? = null,
    val password: String? = null,
    val hidden: Boolean? = null
)

object ScanContentParsers {
    fun extractPhoneNumber(raw: String): String {
        return raw.substringAfter("tel:", raw)
            .substringBefore('?')
            .trim()
    }

    fun parseSms(raw: String): Pair<String, String?> {
        val cleaned = raw.trim()
        return if (cleaned.startsWith("SMSTO:", ignoreCase = true)) {
            val parts = cleaned.split(':', limit = 3)
            val number = parts.getOrNull(1).orEmpty()
            val body = parts.getOrNull(2)
            number to body
        } else {
            cleaned to null
        }
    }

    fun extractEmail(raw: String): String {
        val cleaned = raw.trim()
        if (cleaned.startsWith("mailto:", ignoreCase = true)) {
            return cleaned.substringAfter("mailto:").substringBefore('?').trim()
        }
        val match = Regex("[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+")
            .find(cleaned)
        return match?.value ?: cleaned
    }

    fun parseContact(raw: String): ParsedContact? {
        val cleaned = raw.trim()
        if (cleaned.isBlank()) return null

        return when {
            cleaned.contains("BEGIN:VCARD", ignoreCase = true) -> parseVCard(cleaned)
            cleaned.startsWith("MECARD:", ignoreCase = true) -> parseMeCard(cleaned)
            else -> null
        }
    }

    fun parseCalendarEvent(raw: String): ParsedCalendarEvent? {
        val cleaned = raw.trim()
        if (cleaned.isBlank()) return null
        if (!cleaned.contains("BEGIN:VEVENT", ignoreCase = true)) return null

        val unfolded = unfoldLines(cleaned)
        val lines = unfolded.lines()

        var inEvent = false
        var summary: String? = null
        var description: String? = null
        var location: String? = null
        var startMillis: Long? = null
        var endMillis: Long? = null
        var allDay = false

        lines.forEach { line ->
            val entry = parseKeyValueLine(line) ?: return@forEach
            val key = entry.key.uppercase(Locale.US)
            val value = entry.value

            if (key == "BEGIN" && value.equals("VEVENT", ignoreCase = true)) {
                inEvent = true
                return@forEach
            }
            if (key == "END" && value.equals("VEVENT", ignoreCase = true)) {
                inEvent = false
                return@forEach
            }
            if (!inEvent) return@forEach

            when {
                key == "SUMMARY" -> summary = value
                key == "DESCRIPTION" -> description = value
                key == "LOCATION" -> location = value
                key.startsWith("DTSTART") -> {
                    val tzId = key.substringAfter("TZID=", missingDelimiterValue = "")
                        .substringBefore(';')
                        .ifBlank { null }
                    val parsed = parseIcsDate(value, tzId)
                    startMillis = parsed?.first
                    if (parsed?.second == true) allDay = true
                }
                key.startsWith("DTEND") -> {
                    val tzId = key.substringAfter("TZID=", missingDelimiterValue = "")
                        .substringBefore(';')
                        .ifBlank { null }
                    val parsed = parseIcsDate(value, tzId)
                    endMillis = parsed?.first
                    if (parsed?.second == true) allDay = true
                }
            }
        }

        if (summary == null && description == null && location == null && startMillis == null && endMillis == null) {
            return null
        }
        return ParsedCalendarEvent(
            title = summary,
            description = description,
            location = location,
            startMillis = startMillis,
            endMillis = endMillis,
            allDay = allDay
        )
    }

    fun parseWifi(raw: String): ParsedWifiNetwork? {
        val cleaned = raw.trim()
        if (!cleaned.startsWith("WIFI:", ignoreCase = true)) return null
        val payload = cleaned.substringAfter("WIFI:", "").trim().trimEnd(';')
        if (payload.isBlank()) return null

        var ssid: String? = null
        var security: String? = null
        var password: String? = null
        var hidden: Boolean? = null

        splitByUnescaped(payload, ';').forEach { token ->
            if (token.isBlank()) return@forEach
            val idx = indexOfUnescaped(token, ':')
            if (idx <= 0) return@forEach

            val key = token.substring(0, idx).trim().uppercase(Locale.US)
            val value = unescapeWifiValue(token.substring(idx + 1).trim())
            when (key) {
                "S" -> ssid = value.ifBlank { null }
                "T" -> security = value.ifBlank { null }
                "P" -> password = value.ifBlank { null }
                "H", "HIDDEN" -> hidden = value.equals("true", ignoreCase = true) || value == "1"
            }
        }

        if (ssid == null && security == null && password == null && hidden == null) return null
        return ParsedWifiNetwork(
            ssid = ssid,
            security = security,
            password = password,
            hidden = hidden
        )
    }

    private fun parseVCard(raw: String): ParsedContact? {
        val fromLibrary = parseVCardWithLibrary(raw)
        val fromFallback = parseVCardFallback(raw)
        return mergeParsedContacts(primary = fromLibrary, secondary = fromFallback)
    }

    private fun parseVCardWithLibrary(raw: String): ParsedContact? {
        val contacts = try {
            Contact.fromReader(
                reader = StringReader(raw),
                jCard = false,
                downloader = null
            )
        } catch (_: Throwable) {
            return null
        }
        val contact = contacts.firstOrNull() ?: return null

        val fullName = listOfNotNull(
            contact.prefix?.trim().takeIf { !it.isNullOrBlank() },
            contact.givenName?.trim().takeIf { !it.isNullOrBlank() },
            contact.middleName?.trim().takeIf { !it.isNullOrBlank() },
            contact.familyName?.trim().takeIf { !it.isNullOrBlank() },
            contact.suffix?.trim().takeIf { !it.isNullOrBlank() }
        ).joinToString(" ").ifBlank { contact.displayName?.trim().takeIf { !it.isNullOrBlank() } }

        val organization = contact.organization?.values
            ?.map { it?.trim().orEmpty() }
            ?.filter { it.isNotBlank() }
            ?.joinToString(" / ")
            ?.ifBlank { null }

        val title = contact.jobTitle?.trim().takeIf { !it.isNullOrBlank() }

        val phones = contact.phoneNumbers
            .mapNotNull { it.property.text?.trim() }
            .filter { it.isNotBlank() }
            .distinct()

        val emails = contact.emails
            .mapNotNull { it.property.value?.trim() }
            .filter { it.isNotBlank() }
            .distinct()

        val address = contact.addresses.firstOrNull()?.property?.let { adr ->
            listOf(
                adr.streetAddress,
                adr.extendedAddress,
                adr.locality,
                adr.region,
                adr.postalCode,
                adr.country
            ).mapNotNull { it?.trim() }
                .filter { it.isNotBlank() }
                .joinToString(", ")
                .ifBlank { null }
        }

        val note = contact.note?.trim().takeIf { !it.isNullOrBlank() }

        if (
            fullName == null &&
            organization == null &&
            title == null &&
            phones.isEmpty() &&
            emails.isEmpty() &&
            address == null &&
            note == null
        ) return null

        return ParsedContact(
            fullName = fullName,
            givenName = contact.givenName?.trim().takeIf { !it.isNullOrBlank() },
            familyName = contact.familyName?.trim().takeIf { !it.isNullOrBlank() },
            organization = organization,
            title = title,
            phones = phones,
            emails = emails,
            address = address,
            note = note
        )
    }

    private fun parseVCardFallback(raw: String): ParsedContact? {
        val unfolded = unfoldLines(raw)
        val lines = unfolded.lines()

        var fullName: String? = null
        var givenName: String? = null
        var familyName: String? = null
        var organization: String? = null
        var title: String? = null
        val phones = mutableListOf<String>()
        val emails = mutableListOf<String>()
        var address: String? = null
        var note: String? = null

        lines.forEach { line ->
            val entry = parseKeyValueLine(line) ?: return@forEach
            val key = entry.key.uppercase(Locale.US)
            val property = key.substringBefore(';')
            val value = unescapeVCardValue(entry.value)
            when {
                property == "FN" -> fullName = value
                property == "N" && fullName == null -> {
                    val nameParts = parseVCardName(value)
                    fullName = nameParts.fullName
                    givenName = nameParts.givenName
                    familyName = nameParts.familyName
                }
                property == "ORG" -> {
                    organization = value.split(';')
                        .map { it.trim() }
                        .filter { it.isNotBlank() }
                        .joinToString(" / ")
                        .ifBlank { null }
                }
                property == "TITLE" -> title = value
                property == "TEL" -> phones += value
                property == "EMAIL" -> emails += value
                property == "ADR" && address == null -> {
                    address = value.split(';')
                        .map { it.trim() }
                        .filter { it.isNotBlank() }
                        .joinToString(", ")
                        .ifBlank { null }
                }
                property == "NOTE" -> note = value
            }
        }

        if (
            fullName == null &&
            organization == null &&
            title == null &&
            phones.isEmpty() &&
            emails.isEmpty() &&
            address == null &&
            note == null
        ) return null

        return ParsedContact(
            fullName = fullName,
            givenName = givenName,
            familyName = familyName,
            organization = organization,
            title = title,
            phones = phones.distinct(),
            emails = emails.distinct(),
            address = address,
            note = note
        )
    }

    private fun parseMeCard(raw: String): ParsedContact? {
        val payload = raw.substringAfter("MECARD:", "").trim().trimEnd(';')
        if (payload.isBlank()) return null

        var fullName: String? = null
        var givenName: String? = null
        var familyName: String? = null
        var organization: String? = null
        var title: String? = null
        val phones = mutableListOf<String>()
        val emails = mutableListOf<String>()
        var address: String? = null
        var note: String? = null

        payload.split(';').forEach { token ->
            val key = token.substringBefore(':', "").uppercase(Locale.US)
            val value = token.substringAfter(':', "").trim()
            if (value.isBlank()) return@forEach
            when (key) {
                "N" -> {
                    familyName = value.substringBefore(',', "").trim().ifBlank { null }
                    givenName = value.substringAfter(',', "").trim().ifBlank { null }
                    fullName = listOf(givenName, familyName)
                        .filterNotNull()
                        .filter { it.isNotBlank() }
                        .joinToString(" ")
                        .ifBlank { null }
                }
                "TEL" -> phones += value
                "EMAIL" -> emails += value
                "ADR" -> address = value
                "ORG" -> organization = value
                "TITLE" -> title = value
                "NOTE" -> note = value
            }
        }

        if (
            fullName == null &&
            organization == null &&
            title == null &&
            phones.isEmpty() &&
            emails.isEmpty() &&
            address == null &&
            note == null
        ) {
            return null
        }
        return ParsedContact(
            fullName = fullName,
            givenName = givenName,
            familyName = familyName,
            organization = organization,
            title = title,
            phones = phones.distinct(),
            emails = emails.distinct(),
            address = address,
            note = note
        )
    }

    private data class KeyValueLine(
        val key: String,
        val value: String
    )

    private fun parseKeyValueLine(line: String): KeyValueLine? {
        val delimiter = line.indexOf(':')
        if (delimiter <= 0) return null
        val key = line.substring(0, delimiter).trim()
        val value = line.substring(delimiter + 1).trim()
        if (key.isBlank() || value.isBlank()) return null
        return KeyValueLine(key = key, value = value)
    }

    private fun unfoldLines(raw: String): String {
        return raw
            .replace("\r\n", "\n")
            .replace(Regex("\n[ \t]"), "")
    }

    private data class NameParts(
        val fullName: String?,
        val givenName: String?,
        val familyName: String?
    )

    private fun parseVCardName(nValue: String): NameParts {
        val parts = nValue.split(';')
        val family = parts.getOrNull(0).orEmpty().trim()
        val given = parts.getOrNull(1).orEmpty().trim()
        val additional = parts.getOrNull(2).orEmpty().trim()
        val prefix = parts.getOrNull(3).orEmpty().trim()
        val suffix = parts.getOrNull(4).orEmpty().trim()
        val full = listOf(prefix, given, additional, family, suffix)
            .filter { it.isNotBlank() }
            .joinToString(" ")
            .ifBlank { null }
        return NameParts(
            fullName = full,
            givenName = given.ifBlank { null },
            familyName = family.ifBlank { null }
        )
    }

    private fun unescapeVCardValue(value: String): String {
        return value
            .replace("\\n", "\n")
            .replace("\\N", "\n")
            .replace("\\;", ";")
            .replace("\\,", ",")
            .replace("\\\\", "\\")
            .trim()
    }

    private fun parseIcsDate(value: String, tzId: String?): Pair<Long, Boolean>? {
        val trimmed = value.trim()
        if (trimmed.isBlank()) return null

        if (trimmed.length == 8 && trimmed.all { it.isDigit() }) {
            val format = SimpleDateFormat("yyyyMMdd", Locale.US).apply {
                timeZone = timeZoneOrDefault(tzId)
                isLenient = false
            }
            val millis = format.parse(trimmed)?.time ?: return null
            return millis to true
        }

        if (trimmed.endsWith("Z")) {
            val format = SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US).apply {
                timeZone = TimeZone.getTimeZone("UTC")
                isLenient = false
            }
            val millis = format.parse(trimmed)?.time ?: return null
            return millis to false
        }

        val withSeconds = if (trimmed.length == 13) "${trimmed}00" else trimmed
        val format = SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.US).apply {
            timeZone = timeZoneOrDefault(tzId)
            isLenient = false
        }
        val millis = format.parse(withSeconds)?.time ?: return null
        return millis to false
    }

    private fun timeZoneOrDefault(tzId: String?): TimeZone {
        if (tzId.isNullOrBlank()) return TimeZone.getDefault()
        return TimeZone.getTimeZone(tzId)
    }

    private fun splitByUnescaped(input: String, separator: Char): List<String> {
        val out = mutableListOf<String>()
        val current = StringBuilder()
        var escaped = false
        input.forEach { ch ->
            when {
                escaped -> {
                    current.append(ch)
                    escaped = false
                }
                ch == '\\' -> escaped = true
                ch == separator -> {
                    out += current.toString()
                    current.setLength(0)
                }
                else -> current.append(ch)
            }
        }
        out += current.toString()
        return out
    }

    private fun indexOfUnescaped(input: String, needle: Char): Int {
        var escaped = false
        input.forEachIndexed { index, ch ->
            when {
                escaped -> escaped = false
                ch == '\\' -> escaped = true
                ch == needle -> return index
            }
        }
        return -1
    }

    private fun unescapeWifiValue(value: String): String {
        return value
            .replace("\\\\", "\\")
            .replace("\\;", ";")
            .replace("\\:", ":")
            .replace("\\,", ",")
    }

    private fun mergeParsedContacts(
        primary: ParsedContact?,
        secondary: ParsedContact?
    ): ParsedContact? {
        if (primary == null) return secondary
        if (secondary == null) return primary

        val phones = (primary.phones + secondary.phones).filter { it.isNotBlank() }.distinct()
        val emails = (primary.emails + secondary.emails).filter { it.isNotBlank() }.distinct()

        return ParsedContact(
            fullName = primary.fullName ?: secondary.fullName,
            givenName = primary.givenName ?: secondary.givenName,
            familyName = primary.familyName ?: secondary.familyName,
            organization = primary.organization ?: secondary.organization,
            title = primary.title ?: secondary.title,
            phones = phones,
            emails = emails,
            address = primary.address ?: secondary.address,
            note = primary.note ?: secondary.note
        )
    }
}
