package org.nstart.dep265.requestservice.service

import org.nstart.dep265.requestservice.dto.CellRequestFragmentResponseDto
import org.nstart.dep265.requestservice.dto.CellSummaryResponseDto
import org.nstart.dep265.requestservice.dto.CellWithRequestsResponseDto
import org.nstart.dep265.requestservice.dto.CellsListResponseDto
import org.nstart.dep265.requestservice.dto.CellPriorityMapResponseDto
import org.nstart.dep265.requestservice.entity.EarthCellEntity
import org.nstart.dep265.requestservice.entity.RequestCellEntity
import org.nstart.dep265.requestservice.repository.EarthCellRepository
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import space.nstart.pcp.pcp_types_lib.dto.requests.CellRequestDOT
import space.nstart.pcp.pcp_types_lib.dto.requests.EarthCellWithRequestsDTO
import java.util.UUID

class InvalidCellsQueryException(message: String) : IllegalArgumentException(message)

@Service
class CellsQueryService(
    private val earthCellRepository: EarthCellRepository,
    private val earthGridCatalogService: EarthGridCatalogService,
) {
    @Transactional(readOnly = true)
    fun listCells(
        minImportance: Double?,
        countLat: Int?,
        countLong: Int?,
        page: Int,
        size: Int,
        sort: String?,
    ): CellsListResponseDto {
        validateQuery(minImportance, countLat, countLong, page, size)

        val cells = earthCellRepository.findCells(
            minImportance = minImportance,
            pageable = PageRequest.of(page, size, sort.toCellsSort()),
        )

        return CellsListResponseDto(
            items = cells.content.map { earthCell -> earthCell.toSummaryResponse() },
            page = page,
            size = size,
            totalItems = cells.totalElements,
            totalPages = cells.totalPages,
        )
    }

    @Transactional(readOnly = true)
    fun listCellsPriorityMap(
        minImportance: Double?,
        countLat: Int?,
        countLong: Int?,
    ): CellPriorityMapResponseDto {
        validateWithRequestsQuery(minImportance, countLat, countLong)

        val items = if (countLat == null && countLong == null) {
            earthCellRepository.findCellsWithRequestProjections(minImportance)
                .map { earthCell -> earthCell.toWithRequestsResponse() }
        } else {
            earthGridCatalogService.allWithRequests(
                minImportance = minImportance,
                countLat = countLat,
                countLong = countLong,
            )
                .map { earthCell -> earthCell.toWithRequestsResponse() }
        }

        return CellPriorityMapResponseDto(items = items)
    }

    private fun validateQuery(
        minImportance: Double?,
        countLat: Int?,
        countLong: Int?,
        page: Int,
        size: Int,
    ) {
        if (minImportance != null && minImportance < 0.0) {
            throw InvalidCellsQueryException("minImportance must be greater than or equal to 0")
        }
        if (countLat != null && countLat <= 0) {
            throw InvalidCellsQueryException("countLat must be greater than 0")
        }
        if (countLong != null && countLong <= 0) {
            throw InvalidCellsQueryException("countLong must be greater than 0")
        }
        if (countLat != null || countLong != null) {
            throw InvalidCellsQueryException("Cell aggregation by countLat/countLong is not implemented yet")
        }
        if (page < 0) {
            throw InvalidCellsQueryException("page must be greater than or equal to 0")
        }
        if (size < 1 || size > 500) {
            throw InvalidCellsQueryException("size must be between 1 and 500")
        }
    }

    private fun validateWithRequestsQuery(
        minImportance: Double?,
        countLat: Int?,
        countLong: Int?,
    ) {
        if (minImportance != null && minImportance < 0.0) {
            throw InvalidCellsQueryException("minImportance must be greater than or equal to 0")
        }
        if (countLat != null && countLat <= 0) {
            throw InvalidCellsQueryException("countLat must be greater than 0")
        }
        if (countLong != null && countLong <= 0) {
            throw InvalidCellsQueryException("countLong must be greater than 0")
        }
        if ((countLat == null) != (countLong == null)) {
            throw InvalidCellsQueryException("countLat and countLong must be provided together")
        }
    }

    private fun String?.toCellsSort(): Sort {
        if (isNullOrBlank()) {
            return DEFAULT_SORT
        }

        val parts = split(",").map { part -> part.trim() }.filter { part -> part.isNotEmpty() }
        val field = parts.firstOrNull() ?: DEFAULT_SORT_FIELD
        val direction = parts.getOrNull(1)?.let { value ->
            when (value.lowercase()) {
                "asc" -> Sort.Direction.ASC
                "desc" -> Sort.Direction.DESC
                else -> throw InvalidCellsQueryException("Unsupported sort direction: $value")
            }
        } ?: Sort.Direction.ASC
        val property = ALLOWED_SORT_FIELDS[field]
            ?: throw InvalidCellsQueryException("Unsupported sort field: $field")
        val requestedSort = Sort.by(direction, property)

        return if (property == "cellNum") {
            requestedSort
        } else {
            requestedSort.and(Sort.by(Sort.Direction.ASC, "cellNum"))
        }
    }

    private fun EarthCellEntity.toSummaryResponse(): CellSummaryResponseDto {
        return CellSummaryResponseDto(
            cellNum = cellNum,
            latitude = latitude ?: 0.0,
            longitude = longitude ?: 0.0,
            importance = importance,
            contour = contour,
        )
    }

    private fun EarthCellEntity.toWithRequestsResponse(): CellWithRequestsResponseDto {
        return CellWithRequestsResponseDto(
            cellNum = cellNum,
            latitude = latitude ?: 0.0,
            longitude = longitude ?: 0.0,
            importance = importance,
            contour = contour,
            requests = requestProjections.map { requestProjection -> requestProjection.toFragmentResponse() },
        )
    }

    private fun RequestCellEntity.toFragmentResponse(): CellRequestFragmentResponseDto {
        return CellRequestFragmentResponseDto(
            requestId = requestId,
            contour = contour,
            coveragePercent = coveragePercent ?: 0.0,
            importance = importance,
        )
    }

    private fun EarthCellWithRequestsDTO.toWithRequestsResponse(): CellWithRequestsResponseDto {
        return CellWithRequestsResponseDto(
            cellNum = num,
            latitude = latitude,
            longitude = longitude,
            importance = importance,
            contour = contour,
            requests = requests.map { request -> request.toFragmentResponse() },
        )
    }

    private fun CellRequestDOT.toFragmentResponse(): CellRequestFragmentResponseDto {
        return CellRequestFragmentResponseDto(
            requestId = UUID.fromString(requestId),
            contour = contour,
            coveragePercent = covPercent,
            importance = importance,
        )
    }

    private companion object {
        const val DEFAULT_SORT_FIELD = "importance"
        val DEFAULT_SORT: Sort = Sort.by(Sort.Direction.DESC, "importance")
            .and(Sort.by(Sort.Direction.ASC, "cellNum"))
        val ALLOWED_SORT_FIELDS: Map<String, String> = mapOf(
            "cellNum" to "cellNum",
            "importance" to "importance",
            "latitude" to "latitude",
            "longitude" to "longitude",
        )
    }
}
