package com.nisemoe.nise.search.user

import com.fasterxml.jackson.annotation.JsonInclude
import jakarta.validation.Valid
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity
import org.springframework.util.StopWatch
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RestController

@RestController
class UserSearchController(
    private val userSearchService: UserSearchService
) {

    private val logger = LoggerFactory.getLogger(javaClass)

    companion object {

        const val RESULTS_PER_PAGE = 50

    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    data class SearchResponse(
        val results: List<SearchResponseEntry>,
        val pagination: SearchResponsePagination
    )

    data class SearchResponsePagination(
        val currentPage: Int,
        val pageSize: Int,
        val totalResults: Int
    ) {

        val totalPages: Int
            get() = if (totalResults % pageSize == 0) totalResults / pageSize else totalResults / pageSize + 1

    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    data class SearchResponseEntry(
        val user_id: Long?,
        val username: String?,
        val join_date: String?,
        val country: String?,
        val country_rank: Long?,
        val rank: Long?,
        val pp_raw: Double?,
        val accuracy: Double?,
        val playcount: Long?,
        val total_score: Long?,
        val ranked_score: Long?,
        val seconds_played: Long?,
        val count_300: Long?,
        val count_100: Long?,
        val count_50: Long?,
        val count_miss: Long?,
        val is_banned: Boolean?,
        val approx_ban_date: String?,
    )

    data class SearchRequest(
        @Valid @field:ValidChildQueriesDepth @field:Size(max = 10) val queries: List<SearchQuery>,
        @Valid val sorting: SearchSorting,
        @field:Min(1) val page: Int
    )

    data class SearchSorting(
        @field:NotBlank @field:Size(max = 300) val field: String,
        @field:NotBlank @field:Size(max = 300) val order: String
    )

    data class SearchQuery(
        @field:NotBlank @field:Size(max = 300) val logicalOperator: String,
        @Valid @field:Size(max = 10) val predicates: List<SearchPredicate>,
        @Valid @field:ValidChildQueriesDepth @field:Size(max = 10) val childQueries: List<SearchQuery>?
    )

    data class SearchPredicate(
        @Valid val field: SearchField,
        @Valid val operator: SearchPredicateOperator,
        @field:NotBlank @field:Size(max = 300) val value: String
    )

    data class SearchPredicateOperator(
        @field:NotBlank @field:Size(max = 300) val operatorType: String,
        @field:NotBlank @field:Size(max = 300) val acceptsValues: String
    )

    data class SearchField(
        @field:NotBlank @field:Size(max = 300) val name: String,
        @field:NotBlank @field:Size(max = 300) val type: String
    )

    @PostMapping("search-user")
    fun doSearch(@RequestBody @Valid request: SearchRequest, @RequestHeader("X-NISE-API") apiVersion: String): ResponseEntity<SearchResponse> {
        if (apiVersion.isBlank())
            return ResponseEntity.badRequest().build()

        // TODO: CSRF

        val stopwatch = StopWatch()
        stopwatch.start()
        try {
            val response = this.userSearchService.search(request)
            return ResponseEntity.ok(response)
        } catch (e: Exception) {
            this.logger.error("Error while searching: {}", e.stackTraceToString())
            return ResponseEntity.status(500).build()
        } finally {
            stopwatch.stop()
            this.logger.info("Search took {} seconds", String.format("%.2f", stopwatch.totalTimeSeconds))
        }
    }

}