package ru.forbion.f7cloud.feature.contacts

import android.content.Context
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.auth.OcsUserResolver
import ru.forbion.f7cloud.core.database.ContactEntity
import ru.forbion.f7cloud.core.database.F7Database
import ru.forbion.f7cloud.core.network.CardDavClient
import ru.forbion.f7cloud.core.network.NetworkFactory

data class ContactItem(
    val uid: String,
    val displayName: String,
    val email: String,
    val phone: String,
    val bookName: String,
    val photoBase64: String = "",
    val photoMimeType: String = "",
    val organization: String = "",
    val title: String = "",
    val address: String = "",
    val website: String = "",
    val birthday: String = "",
    val emails: String = "",
    val phones: String = "",
) {
    val emailLines: List<String>
        get() = (if (emails.isNotBlank()) emails else email)
            .lines()
            .map { it.trim() }
            .filter { it.isNotBlank() }
            .distinct()

    val phoneLines: List<String>
        get() = (if (phones.isNotBlank()) phones else phone)
            .lines()
            .map { it.trim() }
            .filter { it.isNotBlank() }
            .distinct()

    val subtitle: String?
        get() = when {
            title.isNotBlank() && organization.isNotBlank() -> "$title · $organization"
            title.isNotBlank() -> title
            organization.isNotBlank() -> organization
            else -> null
        }
}

class ContactsRepository(context: Context) {
    private val appContext = context.applicationContext
    private val dao = F7Database.get(appContext).contactsDao()
    private val prefs = appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

    fun observeContacts(session: AuthSession): Flow<List<ContactItem>> {
        return dao.observeAll(accountKey(session)).map { entities ->
            entities.map { it.toItem() }
        }
    }

    suspend fun getCachedContacts(session: AuthSession): List<ContactItem> {
        return dao.getAll(accountKey(session)).map { it.toItem() }
    }

    suspend fun syncContacts(session: AuthSession, force: Boolean = false): List<ContactItem> {
        val key = accountKey(session)
        val lastSync = prefs.getLong(lastSyncKey(key), 0L)
        val needsPhotoBackfill = !prefs.getBoolean(photoSyncDoneKey(key), false)
        val needsDetailsBackfill = !prefs.getBoolean(detailsSyncDoneKey(key), false)
        if (!force && !needsPhotoBackfill && !needsDetailsBackfill &&
            System.currentTimeMillis() - lastSync < SYNC_INTERVAL_MS
        ) {
            return dao.getAll(key).map { it.toItem() }
        }
        // Дешёвая проверка CTag: если адресные книги не менялись (и бэкфиллы сделаны) —
        // полный PROPFIND всех vCard не нужен, отдаём кэш. Это ускоряет pull-to-refresh.
        val needsBackfill = needsPhotoBackfill || needsDetailsBackfill
        val client = authedClient(session)
        val userId = session.davUserId ?: OcsUserResolver.resolveDavUserId(session)
        val currentSig = CardDavClient.collectionSignature(client, session.serverUrl, userId)
        if (!needsBackfill && currentSig != null &&
            currentSig == prefs.getString(ctagKey(key), null)
        ) {
            prefs.edit().putLong(lastSyncKey(key), System.currentTimeMillis()).apply()
            return dao.getAll(key).map { it.toItem() }
        }
        val remote = fetchRemoteContacts(session)
        val entities = remote.map { contact ->
            ContactEntity(
                accountKey = key,
                uid = contact.uid.ifBlank { "${contact.email}|${contact.displayName}" },
                displayName = contact.displayName,
                email = contact.email,
                phone = contact.phone,
                bookName = contact.bookName,
                photoBase64 = contact.photoBase64,
                photoMimeType = contact.photoMimeType,
                organization = contact.organization,
                title = contact.title,
                address = contact.address,
                website = contact.website,
                birthday = contact.birthday,
                emails = contact.emails,
                phones = contact.phones,
            )
        }
        dao.replaceAll(key, entities)
        val editor = prefs.edit()
            .putLong(lastSyncKey(key), System.currentTimeMillis())
            .putBoolean(photoSyncDoneKey(key), true)
            .putBoolean(detailsSyncDoneKey(key), true)
        if (currentSig != null) editor.putString(ctagKey(key), currentSig) else editor.remove(ctagKey(key))
        editor.apply()
        return entities.map { it.toItem() }
    }

