package de.sebse.fuplanner2.auth

import android.content.Context
import com.android.volley.Header
import de.sebse.fuplanner2.database.User
import de.sebse.fuplanner2.network.NetData
import de.sebse.fuplanner2.network.Requester
import de.sebse.fuplanner2.network.Tools.invalidPassword
import de.sebse.fuplanner2.network.Tools.invalidResponse
import de.sebse.fuplanner2.utils.xml
import java.net.URI
import java.util.concurrent.TimeUnit
import kotlin.random.Random


abstract class FUAuthModule {
    companion object {
        // 7-10 days (random to reduce heap load)
        val REFRESH_FREQUENCY_COURSES_MILLIS = TimeUnit.HOURS.toMillis(7*24) + Random.nextLong(TimeUnit.HOURS.toMillis(3*24))
    }

    abstract suspend fun isAvailable(ctx: Context, name: String): Boolean
    abstract suspend fun login(ctx: Context, name: String, password: String, user: User)

    suspend fun doSaml(ctx: Context, samlUrl: String, name: String, password: String, user: User): SamlReponse {
        val requester = Requester(ctx)
        var response = requester.get(samlUrl, getCookies(user))
        updateCookies(user, response)
        if (response.networkResponse.statusCode == 200) {
            return parseResponse(response.body)
        } else {
            val relLocation = response.headers?.get("Location")
                ?: throw invalidResponse(100110, "No IDP form location!")
            val formUri = URI(samlUrl).resolve(relLocation).toString()
            val csrfToken = requester.get(formUri, getCookies(user)).body.let {
                getRegexGroup("name=\"csrf_token\" value=\"(.*?)\"", 1, it)
            } ?: throw invalidPassword(100112, "No CSRF token found!")
            response = requester.post(
                formUri,
                cookies = getCookies(user),
                data = hashMapOf(
                    "j_username" to name,
                    "j_password" to password,
                    "_eventId_proceed" to "",
                    "csrf_token" to csrfToken
                )
            )
            if (response.networkResponse.statusCode != 200) {
                throw invalidPassword(100111, "Password or username invalid!")
            }
        }
        return parseResponse(response.body)
    }

    protected fun parseCookies(headers: List<Header>): HashMap<String, String> {
        val result: HashMap<String, String> = hashMapOf()
        headers
            .filter { it.name == "Set-Cookie" }
            .forEach {
                result[it.value.substringBefore("=")] = it.value.substringAfter("=").substringBefore(";")
            }
        return result
    }

    private fun parseResponse(body: String): SamlReponse {
        val samlResponse = getRegexGroup(
            "name=\"SAMLResponse\" value=\"(.*?)\"", 1, body
        ) ?: throw invalidResponse(100100, "No SAML response found!")
        val relayState = getRegexGroup(
            "name=\"RelayState\" value=\"(.*?)\"", 1, body
        ) ?: throw invalidResponse(100101, "No Relay State found!")
        val url = getRegexGroup(
            "form action=\"(.*?)\"", 1, body
        ) ?: throw invalidResponse(100102, "No SAML Url found!")
        return SamlReponse(xml.decode(url), xml.decode(relayState), xml.decode(samlResponse))
    }

    private fun getRegexGroup(regex: String, group: Int, text: String): String? {
        val matcher = Regex(regex).find(text)
        return matcher?.groupValues?.let {
            if (it.size >= group+1) it[group] else null
        }
    }

    private fun updateCookies(user: User, response: NetData) {
        val setCookies = response.networkResponse.allHeaders?.let { parseCookies(it) }
        setCookies?.get("JSESSIONID")?.let {
            user.cookies.idpJsessionId = it
        }
    }

    private fun getCookies(user: User): HashMap<String, String>? {
        return user.cookies.idpJsessionId?.let { key -> hashMapOf("JSESSIONID" to key) }
    }
}
