package de.krisenvorrat.server.routes

import de.krisenvorrat.server.model.ErrorResponse
import de.krisenvorrat.server.repository.InventoryRepository
import de.krisenvorrat.server.security.UserPrincipal
import de.krisenvorrat.server.websocket.WebSocketManager
import de.krisenvorrat.shared.model.CreateInventoryRequest
import de.krisenvorrat.shared.model.InventoryDto
import de.krisenvorrat.shared.model.InventoryInfoDto
import de.krisenvorrat.shared.model.ItemDto
import io.ktor.http.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

private val EXPIRY_DATE_REGEX = Regex("""^\d{4}-\d{2}-\d{2}$""")

private val ALLOWED_SETTING_KEYS = setOf(
    "serverUrl", "syncEnabled", "theme", "language", "notificationsEnabled"
)

private fun validateItem(item: ItemDto): String? {
    if (item.id.length > 36) return "Item id too long (max 36 characters)"
    if (item.name.length > 255) return "Item name too long (max 255 characters)"
    if (item.unit.length > 50) return "Item unit too long (max 50 characters)"
    item.expiryDate?.let { date ->
        if (!date.matches(EXPIRY_DATE_REGEX)) return "Invalid expiryDate format '$date' – expected YYYY-MM-DD"
    }
    return null
}

private fun validatePartialItem(fields: JsonObject): String? {
    fields["name"]?.jsonPrimitive?.content?.let { name ->
        if (name.length > 255) return "Item name too long (max 255 characters)"
    }
    fields["unit"]?.jsonPrimitive?.content?.let { unit ->
        if (unit.length > 50) return "Item unit too long (max 50 characters)"
    }
    fields["expiryDate"]?.jsonPrimitive?.contentOrNull?.let { date ->
        if (!date.matches(EXPIRY_DATE_REGEX)) return "Invalid expiryDate format '$date' – expected YYYY-MM-DD"
    }
    return null
}

