package de.walamana.models

import de.walamana.plugins.*
import kotlinx.serialization.Contextual
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import java.util.*

@Serializable
data class Ministrant(
    val id: Int,
    val username: String,
    val passwordHash: String? = "",
    val firstname: String,
    val lastname: String,
    val birthday: DateAsLong,
    val privileges: CommaSeperatedStringList
)

@Serializable
data class SimplifiedMinistrant(
    val id: Int,
    val username: String,
    val firstname: String,
    val lastname: String
)

object Ministranten : Table() {
    val id = integer("id").autoIncrement()
    val username = varchar("username", 128)
    val passwordHash = varchar("passwordHash", 1024)
    val firstname = varchar("firstname", 128)
    val lastname = varchar("lastname", 128)
    val birthday = long("birthday")
    val privileges = varchar("privileges", 1024)

    override val primaryKey = PrimaryKey(id)
}

object MinistrantenDao {
    private fun resultRowToMinistrant(row: ResultRow, showPasswordHash: Boolean = false) = Ministrant(
        row[Ministranten.id],
        row[Ministranten.username],
        if (showPasswordHash) row[Ministranten.passwordHash] else "",
        row[Ministranten.firstname],
        row[Ministranten.lastname],
        Date(row[Ministranten.birthday]),
        row[Ministranten.privileges].split(",")
    )

    suspend fun allMinistranten(): List<Ministrant> = dbQuery {
        Ministranten.selectAll()
            .orderBy(
                Ministranten.lastname to SortOrder.ASC,
                Ministranten.firstname to SortOrder.ASC
            )
            .map(::resultRowToMinistrant)
    }

    suspend fun simplifiedMinistranten(): List<SimplifiedMinistrant> = dbQuery {
        Ministranten.slice(
            Ministranten.id,
            Ministranten.username,
            Ministranten.firstname,
            Ministranten.lastname
        ).selectAll()
            .orderBy(
                Ministranten.lastname to SortOrder.ASC,
                Ministranten.firstname to SortOrder.ASC
            )
            .map { row ->
                SimplifiedMinistrant(
                    row[Ministranten.id],
                    row[Ministranten.username],
                    row[Ministranten.firstname],
                    row[Ministranten.lastname]
                )
            }
    }

    suspend fun getMinistrant(username: String, showPasswordHash: Boolean = false): Ministrant? = dbQuery {
        Ministranten.select(Ministranten.username eq username).map {
            resultRowToMinistrant(it, showPasswordHash)
        }.firstOrNull()
    }

    suspend fun createMinistrant(
        username: String,
        passwordHash: String,
        firstname: String,
        lastname: String,
        birthday: Date,
        privileges: List<String>
    ) = dbQuery {
        val statement = Ministranten.insert {
            it[Ministranten.username] = username
            it[Ministranten.passwordHash] = ""
            it[Ministranten.firstname] = firstname
            it[Ministranten.lastname] = lastname
            it[Ministranten.birthday] = birthday.time
            it[Ministranten.privileges] = privileges.joinToString(",")
        }
        Security.setPassword(username, passwordHash)
        statement.resultedValues?.singleOrNull()?.let(::resultRowToMinistrant)
    }

    suspend fun deleteMinistrant(id: Int): Boolean = dbQuery {
        Ministranten.deleteWhere { Ministranten.id eq id } > 0
    }

    suspend fun updateMinistrant(
        id: Int,
        username: String? = null,
        passwordHash: String? = null,
        firstname: String? = null,
        lastname: String? = null,
        birthday: Date? = null,
        privileges: List<String>? = null
    ) = dbQuery {
        Ministranten.update({ Ministranten.id eq id }) {
            if (username != null) it[Ministranten.username] = username
            if (passwordHash != null) it[Ministranten.passwordHash] = passwordHash
            if (firstname != null) it[Ministranten.firstname] = firstname
            if (lastname != null) it[Ministranten.lastname] = lastname
            if (birthday != null) it[Ministranten.birthday] = birthday.time
            if (privileges != null) it[Ministranten.privileges] = privileges.joinToString(",")
        }
    }
}
