package pt.whatsms.gateway.modules.localserver.routes

import android.content.Context
import com.aventrix.jnanoid.jnanoid.NanoIdUtils
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import pt.whatsms.gateway.data.entities.MessageWithRecipients
import pt.whatsms.gateway.domain.EntitySource
import pt.whatsms.gateway.domain.MessageContent
import pt.whatsms.gateway.domain.ProcessingState
import pt.whatsms.gateway.helpers.DateTimeParser
import pt.whatsms.gateway.modules.localserver.LocalServerSettings
import pt.whatsms.gateway.modules.localserver.auth.AuthScopes
import pt.whatsms.gateway.modules.localserver.auth.requireScope
import pt.whatsms.gateway.modules.localserver.domain.PostMessagesInboxExportRequest
import pt.whatsms.gateway.modules.localserver.domain.messages.DataMessage
import pt.whatsms.gateway.modules.localserver.domain.messages.PostMessageRequest
import pt.whatsms.gateway.modules.localserver.domain.messages.TextMessage
import pt.whatsms.gateway.modules.messages.MessagesService
import pt.whatsms.gateway.modules.messages.data.Message
import pt.whatsms.gateway.modules.messages.data.SendParams
import pt.whatsms.gateway.modules.messages.data.SendRequest
import pt.whatsms.gateway.modules.messages.exceptions.ConflictException
import pt.whatsms.gateway.modules.receiver.ReceiverService
import java.util.Date

