package com.anypreta.bountyboard.data.net

import android.content.Context
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import java.util.concurrent.ConcurrentHashMap

/**
 * A cookie jar that persists the session (`bb_session`, httpOnly) and CSRF
 * (`bb_csrf`) cookies across app restarts in SharedPreferences. This is the
 * entirety of the auth mechanism — the server is cookie-session based.
 */
class PersistentCookieJar(context: Context) : CookieJar {

    private val prefs = context.getSharedPreferences("bb_cookies", Context.MODE_PRIVATE)
    // host -> (cookie name -> serialized Set-Cookie string)
    private val store = ConcurrentHashMap<String, MutableMap<String, String>>()

    init {
        prefs.all.forEach { (key, value) ->
            if (value is String) {
                val host = key.substringBefore('|')
                val name = key.substringAfter('|')
                store.getOrPut(host) { ConcurrentHashMap() }[name] = value
            }
        }
    }

    @Synchronized
    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
        val bucket = store.getOrPut(url.host) { ConcurrentHashMap() }
        val editor = prefs.edit()
        for (cookie in cookies) {
            val key = "${url.host}|${cookie.name}"
            if (cookie.expiresAt < System.currentTimeMillis()) {
                bucket.remove(cookie.name)
                editor.remove(key)
            } else {
                val serialized = cookie.toString()
                bucket[cookie.name] = serialized
                editor.putString(key, serialized)
            }
        }
        editor.apply()
    }

    override fun loadForRequest(url: HttpUrl): List<Cookie> {
        val bucket = store[url.host] ?: return emptyList()
        val now = System.currentTimeMillis()
        return bucket.values.mapNotNull { Cookie.parse(url, it) }
            .filter { it.expiresAt >= now && it.matches(url) }
    }

    /**
     * Import a cookie obtained outside OkHttp (e.g. from the WebView CookieManager after
     * an OIDC redirect) so the session continues in the app's HTTP stack.
     */
    @Synchronized
    fun putSimpleCookie(host: String, name: String, value: String) {
        val cookie = Cookie.Builder()
            .name(name)
            .value(value)
            .domain(host)
            .path("/")
            .expiresAt(System.currentTimeMillis() + 30L * 24 * 3600 * 1000)
            .build()
        val serialized = cookie.toString()
        store.getOrPut(host) { ConcurrentHashMap() }[name] = serialized
        prefs.edit().putString("$host|$name", serialized).apply()
    }

    /** Parse a `k=v; k2=v2` cookie header (CookieManager.getCookie) into the jar. */
    fun importCookieHeader(host: String, header: String?) {
        if (header.isNullOrBlank()) return
        header.split(';').forEach { pair ->
            val eq = pair.indexOf('=')
            if (eq > 0) {
                val name = pair.substring(0, eq).trim()
                val value = pair.substring(eq + 1).trim()
                if (name.isNotEmpty() && value.isNotEmpty()) putSimpleCookie(host, name, value)
            }
        }
    }

    /** Read a cookie value (e.g. `bb_csrf`) for the given host. */
    fun cookieValue(host: String, name: String): String? {
        val raw = store[host]?.get(name) ?: return null
        return raw.substringBefore(';').substringAfter('=').takeIf { it.isNotEmpty() }
    }

    fun hasSession(host: String): Boolean = cookieValue(host, "bb_session") != null

    @Synchronized
    fun clear() {
        store.clear()
        prefs.edit().clear().apply()
    }
}
