package ru.forbion.f7cloud.feature.notes

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.network.NetworkFactory

/**
 * Репозиторий Заметок: связывает сессию ([AuthSession]) → [NotesApiClient] → [NotesCache].
 * Сеть — на IO. Списки отдаются из ETag-кэша при 304 или отсутствии сети (offline-first
 * read). Пишущие операции идут напрямую в API (проверяются юнит-тестами, не на forbion —
 * mail/073).
 */
class NotesRepository(
    private val cache: NotesCache? = null,
    private val apiFactory: (AuthSession) -> NotesApiClient = ::defaultApi,
) {
    private fun accountKey(s: AuthSession) = "${s.serverUrl}|${s.davUserId ?: s.username}"

    /**
     * Список заметок (лёгкий, exclude=content). Пытается сеть с `If-None-Match` по кэш-ETag;
     * при 304 или сетевой ошибке возвращает кэш (если он есть). Свежий ответ кладётся в кэш.
     */
    suspend fun listNotes(session: AuthSession): List<Note> = withContext(Dispatchers.IO) {
        val api = apiFactory(session)
        val key = accountKey(session)
        val cached = cache?.get(key)
        try {
            val res = api.list(excludeContent = true, ifNoneMatch = cached?.etag)
            if (res.notModified) {
                cached?.let { return@withContext Note.listFromJson(JSONArray(it.json)) }
                return@withContext emptyList()
            }
            cache?.put(key, res.etag, notesToJson(res.notes))
            res.notes
        } catch (e: Exception) {
            // offline / сеть недоступна → отдаём кэш, если он есть
            cached?.let { return@withContext Note.listFromJson(JSONArray(it.json)) }
            throw e
        }
    }

    suspend fun getNote(session: AuthSession, id: Long): Note = withContext(Dispatchers.IO) {
        apiFactory(session).get(id)
    }

    suspend fun createNote(session: AuthSession, content: String, category: String = ""): Note =
        withContext(Dispatchers.IO) { apiFactory(session).create(content, category) }

    /** Сохранение содержимого с If-Match; [NoteConflictException] на 412 (обрабатывает UI). */
    suspend fun saveNote(session: AuthSession, note: Note): Note = withContext(Dispatchers.IO) {
        apiFactory(session).saveContent(note)
    }

    suspend fun setFavorite(session: AuthSession, id: Long, favorite: Boolean): Note =
        withContext(Dispatchers.IO) { apiFactory(session).setFavorite(id, favorite) }

    suspend fun setCategory(session: AuthSession, id: Long, category: String): Note =
        withContext(Dispatchers.IO) { apiFactory(session).setCategory(id, category) }

    suspend fun renameNote(session: AuthSession, id: Long, title: String): Note =
        withContext(Dispatchers.IO) { apiFactory(session).rename(id, title) }

    suspend fun deleteNote(session: AuthSession, id: Long) = withContext(Dispatchers.IO) {
        apiFactory(session).delete(id)
    }

    private fun notesToJson(notes: List<Note>): String {
        val arr = JSONArray()
        notes.forEach { n ->
            arr.put(
                JSONObject()
                    .put("id", n.id)
                    .put("etag", n.etag)
                    .put("readonly", n.readonly)
                    .put("title", n.title)
                    .put("category", n.category)
                    .put("content", n.content)
                    .put("favorite", n.favorite)
                    .put("modified", n.modified),
            )
        }
        return arr.toString()
    }

    private companion object {
        fun defaultApi(session: AuthSession): NotesApiClient {
            val client = NetworkFactory.newAuthedClient(
                username = session.username,
                appPassword = session.appPassword,
                trustAllCerts = session.trustAllCerts,
            )
            return NotesApiClient(client, session.serverUrl)
        }
    }
}