class MessagesRoutes(
    private val context: Context,
    private val messagesService: MessagesService,
    private val receiverService: ReceiverService,
    private val settings: LocalServerSettings,
) {
    fun register(routing: Route) {
        routing.apply {
            messagesRoutes()
            route("/inbox") {
                inboxRoutes(context)
            }
        }
    }

    private fun Route.messagesRoutes() {
        get {
            if (!requireScope(AuthScopes.MessagesRead)) return@get
            // Parse and validate parameters
            val state = call.request.queryParameters["state"]?.takeIf { it.isNotEmpty() }
                ?.let { ProcessingState.valueOf(it) }
            val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 50
            val offset = call.request.queryParameters["offset"]?.toIntOrNull() ?: 0
            val includeContent = call.request.queryParameters["includeContent"]?.let {
                it.toBooleanStrictOrNull() ?: run {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        mapOf("message" to "includeContent must be true or false")
                    )
                    return@get
                }
            } ?: false

            // Parse date range parameters
            val from = call.request.queryParameters["from"]?.let {
                DateTimeParser.parseIsoDateTime(it)?.time
            } ?: 0
            val to = call.request.queryParameters["to"]?.let {
                DateTimeParser.parseIsoDateTime(it)?.time
            } ?: Date().time

            val deviceId = call.request.queryParameters["deviceId"]
            if (deviceId != null && deviceId != settings.deviceId) {
                call.respond(
                    HttpStatusCode.BadRequest,
                    mapOf("message" to "Invalid device ID")
                )
                return@get
            }

            // Ensure start date is before end date
            if (from > to) {
                call.respond(
                    HttpStatusCode.BadRequest,
                    mapOf("message" to "Start date cannot be after end date")
                )
                return@get
            }

            // Get total count for pagination
            val total = try {
                messagesService.countMessages(EntitySource.Local, state, from, to)
            } catch (e: Throwable) {
                call.respond(
                    HttpStatusCode.InternalServerError,
                    mapOf("message" to "Failed to count messages: ${e.message}")
                )
                return@get
            }

            // Get messages with pagination
            val messages = try {
                messagesService.selectMessages(EntitySource.Local, state, from, to, limit, offset)
            } catch (e: Throwable) {
                call.respond(
                    HttpStatusCode.InternalServerError,
                    mapOf("message" to "Failed to retrieve messages: ${e.message}")
                )
                return@get
            }

            call.response.headers.append("X-Total-Count", total.toString())

            call.respond(
                messages.map {
                    it.toDomain(requireNotNull(settings.deviceId), includeContent)
                }
            )
        }

        post {
            if (!requireScope(AuthScopes.MessagesSend)) return@post
            val request = call.receive<PostMessageRequest>()

            if (request.deviceId?.let { it == settings.deviceId } == false) {
                call.respond(
                    HttpStatusCode.BadRequest,
                    mapOf("message" to "Invalid device ID")
                )
                return@post
            }

            val messageTypes =
                listOfNotNull(request.textMessage, request.dataMessage, request.message)
            when {
                messageTypes.isEmpty() -> {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        mapOf("message" to "Must specify exactly one of: textMessage, dataMessage, or message")
                    )
                    return@post
                }

                messageTypes.size > 1 -> {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        mapOf("message" to "Cannot specify multiple message types simultaneously")
                    )
                    return@post
                }
            }

            // Validate message parameters
            request.message?.let { msg ->
                // Text validation
                if (msg.isEmpty()) {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        mapOf("message" to "Text message is empty")
                    )
                    return@post
                }
            }

            // Validate data message parameters
            request.dataMessage?.let { dataMsg ->
                // Port validation
                if (dataMsg.port < 0 || dataMsg.port > 65535) {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        mapOf("message" to "Port must be between 0 and 65535")
                    )
                    return@post
                }

                // Data validation (only for non-empty check)
                if (dataMsg.data.isEmpty()) {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        mapOf("message" to "Data message cannot be empty")
                    )
                    return@post
                }
            }

            // Validate text message parameters
            request.textMessage?.let { textMsg ->
                // Text validation
                if (textMsg.text.isEmpty()) {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        mapOf("message" to "Text message is empty")
                    )
                    return@post
                }
            }

            // Existing validation
            if (request.phoneNumbers.isEmpty()) {
                call.respond(
                    HttpStatusCode.BadRequest,
                    mapOf("message" to "phoneNumbers is empty")
                )
                return@post
            }
            if (request.simNumber != null && request.simNumber < 1) {
                call.respond(
                    HttpStatusCode.BadRequest,
                    mapOf("message" to "simNumber must be >= 1")
                )
                return@post
            }
            val skipPhoneValidation =
                call.request.queryParameters["skipPhoneValidation"]
                    ?.toBooleanStrict() ?: false

            // Create message content based on type
            val messageContent = when {
                request.message != null -> {
                    MessageContent.Text(request.message)
                }

                request.textMessage != null -> {
                    MessageContent.Text(request.textMessage.text)
                }

                request.dataMessage != null -> {
                    MessageContent.Data(
                        request.dataMessage.data,
                        request.dataMessage.port.toUShort()
                    )
                }

                else -> {
                    // This case should be caught by validation, but just in case
                    throw IllegalStateException("Unknown message type")
                }
            }

            val sendRequest = SendRequest(
                EntitySource.Local,
                Message(
                    request.id ?: NanoIdUtils.randomNanoId(),
                    content = messageContent,
                    phoneNumbers = request.phoneNumbers,
                    isEncrypted = request.isEncrypted ?: false,
                    createdAt = Date(),
                ),
                SendParams(
                    request.withDeliveryReport ?: true,
                    skipPhoneValidation = skipPhoneValidation,
                    simNumber = request.simNumber,
                    validUntil = request.validUntil,
                    priority = request.priority,
                )
            )

            val message = try {
                messagesService.enqueueMessage(sendRequest)
            } catch (e: ConflictException) {
                call.respond(
                    HttpStatusCode.Conflict,
                    mapOf("message" to e.message)
                )
                return@post
            }


            call.respond(
                HttpStatusCode.Accepted,
                message.toDomain(requireNotNull(settings.deviceId), true)
            )
        }
        get("{id}") {
            if (!requireScope(AuthScopes.MessagesRead)) return@get
            val id = call.parameters["id"]
                ?: return@get call.respond(HttpStatusCode.BadRequest)

            val message = try {
                messagesService.getMessage(id)
                    ?: return@get call.respond(HttpStatusCode.NotFound)
            } catch (e: Throwable) {
                return@get call.respond(
                    HttpStatusCode.InternalServerError,
                    mapOf("message" to e.message)
                )
            }

            call.respond(
                message.toDomain(
                    requireNotNull(settings.deviceId),
                    includeContent = true
                )
            )
        }
    }

    private fun Route.inboxRoutes(context: Context) {
        post("export") {
            if (!requireScope(AuthScopes.MessagesExport)) return@post
            val request = call.receive<PostMessagesInboxExportRequest>().validate()
            try {
                receiverService.export(context, request.period, true)
                call.respond(HttpStatusCode.Accepted)
            } catch (e: Exception) {
                call.respond(
                    HttpStatusCode.InternalServerError,
                    mapOf("message" to "Failed to export inbox: ${e.message}")
                )
            }
        }
    }

    private fun MessageWithRecipients.toDomain(
        deviceId: String,
        includeContent: Boolean = false
    ): pt.whatsms.gateway.modules.localserver.domain.messages.Message {
        return pt.whatsms.gateway.modules.localserver.domain.messages.Message(
            id = message.id,
            deviceId = deviceId,
            state = message.state,
            isHashed = false,
            isEncrypted = message.isEncrypted,
            textMessage = when (includeContent) {
                true -> message.textContent?.let {
                    TextMessage(it.text)
                }

                else -> null
            },
            dataMessage = when (includeContent) {
                true -> message.dataContent?.let {
                    DataMessage(
                        data = it.data,
                        port = it.port.toInt()
                    )
                }

                else -> null
            },
            hashedMessage = null,
            recipients = recipients.map {
                pt.whatsms.gateway.modules.localserver.domain.messages.Message.Recipient(
                    it.phoneNumber,
                    it.state,
                    it.error
                )
            },
            states = states.associate {
                it.state to Date(it.updatedAt)
            }
        )
    }
}
