package dev.inmo.postssystem.features.posts.server

import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.postssystem.features.content.common.ContentId
import dev.inmo.postssystem.features.posts.common.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateStatement

private class PostsContentsRelations(
    override val database: Database,
    postIdColumn: Column<Long>
) : Table("PostsContentsRelations"), ExposedRepo {
    val postIdColumn = long("postid").references(postIdColumn)
    val contentIdColumn = text("contentId")
}

class ExposedServerPostsStorage(
    override val database: Database,
) : ServerPostsStorage, AbstractExposedCRUDRepo<RegisteredPost, PostId, NewPost>(
    tableName = "Posts"
) {
    val idColumn = long("id").autoIncrement()
    private val datetimeColumn = long("creationDT")
    override val primaryKey: PrimaryKey = PrimaryKey(idColumn)

    private val subrepo = PostsContentsRelations(
        database,
        idColumn
    )
    private fun getContentInTransaction(id: PostId): List<ContentId> {
        return subrepo.select { subrepo.postIdColumn.eq(id.long) }.map { it[subrepo.contentIdColumn].let(::ContentId) }
    }

    override val selectByIds: SqlExpressionBuilder.(List<PostId>) -> Op<Boolean> = {
        idColumn.inList(it.map { it.long })
    }

    override val selectById: SqlExpressionBuilder.(PostId) -> Op<Boolean> = {
        idColumn.eq(it.long)
    }
    override val ResultRow.asObject: RegisteredPost
        get() {
            val id = get(idColumn).let(::PostId)
            return RegisteredPost(
                get(idColumn).let(::PostId),
                getContentInTransaction(id),
                get(datetimeColumn).let(DateTime::fromUnix)
            )
        }

    init {
        initTable()
        subrepo.initTable()
    }

    override fun insert(value: NewPost, it: InsertStatement<Number>) {
        it[datetimeColumn] = DateTime.now().unixMillisLong
    }

    override fun update(id: PostId, value: NewPost, it: UpdateStatement) {
        it[datetimeColumn] = DateTime.now().unixMillisLong

        subrepo.deleteWhere {
            subrepo.postIdColumn.eq(id.long)
        }

        value.content.map { it.string }.forEach { contentId ->
            subrepo.insert {
                it[subrepo.postIdColumn] = id.long
                it[subrepo.contentIdColumn] = contentId
            }
        }
    }

    override fun InsertStatement<Number>.asObject(value: NewPost): RegisteredPost {
        val id = get(idColumn).let(::PostId)

        value.content.map { it.string }.forEach { contentId ->
            subrepo.insert {
                it[subrepo.postIdColumn] = id.long
                it[subrepo.contentIdColumn] = contentId
            }
        }

        return RegisteredPost(
            get(idColumn).let(::PostId),
            getContentInTransaction(id),
            get(datetimeColumn).let(DateTime::fromUnix)
        )
    }

}
