package de.sebse.fuplanner2.whiteboard

import android.content.Context
import android.net.Uri
import com.android.volley.VolleyError
import com.beust.klaxon.JsonObject
import com.beust.klaxon.Parser
import de.sebse.fuplanner2.auth.FUAuthModule
import de.sebse.fuplanner2.auth.SamlReponse
import de.sebse.fuplanner2.database.User
import de.sebse.fuplanner2.network.NetData
import de.sebse.fuplanner2.network.Requester
import de.sebse.fuplanner2.network.Tools.invalidResponse

object Whiteboard: FUAuthModule() {
    private const val LOGIN_URL = "https://mycampus.imp.fu-berlin.de/sakai-login-tool/container"
    private const val TEST_URL = "https://mycampus.imp.fu-berlin.de/direct/user/%s.json"
    private const val RESTORE_ON_REDIRECT_TO = "/portal"
    private const val REMOVE_COOKIE_ON_REDIRECT_TO = "/sakai-login-tool/container"
    internal const val MODULE_TYPE = 2

    internal const val FETCH_COURSE_MEMBERSHIP = "https://mycampus.imp.fu-berlin.de/direct/membership.json?_validateSession="
    internal const val FETCH_COURSE_DETAILS = "https://mycampus.imp.fu-berlin.de/direct%s.json?_validateSession="

    internal const val FETCH_EVENT_LIST = "https://mycampus.imp.fu-berlin.de/direct/calendar%s.json?detailed=true&_validateSession="

    internal const val FETCH_ANNOUNCEMENT_LIST = "https://mycampus.imp.fu-berlin.de/direct/announcement%s.json?n=999999&d=999999999&_validateSession="

    override suspend fun isAvailable(ctx: Context, name: String): Boolean {
        val requester = Requester(ctx)
        try {
            requester.get(String.format(TEST_URL, Uri.encode(name)), null)
        } catch (e: VolleyError) {
            return e.networkResponse.statusCode == 403
        }
        return true
    }

    override suspend fun login(ctx: Context, name: String, password: String, user: User) {
        val requester = Requester(ctx)
        val request = requester.get(LOGIN_URL, cookies = getCookies(user, shib = true))
        val samlUri = request.headers?.get("Location")
            ?: throw invalidResponse(102100, "Location header not set!")
        if (samlUri == RESTORE_ON_REDIRECT_TO) {
            updateCookies(user, request)
            return
        }
        if (samlUri == REMOVE_COOKIE_ON_REDIRECT_TO) {
            if (delCookies(user))
                login(ctx, name, password, user)
            return
        }
        val samlResponse: SamlReponse = doSaml(ctx, samlUri, name, password, user)
        // Shib-Session-Cookie
        var response = requester.post(samlResponse.uri, cookies = getCookies(user), data = hashMapOf(
            "RelayState" to samlResponse.relayState,
            "SAMLResponse" to samlResponse.samlResponse
        ))
        updateCookies(user, response)
        // Finish BB & Start Session
        response = requester.get(
            response.networkResponse.headers?.get("Location") ?: throw invalidResponse(102101, "No Location header to finish Blackboard"),
            getCookies(user, shib = true)
        )

        updateCookies(user, response)

        response = requester.get(String.format(TEST_URL, Uri.encode(name)), getCookies(user))
        (Parser.default().parse(StringBuilder(response.body)) as JsonObject).run {
            user.wbInfo.id = string("id") ?: user.wbInfo.id
            user.email = string("email") ?: user.email
            user.matNumber = obj("props")?.string("zedat:matrikelnr")?.toIntOrNull() ?: user.matNumber
            user.firstName = string("firstName") ?: user.firstName
            user.lastName = string("lastName") ?: user.lastName
        }
    }

    private fun updateCookies(user: User, response: NetData) {
        val setCookies = response.networkResponse.allHeaders?.let { parseCookies(it) }
        setCookies?.get("SAKAI2SESSIONID")?.let {
            user.cookies.wbJsessionId = it
        }
        setCookies
            ?.filter{ (key, _) -> key.startsWith("_shibsession_") }
            ?.forEach { (key, value) ->
                user.cookies.wbShibKey = key
                user.cookies.wbShibValue = value
                return@forEach
            }
    }

    internal fun getCookies(user: User, shib: Boolean = false): HashMap<String, String>? {
        val cookies = user.cookies.wbJsessionId?.let { key -> hashMapOf("SAKAI2SESSIONID" to key) } ?: hashMapOf()
        if (shib && user.cookies.wbShibValue != null) {
            user.cookies.wbShibKey?.let { cookies[it] = user.cookies.wbShibValue ?: "" }
        }
        return cookies
    }

    private fun delCookies(user: User): Boolean {
        val isSuccessful = user.cookies.wbShibKey != null
        user.cookies.wbJsessionId = null
        user.cookies.wbShibKey = null
        user.cookies.wbShibValue = null
        return isSuccessful
    }

    internal fun isAvailable(user: User): Boolean {
        return user.cookies.wbJsessionId != null
    }
}