package ru.sweetbread.unn

import android.util.Log
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.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
import ru.sweetbread.unn.db.cacheSchedule
import ru.sweetbread.unn.db.cacheUser
import ru.sweetbread.unn.db.loadSchedule
import ru.sweetbread.unn.db.loadUserByBitrixId
import ru.sweetbread.unn.ui.layout.LoginData
import ru.sweetbread.unn.ui.layout.client
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


private lateinit var PHPSESSID: String
private lateinit var CSRF: String
lateinit var ME: User

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"


enum class Type(val s: String) {
    Student("student"),
    Group("group"),
    Lecturer("lecturer"),
    Auditorium("auditorium"),
    Employee("employee")
}

enum class LecturerRank(val id: Int) {
    Assistant(R.string.assistant),
    Lecturer(R.string.lecturer),
    SLecturer(R.string.slecturer),
    AProfessor(R.string.aprofessor)
}

class ScheduleUnit(
    val oid: Int,
    val auditorium: Auditorium,
    val date: LocalDate,
    val discipline: Discipline,
    val kindOfWork: KindOfWork,
    val lecturers: ArrayList<Lecturer>,
    val stream: String,
    val begin: LocalTime,
    val end: LocalTime
)

class Auditorium(
    val name: String,
    val oid: Int,
    val floor: Int,
    val building: Building
)

class Building(
    val name: String,
    val gid: Int,
    val oid: Int
)

class Discipline(
    val name: String,
    val oid: Int,
    val type: Int
)

class KindOfWork(
    val name: String,
    val oid: Int,
    val uid: String,
    val complexity: Int
)

class Lecturer(
    val name: String,
    val rank: LecturerRank,
    val email: String,
    val unnId: Int,
    val uid: String
)

class User(
    val unnId: Int?,
    val bitrixId: Int,
    val userId: Int,
    val type: Type,
    val email: String,
    val nameRu: String,
    val nameEn: String,
    val isMale: Boolean,
    val birthday: LocalDate,
    val avatar: AvatarSet
)

class Post(
    val id: Int,
    val authorId: Int,
    val enableComments: Boolean,
    val numComments: Int,
    val date: LocalDateTime,
    val content: String
)

class AvatarSet(
    val original: String,
    val thumbnail: String,
    val small: String
)


/**
 * Authorize user by [login] and [password]
 *
 * Also defines local vars [PHPSESSID] and [ME]
 */
suspend fun auth(
    login: String = LoginData.login,
    password: String = LoginData.password,
    forced: Boolean = false
): Boolean {
    if (!forced) {
        if (::PHPSESSID.isInitialized and ::ME.isInitialized)
            return true
    }
    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) {
        PHPSESSID =
            """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
            getMyself(login)
            getCSRF()
        return true
    }
    return false
}

/**
 * Save info about current [User] in memory
 */
private suspend fun getMyself(login: String) {
    // WARNING: trailing / is important, 'cuz API devs are eating shit
    val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo/") {
        header("Cookie", "PHPSESSID=$PHPSESSID")
        parameter("uns", login.drop(1))
    }.bodyAsText())

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

    Log.d("studentInfo", studentinfo.toString(2))

    ME = User(
        unnId = studentinfo.getString("id").toInt(),
        bitrixId = user.getInt("bitrix_id"),
        userId = user.getInt("id"),
        type = when (studentinfo.getString("type")) {
            "lecturer" -> Type.Lecturer  // ig,,,
            else -> Type.Student
        },
        email = user.getString("email"),
        nameRu = user.getString("fullname"),
        nameEn = user.getString("fullname_en"),
        isMale = user.getString("sex") == "M",
        birthday = Instant
            .parse(user.getString("birthdate"))
            .atZone(ZoneId.of("Europe/Moscow"))
            .toLocalDate(),
        avatar = user.getJSONObject("photo").let {
            AvatarSet(
                it.getString("orig"),
                it.getString("thumbnail"),
                it.getString("small"),
            )
        }
    )
}

suspend fun getScheduleDay(
    type: Type = ME.type,
    id: Int = ME.unnId!!,
    date: LocalDate
): ArrayList<ScheduleUnit> {

    if ((type == ME.type) and (id == ME.unnId!!)) {
        val schedule = withContext(Dispatchers.IO) { loadSchedule(date) }
        Log.d("Schedule", schedule.joinToString())
        if (schedule.size != 0) {
            return schedule
        }
    }

    return getSchedule(type, id, date, date)
}

suspend fun getSchedule(
    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.s}/$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")
                )
            )
        )
    }

    if ((type == ME.type) and (id == ME.unnId!!)) {
        cacheSchedule(out)
    }
    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 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
}

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 getUser(id: Int): User {
    val json = JSONObject(
        client.get("$vuzapiURL/user/$id") {
            header("Cookie", "PHPSESSID=$PHPSESSID")
        }.bodyAsText()
    )

    Log.d("type", json.getJSONArray("profiles").getJSONObject(0).getString("type"))

    return User(
        unnId = null,
        bitrixId = json.getInt("bitrix_id"),
        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"),
            )
        }
    )
}