    suspend fun createContact(
        session: AuthSession,
        displayName: String,
        email: String,
        phone: String = "",
    ): ContactItem {
        val client = authedClient(session)
        val userId = session.davUserId ?: OcsUserResolver.resolveDavUserId(session)
        val created = CardDavClient.createContact(
            client = client,
            serverUrl = session.serverUrl,
            userId = userId,
            displayName = displayName,
            email = email,
            phone = phone,
        )
        val key = accountKey(session)
        val entity = ContactEntity(
            accountKey = key,
            uid = created.uid,
            displayName = created.displayName,
            email = created.email,
            phone = created.phone,
            bookName = created.bookName,
            photoBase64 = created.photoBase64,
            photoMimeType = created.photoMimeType,
        )
        dao.insert(entity)
        return entity.toItem()
    }

    fun filterSuggestions(
        contacts: List<ContactItem>,
        query: String,
        limit: Int = 12,
    ): List<ContactItem> {
        val q = query.trim()
        if (q.isEmpty()) return emptyList()
        return contacts
            .filter {
                it.displayName.contains(q, ignoreCase = true) ||
                    it.email.contains(q, ignoreCase = true)
            }
            .sortedWith(
                compareBy<ContactItem> { !it.displayName.startsWith(q, ignoreCase = true) }
                    .thenBy { !it.email.startsWith(q, ignoreCase = true) }
                    .thenBy { it.displayName.lowercase() },
            )
            .take(limit)
    }

    private suspend fun fetchRemoteContacts(session: AuthSession): List<ContactItem> {
        val client = authedClient(session)
        val userId = session.davUserId ?: OcsUserResolver.resolveDavUserId(session)
        return CardDavClient.listContacts(client, session.serverUrl, userId)
            .map {
                ContactItem(
                    uid = it.uid,
                    displayName = it.displayName,
                    email = it.email,
                    phone = it.phone,
                    bookName = it.bookName,
                    photoBase64 = it.photoBase64,
                    photoMimeType = it.photoMimeType,
                    organization = it.organization,
                    title = it.title,
                    address = it.address,
                    website = it.website,
                    birthday = it.birthday,
                    emails = it.emails,
                    phones = it.phones,
                )
            }
            .sortedBy { it.displayName.lowercase() }
    }

    private fun authedClient(session: AuthSession) = NetworkFactory.newAuthedClient(
        session.username,
        session.appPassword,
        session.trustAllCerts,
    )

    private fun accountKey(session: AuthSession): String =
        "${session.serverUrl}|${session.username}"

    private fun lastSyncKey(accountKey: String) = "last_sync_$accountKey"

    private fun ctagKey(accountKey: String) = "ctag_$accountKey"

    private fun photoSyncDoneKey(accountKey: String) = "photo_sync_done_$accountKey"

    private fun detailsSyncDoneKey(accountKey: String) = "details_sync_done_$accountKey"

    private fun ContactEntity.toItem() = ContactItem(
        uid = uid,
        displayName = displayName,
        email = email,
        phone = phone,
        bookName = bookName,
        photoBase64 = photoBase64,
        photoMimeType = photoMimeType,
        organization = organization,
        title = title,
        address = address,
        website = website,
        birthday = birthday,
        emails = emails,
        phones = phones,
    )

    companion object {
        const val SYNC_INTERVAL_MS = 10 * 60 * 1000L
        private const val PREFS_NAME = "contacts_sync"
    }
}
