package com.example.data.engine import com.example.data.local.entity.ContactEntity data class ImportRowResult( val rowNumber: Int, val contact: ContactEntity?, val isValid: Boolean, val isDuplicate: Boolean, val hasConsent: Boolean, val validationErrors: List ) data class ImportValidationReport( val totalRowsProcessed: Int, val validCount: Int, val duplicateCount: Int, val nonConsentingCount: Int, val invalidRowsCount: Int, val validContacts: List, val rowResults: List ) object ContactImportValidator { const val SAMPLE_CSV_TEMPLATE = """country_code,mobile_number,customer_name,district,state,pin_code,whatsapp_opt_in,consent_source,consent_date,customer_notes 91,9355321162,Mrs Bimla,Bhiwani,Haryana,127021,true,DEWA Customer Care,2026-10-16,Priority Gram Udhyog customer 91,9315321162,Mr. Dinesh Kumar,Bhiwani,Haryana,127021,true,Direct WhatsApp,2026-10-16,Verified customer 91,6000072002,Vikash Verma,Bhiwani,Haryana,127021,true,Customer Portal,2026-10-16,Opted-in consumer 91,9812405817,Ajay Kumar,Bhiwani,Haryana,127021,true,Customer Care,2026-10-16,Bulk consumer inquiries 91,7600910000,Sudhir Malik,Bhiwani,Haryana,127021,true,Direct Web Form,2026-10-16,Consent confirmed """ fun parseCsvContent( csvText: String, existingPhones: Set, defaultCategory: String = "Consumer Upload List", defaultCategoryId: Long = 1 ): ImportValidationReport { val lines = csvText.lines().map { it.trim() }.filter { it.isNotEmpty() } if (lines.isEmpty()) { return ImportValidationReport(0, 0, 0, 0, 0, emptyList(), emptyList()) } // Header parsing val headerLine = lines.first() val headers = parseCsvRow(headerLine).map { it.lowercase().trim() } val colCountryCode = headers.indexOfFirst { it.contains("country") || it.contains("code") } val colMobile = headers.indexOfFirst { it.contains("mobile") || it.contains("phone") || it.contains("number") } val colName = headers.indexOfFirst { it.contains("customer") || it.contains("consumer") || it.contains("name") } val colDistrict = headers.indexOfFirst { it.contains("district") } val colCity = headers.indexOfFirst { it.contains("city") } val colState = headers.indexOfFirst { it.contains("state") } val colPinCode = headers.indexOfFirst { it.contains("pin") || it.contains("postal") || it.contains("zip") } val colBusiness = headers.indexOfFirst { it.contains("business") || it.contains("company") } val colEmail = headers.indexOfFirst { it.contains("email") } val colCategory = headers.indexOfFirst { it.contains("category") } val colProduct = headers.indexOfFirst { it.contains("product") || it.contains("interest") } val colTags = headers.indexOfFirst { it.contains("tag") } val colOptIn = headers.indexOfFirst { it.contains("opt_in") || it.contains("optin") || it.contains("consent") } val colSource = headers.indexOfFirst { it.contains("source") } val colConsentDate = headers.indexOfFirst { it.contains("consent_date") || it.contains("date") } val colNotes = headers.indexOfFirst { it.contains("notes") || it.contains("comment") } val seenInBatch = mutableSetOf() val rowResults = mutableListOf() val validContacts = mutableListOf() var totalValid = 0 var totalDuplicates = 0 var totalNonConsenting = 0 var totalInvalid = 0 for (i in 1 until lines.size) { val line = lines[i] val columns = parseCsvRow(line) val errors = mutableListOf() val countryCode = if (colCountryCode >= 0 && colCountryCode < columns.size) { columns[colCountryCode].replace("+", "").trim().ifEmpty { "91" } } else "91" val rawMobile = if (colMobile >= 0 && colMobile < columns.size) columns[colMobile].trim() else "" val cleanDigits = rawMobile.replace(Regex("[^0-9]"), "") if (cleanDigits.length < 10) { errors.add("Invalid mobile number format: '$rawMobile' must have at least 10 digits") } // Normalize to E.164: +919876543210 val normalizedPhone = if (cleanDigits.startsWith(countryCode)) { "+$cleanDigits" } else if (cleanDigits.length == 10) { "+$countryCode$cleanDigits" } else { "+$cleanDigits" } var isDuplicate = false if (existingPhones.contains(normalizedPhone) || seenInBatch.contains(normalizedPhone)) { isDuplicate = true totalDuplicates++ errors.add("Duplicate phone number detected: $normalizedPhone") } else { seenInBatch.add(normalizedPhone) } // WhatsApp Marketing Consent Validation val rawOptIn = if (colOptIn >= 0 && colOptIn < columns.size) columns[colOptIn].lowercase().trim() else "false" val hasExplicitConsent = rawOptIn in listOf("true", "1", "yes", "opted_in", "y") val consentSource = if (colSource >= 0 && colSource < columns.size && columns[colSource].isNotBlank()) { columns[colSource].trim() } else { if (hasExplicitConsent) "CSV Consumer Import" else "Missing Consent" } val consentDate = if (colConsentDate >= 0 && colConsentDate < columns.size && columns[colConsentDate].isNotBlank()) { columns[colConsentDate].trim() } else { "2026-10-16" } if (!hasExplicitConsent) { totalNonConsenting++ errors.add("Non-consenting: WhatsApp marketing consent is required for promotional messaging") } val name = if (colName >= 0 && colName < columns.size) columns[colName].trim() else "" val district = if (colDistrict >= 0 && colDistrict < columns.size) { columns[colDistrict].trim() } else if (colCity >= 0 && colCity < columns.size) { columns[colCity].trim() } else "" val state = if (colState >= 0 && colState < columns.size) columns[colState].trim() else "" val pinCode = if (colPinCode >= 0 && colPinCode < columns.size) columns[colPinCode].trim() else "" val business = if (colBusiness >= 0 && colBusiness < columns.size) columns[colBusiness].trim() else "" val email = if (colEmail >= 0 && colEmail < columns.size) columns[colEmail].trim() else "" val category = "Consumer Upload List" val productInterest = if (colProduct >= 0 && colProduct < columns.size) columns[colProduct].trim() else "" val tags = if (colTags >= 0 && colTags < columns.size) columns[colTags].trim() else "consumer" val notes = if (colNotes >= 0 && colNotes < columns.size) columns[colNotes].trim() else "" val isValidRow = errors.isEmpty() || (cleanDigits.length >= 10 && !isDuplicate) val optInStatus = when { !hasExplicitConsent -> "PENDING" isDuplicate -> "OPTED_OUT" else -> "OPTED_IN" } val contactEntity = ContactEntity( countryCode = countryCode, mobileNumber = cleanDigits.takeLast(10), e164Phone = normalizedPhone, customerName = name, businessName = business, email = email, city = district, district = district, state = state, pinCode = pinCode, categoryId = defaultCategoryId, categoryName = category, productInterest = productInterest, tags = tags, optInStatus = optInStatus, consentSource = consentSource, consentDate = consentDate, customerNotes = notes, isApprovedByAdmin = false, isConsumerUpload = true // Excludes Vendors and Retailers ) if (isValidRow && hasExplicitConsent && !isDuplicate) { totalValid++ validContacts.add(contactEntity) } else if (cleanDigits.length < 10) { totalInvalid++ } rowResults.add( ImportRowResult( rowNumber = i + 1, contact = contactEntity, isValid = isValidRow && cleanDigits.length >= 10, isDuplicate = isDuplicate, hasConsent = hasExplicitConsent, validationErrors = errors ) ) } return ImportValidationReport( totalRowsProcessed = lines.size - 1, validCount = totalValid, duplicateCount = totalDuplicates, nonConsentingCount = totalNonConsenting, invalidRowsCount = totalInvalid, validContacts = validContacts, rowResults = rowResults ) } private fun parseCsvRow(row: String): List { val tokens = mutableListOf() val sb = StringBuilder() var inQuotes = false for (c in row) { when (c) { '"' -> inQuotes = !inQuotes ',' -> { if (inQuotes) { sb.append(c) } else { tokens.add(sb.toString().trim()) sb.setLength(0) } } else -> sb.append(c) } } tokens.add(sb.toString().trim()) return tokens } }