package ru.sweetbread.unn.db

import android.util.Log
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import ru.sweetbread.unn.Auditorium
import ru.sweetbread.unn.Building
import ru.sweetbread.unn.Discipline
import ru.sweetbread.unn.KindOfWork
import ru.sweetbread.unn.Lecturer
import ru.sweetbread.unn.LecturerRank
import ru.sweetbread.unn.ScheduleUnit
import ru.sweetbread.unn.ui.layout.db
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter

@Entity
data class BuildingDB(
    @PrimaryKey val oid: Int,
    @ColumnInfo val name: String,
    @ColumnInfo val gid: Int,
    @ColumnInfo val expiredAt: String
)

@Dao
interface BuildingDao {
    @Query("SELECT * FROM buildingdb WHERE oid = :oid LIMIT 1")
    fun get(oid: Int): BuildingDB?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(building: BuildingDB)

    @Delete
    fun delete(building: BuildingDB)
}

fun cacheBuilding(building: Building) {
    db.buildingDao().insert(
        BuildingDB(
            building.oid,
            building.name,
            building.gid,
            LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
        )
    )
}

fun loadBuilding(oid: Int): Building? {
    return db.buildingDao().get(oid)?.let {
        if (LocalDateTime.parse(
                it.expiredAt,
                DateTimeFormatter.ISO_DATE_TIME
            ) > LocalDateTime.now()
        ) {
            db.buildingDao().delete(it)
            return null
        }
        Building(
            it.name,
            it.gid,
            it.oid
        )
    }
}

@Entity
data class AuditoriumDB(
    @PrimaryKey val oid: Int,
    @ColumnInfo val name: String,
    @ColumnInfo val floor: Int?,
    @ColumnInfo val buildingOid: Int,
    @ColumnInfo val expiredAt: String
)

@Dao
interface AuditoriumDao {
    @Query("SELECT * FROM auditoriumdb WHERE oid = :oid LIMIT 1")
    fun get(oid: Int): AuditoriumDB?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(auditorium: AuditoriumDB)

    @Delete
    fun delete(auditorium: AuditoriumDB)
}

fun cacheAuditorium(auditorium: Auditorium) {
    cacheBuilding(auditorium.building)
    db.auditoriumDao().insert(
        AuditoriumDB(
            auditorium.oid,
            auditorium.name,
            auditorium.floor,
            auditorium.building.oid,
            LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
        )
    )
}

fun loadAuditorium(oid: Int): Auditorium? {
    return db.auditoriumDao().get(oid)?.let {
        if (LocalDateTime.parse(
                it.expiredAt,
                DateTimeFormatter.ISO_DATE_TIME
            ) > LocalDateTime.now()
        ) {
            db.auditoriumDao().delete(it)
            return null
        }
        val building = loadBuilding(it.buildingOid) ?: return null
        return Auditorium(
            it.name,
            it.oid,
            it.floor ?: 0,
            building
        )
    }
}

@Entity
data class DisciplineDB(
    @PrimaryKey val oid: Int,
    @ColumnInfo val name: String,
    @ColumnInfo val type: Int,
    @ColumnInfo val expiredAt: String
)

@Dao
interface DisciplineDao {
    @Query("SELECT * FROM disciplinedb WHERE oid = :oid LIMIT 1")
    fun get(oid: Int): DisciplineDB?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(discipline: DisciplineDB)

    @Delete
    fun delete(discipline: DisciplineDB)
}

fun cacheDiscipline(discipline: Discipline) {
    db.disciplineDao().insert(
        DisciplineDB(
            discipline.oid,
            discipline.name,
            discipline.type,
            LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
        )
    )
}

fun loadDiscipline(oid: Int): Discipline? {
    return db.disciplineDao().get(oid)?.let {
        if (LocalDateTime.parse(
                it.expiredAt,
                DateTimeFormatter.ISO_DATE_TIME
            ) > LocalDateTime.now()
        ) {
            db.disciplineDao().delete(it)
            return null
        }

        return Discipline(
            it.name,
            it.oid,
            it.type
        )
    }
}

@Entity
data class KindOfWorkDB(
    @PrimaryKey val oid: Int,
    @ColumnInfo val name: String,
    @ColumnInfo val uid: String,
    @ColumnInfo val complexity: Int,
    @ColumnInfo val expiredAt: String
)

@Dao
interface KindOfWorkDao {
    @Query("SELECT * FROM kindofworkdb WHERE oid = :oid LIMIT 1")
    fun get(oid: Int): KindOfWorkDB?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(kindOfWork: KindOfWorkDB)

    @Delete
    fun delete(kindOfWork: KindOfWorkDB)
}

fun cacheKindOfWork(kindOfWork: KindOfWork) {
    db.kindOfWorkDao().insert(
        KindOfWorkDB(
            kindOfWork.oid,
            kindOfWork.name,
            kindOfWork.uid,
            kindOfWork.complexity,
            LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
        )
    )
}

fun loadKindOfWork(oid: Int): KindOfWork? {
    return db.kindOfWorkDao().get(oid)?.let {
        if (LocalDateTime.parse(
                it.expiredAt,
                DateTimeFormatter.ISO_DATE_TIME
            ) > LocalDateTime.now()
        ) {
            db.kindOfWorkDao().delete(it)
            return null
        }

        return KindOfWork(
            it.name,
            it.oid,
            it.uid,
            it.complexity
        )
    }
}

