package com.bytelyst.platform

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
 * Generic offline-first sync engine.
 *
 * Apps implement [BLSyncAdapter] for their specific data type,
 * then [BLSyncEngine] handles the pull-merge-push cycle.
 *
 * Mirrors the Swift BLSyncEngine + BLSyncAdapter API.
 */
class BLSyncEngine<T>(
    private val adapter: BLSyncAdapter<T>,
    private val config: BLPlatformConfig,
    tokenProvider: () -> String? = { null },
) {
    private val client = BLPlatformClient(config, tokenProvider)

    /**
     * Run a full sync cycle: pull → merge → push.
     *
     * @return [SyncResult] with counts of pulled, pushed, and conflicted items.
     */
    suspend fun sync(): SyncResult = withContext(Dispatchers.IO) {
        var pulled = 0
        var pushed = 0
        var conflicts = 0

        try {
            // Step 1: Pull remote changes
            val lastSync = adapter.getLastSyncTimestamp()
            val pullPath = adapter.pullPath(lastSync)
            val response = client.request("GET", pullPath)
            val remoteItems = adapter.deserializePullResponse(response)
            pulled = remoteItems.size

            // Step 2: Merge remote into local
            for (item in remoteItems) {
                val merged = adapter.merge(item)
                if (!merged) conflicts++
            }

            // Step 3: Push local changes
            val localChanges = adapter.getLocalChanges()
            for (item in localChanges) {
                try {
                    val body = adapter.serializeForPush(item)
                    val method = adapter.pushMethod(item)
                    val path = adapter.pushPath(item)
                    client.request(method, path, body)
                    adapter.markSynced(item)
                    pushed++
                } catch (_: Exception) {
                    // Individual push failure — will retry next sync
                }
            }

            // Step 4: Update last sync timestamp
            adapter.setLastSyncTimestamp(System.currentTimeMillis())
        } catch (_: Exception) {
            // Full sync failure — will retry next time
        }

        SyncResult(pulled = pulled, pushed = pushed, conflicts = conflicts)
    }

    data class SyncResult(
        val pulled: Int = 0,
        val pushed: Int = 0,
        val conflicts: Int = 0,
    )
}

/**
 * Adapter interface for [BLSyncEngine].
 *
 * Each app implements this for their domain model (timers, sessions, etc.).
 */
interface BLSyncAdapter<T> {
    /** Get the timestamp of the last successful sync (epoch millis), or null if never synced. */
    fun getLastSyncTimestamp(): Long?

    /** Set the last sync timestamp after a successful sync. */
    fun setLastSyncTimestamp(timestamp: Long)

    /** Build the pull endpoint path, optionally including a since parameter. */
    fun pullPath(since: Long?): String

    /** Deserialize the pull response JSON into a list of remote items. */
    fun deserializePullResponse(json: String): List<T>

    /** Merge a remote item into local storage. Return true if merged cleanly, false if conflict. */
    fun merge(remoteItem: T): Boolean

    /** Get local items that have been modified since last sync. */
    fun getLocalChanges(): List<T>

    /** Serialize a local item for push. */
    fun serializeForPush(item: T): String

    /** HTTP method for pushing an item (POST for new, PUT for update). */
    fun pushMethod(item: T): String

    /** Build the push endpoint path for an item. */
    fun pushPath(item: T): String

    /** Mark an item as synced (clear dirty flag). */
    fun markSynced(item: T)
}
