package dev.inmo.postssystem.services.posts.server

import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.ktor.common.decodeHex
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import dev.inmo.micro_utils.mime_types.findBuiltinMimeType
import dev.inmo.micro_utils.repos.ktor.common.crud.createRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.updateRouting
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.removeRoute
import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadStandardCrudRepoRoutes
import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.posts.common.*
import dev.inmo.postssystem.services.posts.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.auth.authenticate
import io.ktor.http.content.PartData
import io.ktor.request.isMultipart
import io.ktor.request.receiveMultipart
import io.ktor.routing.*
import io.ktor.util.asStream
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.use
import io.ktor.utils.io.streams.asInput
import kotlinx.serialization.builtins.*

class ServerPostsServiceRoutingConfigurator(
    private val readPostsService: ReadPostsService,
    private val writePostsService: WritePostsService? = readPostsService as? WritePostsService,
    private val unifiedRouter: UnifiedRouter
) : ApplicationRoutingConfigurator.Element {
    private val contentEitherSerializer = EitherSerializer(ContentId.serializer(), ContentSerializer)
    private val contentsEitherSerializer = ListSerializer(contentEitherSerializer)
    private val contentsSerializer = ListSerializer(ContentSerializer)

    private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContents(): List<Content> {
        return unifiedRouter.run {
            if (call.request.isMultipart()) {
                val multipart = call.receiveMultipart()
                val list = mutableListOf<Pair<String, Content>>()

                var part = multipart.readPart()

                while (part != null) {
                    val name = part.name
                    val capturedPart = part
                    when {
                        name == null -> {}
                        capturedPart is PartData.FormItem -> {
                            list.add(
                                name to unifiedRouter.serialFormat.decodeHex(
                                    ContentSerializer,
                                    capturedPart.value
                                )
                            )
                        }
                        capturedPart is PartData.FileItem -> {
                            val filename = capturedPart.originalFileName ?.let(::FileName) ?: error("File name is unknown for default part")
                            val mimeType = capturedPart.contentType ?.let {
                                findBuiltinMimeType("${it.contentType}/${it.contentSubtype}")
                            } ?: error("File type is unknown for default part")
                            val resultInput = MPPFile.createTempFile(
                                filename.nameWithoutExtension.let {
                                    var resultName = it
                                    while (resultName.length < 3) {
                                        resultName += "_"
                                    }
                                    resultName
                                },
                                ".${filename.extension}"
                            ).apply {
                                outputStream().use { fileStream ->
                                    capturedPart.provider().asStream().copyTo(fileStream)
                                }
                            }

                            list.add(
                                name to BinaryContent(
                                    filename,
                                    mimeType
                                ) { resultInput.inputStream().asInput() }
                            )
                        }
                        else -> {}
                    }

                    part = multipart.readPart()
                }

                list.sortedBy { it.first }.map { it.second }
            } else {
                uniload(contentsSerializer)
            }
        }
    }

    private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContentsEithers(): List<Either<ContentId, Content>> {
        return unifiedRouter.run {
            if (call.request.isMultipart()) {
                val multipart = call.receiveMultipart()
                val list = mutableListOf<Pair<String, Either<ContentId, Content>>>()

                var part = multipart.readPart()

                while (part != null) {
                    val name = part.name
                    val capturedPart = part
                    when {
                        name == null -> {}
                        capturedPart is PartData.FormItem -> {
                            list.add(
                                name to unifiedRouter.serialFormat.decodeHex(
                                    contentEitherSerializer,
                                    capturedPart.value
                                )
                            )
                        }
                        capturedPart is PartData.FileItem -> {
                            val filename = capturedPart.originalFileName ?.let(::FileName) ?: error("File name is unknown for default part")
                            val mimeType = capturedPart.contentType ?.let {
                                findBuiltinMimeType("${it.contentType}/${it.contentSubtype}")
                            } ?: error("File type is unknown for default part")
                            val resultInput = MPPFile.createTempFile(
                                filename.nameWithoutExtension.let {
                                    var resultName = it
                                    while (resultName.length < 3) {
                                        resultName += "_"
                                    }
                                    resultName
                                },
                                ".${filename.extension}"
                            ).apply {
                                outputStream().use { fileStream ->
                                    capturedPart.provider().asStream().copyTo(fileStream)
                                }
                            }

                            list.add(
                                name to BinaryContent(
                                    filename,
                                    mimeType
                                ) { resultInput.inputStream().asInput() }.either()
                            )
                        }
                        else -> {}
                    }

                    part = multipart.readPart()
                }

                list.sortedBy { it.first }.map { it.second }
            } else {
                uniload(contentsEitherSerializer)
            }
        }
    }

    override fun Route.invoke() {
        authenticate {
            route(postsRootPath) {
                configureReadStandardCrudRepoRoutes(
                    readPostsService,
                    RegisteredPost.serializer(),
                    RegisteredPost.serializer().nullable,
                    PostId.serializer(),
                    unifiedRouter
                )

                writePostsService ?.let {

                    unifiedRouter.apply {

                        post(createRouting) {
                            val data = receiveContents()

                            unianswer(
                                RegisteredPost.serializer().nullable,
                                writePostsService.create(FullNewPost(data))
                            )
                        }

                        post(updateRouting) {
                            val postId = call.decodeUrlQueryValueOrSendError(postsPostIdParameter, PostId.serializer()) ?: return@post
                            val data = receiveContentsEithers()

                            unianswer(
                                RegisteredPost.serializer().nullable,
                                writePostsService.update(
                                    postId,
                                    data
                                )
                            )
                        }

                        post(removeRoute) {
                            val postId = uniload(PostId.serializer())

                            unianswer(
                                Unit.serializer(),
                                writePostsService.remove(postId)
                            )
                        }

                    }

                }
            }
        }
    }
}