internal fun Route.inventoryRoutes(
    repository: InventoryRepository,
    wsManager: WebSocketManager
) {
    route("/api/inventory") {
        get {
            val userId = call.principal<UserPrincipal>()?.userId
                ?: return@get call.respond(HttpStatusCode.Unauthorized, ErrorResponse(status = 401, message = "Unauthorized"))
            val inventoryId = repository.getEffectiveInventoryId(userId)
            val since = call.request.queryParameters["since"]?.toLongOrNull()
            val inventory = if (since != null) {
                repository.loadInventorySince(inventoryId, since)
            } else {
                repository.loadInventory(inventoryId)
            }
            call.respond(HttpStatusCode.OK, inventory)
        }

        put {
            val userId = call.principal<UserPrincipal>()?.userId
                ?: return@put call.respond(HttpStatusCode.Unauthorized, ErrorResponse(status = 401, message = "Unauthorized"))
            val inventoryId = repository.getEffectiveInventoryId(userId)
            val inventory = call.receive<InventoryDto>()

            // Array size limits
            if (inventory.items.size > 10_000) return@put call.respond(
                HttpStatusCode.UnprocessableEntity, ErrorResponse(status = 422, message = "Too many items (max 10000)")
            )
            if (inventory.categories.size > 500) return@put call.respond(
                HttpStatusCode.UnprocessableEntity, ErrorResponse(status = 422, message = "Too many categories (max 500)")
            )
            if (inventory.locations.size > 500) return@put call.respond(
                HttpStatusCode.UnprocessableEntity, ErrorResponse(status = 422, message = "Too many locations (max 500)")
            )

            // Category and location name lengths
            inventory.categories.forEach { category ->
                if (category.name.length > 255) return@put call.respond(
                    HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Category name too long (max 255 characters)")
                )
            }
            inventory.locations.forEach { location ->
                if (location.name.length > 255) return@put call.respond(
                    HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Location name too long (max 255 characters)")
                )
            }

            // Item field validation
            inventory.items.forEach { item ->
                validateItem(item)?.let { error ->
                    return@put call.respond(HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = error))
                }
            }

            // Settings validation
            inventory.settings.forEach { setting ->
                if (setting.key.length > 255) return@put call.respond(
                    HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Setting key too long (max 255 characters)")
                )
            }

            repository.saveInventory(inventoryId, inventory)
            val saved = repository.loadInventory(inventoryId)
            wsManager.notifyFullSyncRequired(userId)
            call.respond(HttpStatusCode.OK, saved)
        }

        patch("/items/{id}") {
            val userId = call.principal<UserPrincipal>()?.userId
                ?: return@patch call.respond(HttpStatusCode.Unauthorized, ErrorResponse(status = 401, message = "Unauthorized"))
            val inventoryId = repository.getEffectiveInventoryId(userId)
            val itemId = call.parameters["id"] ?: return@patch call.respond(
                HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Missing item id")
            )
            if (itemId.length > 36) return@patch call.respond(
                HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Item id too long (max 36 characters)")
            )
            val body = call.receiveText()
            val fields = Json.parseToJsonElement(body).jsonObject
            validatePartialItem(fields)?.let { error ->
                return@patch call.respond(HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = error))
            }
            val updated = repository.patchItemPartial(inventoryId, itemId, fields)
            if (!updated) {
                call.respond(HttpStatusCode.NotFound, ErrorResponse(status = 404, message = "Item not found"))
            } else {
                val item = repository.loadItem(inventoryId, itemId)!!
                wsManager.notifyInventoryUpdated(userId, itemId)
                call.respond(HttpStatusCode.OK, item)
            }
        }

        delete("/items/{id}") {
            val userId = call.principal<UserPrincipal>()?.userId
                ?: return@delete call.respond(HttpStatusCode.Unauthorized, ErrorResponse(status = 401, message = "Unauthorized"))
            val inventoryId = repository.getEffectiveInventoryId(userId)
            val itemId = call.parameters["id"] ?: return@delete call.respond(
                HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Missing item id")
            )
            val deleted = repository.deleteItem(inventoryId, itemId)
            if (!deleted) {
                call.respond(HttpStatusCode.NotFound, ErrorResponse(status = 404, message = "Item not found"))
            } else {
                wsManager.notifyInventoryUpdated(userId, itemId)
                call.respond(HttpStatusCode.NoContent)
            }
        }
    }

    route("/api/inventories") {
        get {
            val userId = call.principal<UserPrincipal>()?.userId
                ?: return@get call.respond(HttpStatusCode.Unauthorized, ErrorResponse(status = 401, message = "Unauthorized"))
            val inventories = repository.getUserInventories(userId)
            call.respond(HttpStatusCode.OK, inventories)
        }

        post {
            val userId = call.principal<UserPrincipal>()?.userId
                ?: return@post call.respond(HttpStatusCode.Unauthorized, ErrorResponse(status = 401, message = "Unauthorized"))
            val request = call.receive<CreateInventoryRequest>()
            if (request.name.isBlank()) {
                call.respond(HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Inventory name must not be empty"))
                return@post
            }
            if (request.name.length > 255) {
                call.respond(HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Inventory name too long (max 255 characters)"))
                return@post
            }
            val inventoryId = repository.createInventory(request.name)
            repository.assignUserToInventory(userId, inventoryId)
            call.respond(HttpStatusCode.Created, InventoryInfoDto(id = inventoryId, name = request.name, isActive = true))
        }

        post("/{id}/switch") {
            val userId = call.principal<UserPrincipal>()?.userId
                ?: return@post call.respond(HttpStatusCode.Unauthorized, ErrorResponse(status = 401, message = "Unauthorized"))
            val targetId = call.parameters["id"] ?: return@post call.respond(
                HttpStatusCode.BadRequest, ErrorResponse(status = 400, message = "Missing inventory id")
            )
            if (!repository.inventoryExists(targetId)) {
                call.respond(HttpStatusCode.NotFound, ErrorResponse(status = 404, message = "Inventory not found"))
                return@post
            }
            repository.assignUserToInventory(userId, targetId)
            val name = repository.getInventoryName(targetId)
            call.respond(HttpStatusCode.OK, InventoryInfoDto(id = targetId, name = name, isActive = true))
        }
    }
}


