// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE).

package ru.sweetbread.unn.api

import android.util.Log
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.parameter
import io.ktor.client.statement.bodyAsText
import io.ktor.http.parameters
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
import ru.sweetbread.unn.db.cacheUser
import ru.sweetbread.unn.db.loadUserByBitrixId
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter


const val portalURL = "https://portal.unn.ru"
const val ruzapiURL = "$portalURL/ruzapi"
const val vuzapiURL = "$portalURL/bitrix/vuz/api"
const val prtl2URL = "$portalURL/portal2/api"
const val restURL = "$portalURL/rest"


val client = HttpClient(Android) {
    install(HttpCache)
    install(Logging) {
        logger = object : Logger {
            override fun log(message: String) {
                Log.i("Ktor", message)
            }
        }
        level = LogLevel.ALL
    }
    install(HttpTimeout) {
        connectTimeoutMillis = 5000
    }
    install(HttpRequestRetry) {
        retryOnException(maxRetries = 3, retryOnTimeout = true)
        exponentialDelay()
        modifyRequest { request ->
            request.headers.append("x-retry-count", retryCount.toString())
        }
    }
}

suspend fun getToken(login: String, password: String): String? {
    val r = client.submitForm("$portalURL/auth/?login=yes",
        formParameters = parameters {
            append("AUTH_FORM", "Y")
            append("TYPE", "AUTH")
            append("backurl", "/")
            append("USER_LOGIN", login)
            append("USER_PASSWORD", password)
        }
    )

    if (r.status.value == 302)
        return """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
    return null
}

suspend fun getId(login: String): Int {
    return JSONObject(client.get("$ruzapiURL/studentinfo/") {
        header("Cookie", "PHPSESSID=$PHPSESSID")
        parameter("uns", login.drop(1))
    }.bodyAsText()).getString("id").toInt()
}

suspend fun getUser(userId: Int? = null): User {
    // WARNING: trailing / is important, 'cuz API devs are eating shit
    // TODO: make up another way to get unnId: this is not useful for lectures
    val json = JSONObject(
        client.get("$vuzapiURL/user/${userId ?: ""}") {
            header("Cookie", "PHPSESSID=$PHPSESSID")
        }.bodyAsText()
    )

    return User(
        unnId = null,
        userId = json.getInt("id"),
        type = if (json.getJSONArray("profiles").getJSONObject(0)
                .getString("type") == "employee"
        ) Type.employee else Type.student,
        email = json.getString("email"),
        nameRu = json.getString("fullname"),
        nameEn = json.getString("fullname_en"),
        isMale = json.getString("sex") == "M",
        birthday = Instant
            .parse(json.getString("birthdate"))
            .atZone(ZoneId.of("Europe/Moscow"))
            .toLocalDate(),
        avatar = json.getJSONObject("photo").let {
            AvatarSet(
                it.getString("orig"),
                it.getString("thumbnail"),
                it.getString("small"),
            )
        }
    )
}

suspend fun downloadSchedule(
    type: Type = ME.type,
    id: Int = ME.unnId!!,
    start: LocalDate,
    finish: LocalDate
): ArrayList<ScheduleUnit> {
    val unnDatePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd")

    val r = client.get("$ruzapiURL/schedule/${type.name}/$id") {
        parameter("start", start.format(unnDatePattern))
        parameter("finish", finish.format(unnDatePattern))
        parameter("lng", "1")
    }

    val json = JSONArray(r.bodyAsText())
    val out = arrayListOf<ScheduleUnit>()
    for (i in 0 until json.length()) {
        val unit = json.getJSONObject(i)
        val lecturesJson = unit.getJSONArray("listOfLecturers")
        val lecturers = arrayListOf<Lecturer>()

        for (j in 0 until lecturesJson.length()) {
            val lecturer = lecturesJson.getJSONObject(j)
            lecturers.add(
                Lecturer(
                    name = lecturer.getString("lecturer"),
                    email = lecturer.getString("lecturerEmail"),
                    unnId = lecturer.getInt("lecturerOid"),
                    uid = lecturer.getString("lecturerUID"),
                    rank = when (lecturer.getString("lecturer_rank")) {
                        "АССИСТ" -> LecturerRank.Assistant
                        "СТПРЕП" -> LecturerRank.SLecturer
                        "ДОЦЕНТ" -> LecturerRank.AProfessor
                        else -> LecturerRank.Lecturer
                    }
                )
            )
        }

        out.add(
            ScheduleUnit(
                oid = unit.getInt("lessonOid"),
                auditorium = Auditorium(
                    name = unit.getString("auditorium"),
                    oid = unit.getInt("auditoriumOid"),
                    floor = unit.getInt("auditoriumfloor"),
                    building = Building(
                        name = unit.getString("building"),
                        gid = unit.getInt("buildingGid"),
                        oid = unit.getInt("buildingOid")
                    )
                ),
                date = LocalDate.parse(unit.getString("date"), unnDatePattern),
                discipline = Discipline(
                    name = unit.getString("discipline"),
                    oid = unit.getInt("disciplineOid"),
                    type = unit.getInt("disciplinetypeload")
                ),
                kindOfWork = KindOfWork(
                    name = unit.getString("kindOfWork"),
                    complexity = unit.getInt("kindOfWorkComplexity"),
                    oid = unit.getInt("kindOfWorkOid"),
                    uid = unit.getString("kindOfWorkUid")
                ),
                lecturers = lecturers,
                stream = unit.getString("stream"),
                begin = LocalTime.parse(
                    unit.getString("beginLesson"),
                    DateTimeFormatter.ofPattern("HH:mm")
                ),
                end = LocalTime.parse(
                    unit.getString("endLesson"),
                    DateTimeFormatter.ofPattern("HH:mm")
                )
            )
        )
    }

    return out
}

suspend fun getCSRF() {
    val r = client.get("$restURL/log.blogpost.get") {
        header("Cookie", "PHPSESSID=$PHPSESSID")
        parameter("sessid", "")
    }
    CSRF = JSONObject(r.bodyAsText()).getString("sessid")
}

suspend fun getUserByBitrixId(id: Int): User {
    withContext(Dispatchers.IO) {
        loadUserByBitrixId(id)
    }?.let { return it }

    val userId = JSONObject(client.get("$vuzapiURL/user/bx/$id") {
        header("Cookie", "PHPSESSID=$PHPSESSID")
    }.bodyAsText()).getInt("id")

    getUser(userId).let { user ->
        withContext(Dispatchers.IO) {
            cacheUser(user)
        }
        return user
    }
}

suspend fun getBlogposts(): ArrayList<Post> {
    val r = client.get("$prtl2URL/news.php") {
        header("Cookie", "PHPSESSID=$PHPSESSID")
        header("x-bitrix-sessid-token", CSRF)
    }
    val result = JSONArray(r.bodyAsText())

    val out = arrayListOf<Post>()
    for (i in 0 until result.length()) {
        val el = result.getJSONObject(i)
        out.add(
            Post(
                id = el.getString("id").toInt(),
                authorId = el.getJSONObject("author").getInt("id").toInt(),
                enableComments = true, // FIXME: Delete the field or get correct value
                numComments = el.getString("commentsnum").toInt(),
                date = LocalDateTime.parse(
                    el.getString("time"),
                    DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
                ),
                content = el.getString("fulltext")
            )
        )
    }
    return out
}