@Entity
data class LecturerDB(
    @PrimaryKey val unnId: Int,
    @ColumnInfo val name: String,
    @ColumnInfo val rank: LecturerRank,
    @ColumnInfo val email: String,
    @ColumnInfo val uid: String,
    @ColumnInfo val expiredAt: String
)

@Dao
interface LecturerDao {
    @Query("SELECT * FROM lecturerdb WHERE unnId = :unnId LIMIT 1")
    fun get(unnId: Int): LecturerDB?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(lecturer: LecturerDB)

    @Delete
    fun delete(lecturer: LecturerDB)
}

fun cacheLecturer(lecturer: Lecturer) {
    db.lecturerDao().insert(
        LecturerDB(
            lecturer.unnId,
            lecturer.name,
            lecturer.rank,
            lecturer.email,
            lecturer.uid,
            LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
        )
    )
}

fun loadLecturer(unnId: Int): Lecturer? {
    return db.lecturerDao().get(unnId)?.let {
        if (LocalDateTime.parse(
                it.expiredAt,
                DateTimeFormatter.ISO_DATE_TIME
            ) > LocalDateTime.now()
        ) {
            db.lecturerDao().delete(it)
            return null
        }

        return Lecturer(
            it.name,
            it.rank,
            it.email,
            it.unnId,
            it.uid
        )
    }
}

@Entity
data class ScheduleUnitDB(
    @PrimaryKey val oid: Int,
    @ColumnInfo val date: String,
    @ColumnInfo val stream: String,
    @ColumnInfo val begin: String,
    @ColumnInfo val end: String,

    @ColumnInfo val auditoriumOid: Int,
    @ColumnInfo val disciplineOid: Int,
    @ColumnInfo val kindOfWorkOid: Int,
    @ColumnInfo val lecturerId: Int,  // TODO: many-to-many

    @ColumnInfo val expiredAt: String
)
@Dao
interface ScheduleItemDao {
    @Query("SELECT * FROM scheduleUnitDB WHERE oid = :oid LIMIT 1")
    fun getSchedule(oid: Int): ScheduleUnitDB?

    @Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`")
    fun getSchedule(date: String): List<ScheduleUnitDB>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(user: ScheduleUnitDB)

    @Delete
    fun delete(user: ScheduleUnitDB)
}

fun cacheSchedule(item: ScheduleUnit) {
    cacheAuditorium(item.auditorium)
    cacheDiscipline(item.discipline)
    cacheKindOfWork(item.kindOfWork)
    cacheLecturer(item.lecturers[0])

    db.scheduleDao().insert(
        ScheduleUnitDB(
            item.oid,
            item.date.format(DateTimeFormatter.ISO_DATE),
            item.stream,
            item.begin.format(DateTimeFormatter.ISO_TIME),
            item.end.format(DateTimeFormatter.ISO_TIME),
            item.auditorium.oid,
            item.discipline.oid,
            item.kindOfWork.oid,
            item.lecturers[0].unnId,
            when {
                (LocalDate.now() > item.date) -> LocalDateTime.now().plusWeeks(2)
                (LocalDate.now().plusWeeks(1) > item.date) -> LocalDateTime.now().plusDays(1)
                (LocalDate.now().plusWeeks(2) > item.date) -> LocalDateTime.now().plusWeeks(1)
                else -> LocalDateTime.now().plusWeeks(2)
            }.format(DateTimeFormatter.ISO_DATE_TIME)
        )
    )
}

fun cacheSchedule(items: ArrayList<ScheduleUnit>) {
    for (item in items)
        cacheSchedule(item)
}

fun loadSchedule(oid: Int): ScheduleUnit? {
    db.scheduleDao().getSchedule(oid)?.let {
        Log.d("load", it.oid.toString())
        if (LocalDateTime.parse(
                it.expiredAt,
                DateTimeFormatter.ISO_DATE_TIME
            ) < LocalDateTime.now()
        ) {
            Log.d("delete", it.oid.toString())
            db.scheduleDao().delete(it)
            return null
        }

        return ScheduleUnit(
            it.oid,
            loadAuditorium(it.auditoriumOid) ?: return null,
            LocalDate.parse(it.date, DateTimeFormatter.ISO_DATE),
            loadDiscipline(it.disciplineOid) ?: return null,
            loadKindOfWork(it.kindOfWorkOid) ?: return null,
            arrayListOf(loadLecturer(it.lecturerId) ?: return null),
            it.stream,
            LocalTime.parse(it.begin, DateTimeFormatter.ISO_TIME),
            LocalTime.parse(it.end, DateTimeFormatter.ISO_TIME)
        )
    }
    return null
}

fun loadSchedule(date: LocalDate): ArrayList<ScheduleUnit> {
    db.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
        .mapNotNull { Log.d("meow", "${it.oid}: ${loadSchedule(it.oid)}") }
    return ArrayList(
        db.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
            .mapNotNull { loadSchedule(it.oid) }
    )
}