package org.nstart.dep265.requestservice.service

import org.locationtech.jts.geom.Geometry
import org.locationtech.jts.geom.MultiPolygon
import org.locationtech.jts.geom.Polygon
import org.nstart.dep265.requestservice.entity.EarthCellEntity
import org.nstart.dep265.requestservice.entity.RequestCellEntity
import org.nstart.dep265.requestservice.entity.RequestEntity
import org.nstart.dep265.requestservice.repository.EarthCellRepository
import org.nstart.dep265.requestservice.repository.RequestCellRepository
import org.nstart.dep265.requestservice.repository.RequestRepository
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.UUID

@Service
class RequestGridProjectionService(
    private val requestRepository: RequestRepository,

    private val earthCellRepository: EarthCellRepository,

    private val requestCellRepository: RequestCellRepository,

    private val geometryService: GeometryService,

    @Value("\${app.grid.enabled:true}")
    private val gridEnabled: Boolean,
) {
    private val log = LoggerFactory.getLogger(this::class.java)

    @Transactional
    fun rebuildProjection(requestId: UUID) {
        if (!gridEnabled) {
            return
        }

        val requestEntity = requestRepository.findById(requestId).orElse(null) ?: return
        rebuildProjection(requestEntity)
    }

    @Transactional
    fun rebuildProjection(requestEntity: RequestEntity) {
        if (!gridEnabled) {
            return
        }

        /** Старая проекция удаляется целиком, потому что rebuild должен быть идемпотентным. */
        requestCellRepository.deleteByRequestId(requestEntity.id)

        val projectionGeometry = geometryService.parsePolygonalGeometry(requestEntity.geometry)

        /** Для пересечения с сеткой [0..360] геометрию нужно перевести в непрерывный диапазон. */
        val normalizedProjectionGeometry = LongitudeWrapGeometry.normalizeToContinuous360(projectionGeometry)

        /** Поиск по одному WKT недостаточен для заявок, пересекающих нулевой меридиан. */
        val intersectingCells = LongitudeWrapGeometry.buildSearchVariants(normalizedProjectionGeometry)
            .flatMap { projectionVariant ->
                earthCellRepository.findIntersectingByContour(geometryService.toWkt(projectionVariant))
            }
            .distinctBy { earthCell -> earthCell.cellId ?: earthCell.cellNum }

        val requestCellEntities = mutableListOf<RequestCellEntity>()

        intersectingCells.forEach { earthCell ->
            val cellGeometry = geometryService.parsePolygonalGeometry(earthCell.contour)

            /** Ячейка выравнивается к полосе долгот заявки, чтобы точное пересечение было корректным. */
            val alignedCellGeometry = LongitudeWrapGeometry.alignToReference(cellGeometry, normalizedProjectionGeometry)

            /** Пересечение заявка ∩ ячейка может дать Polygon, MultiPolygon или пустую геометрию. */
            val intersectionGeometry = geometryService.keepPolygonalGeometry(
                normalizedProjectionGeometry.intersection(alignedCellGeometry)
            )

            when (intersectionGeometry) {
                is Polygon -> addProjectionPiece(requestCellEntities, requestEntity, earthCell, cellGeometry, intersectionGeometry)
                is MultiPolygon -> {
                    for (geometryIndex in 0 until intersectionGeometry.numGeometries) {
                        /** Отдельная часть MultiPolygon добавляется как самостоятельный request_cells fragment. */
                        val geometryPart = intersectionGeometry.getGeometryN(geometryIndex)
                        if (geometryPart is Polygon && !geometryPart.isEmpty) {
                            addProjectionPiece(requestCellEntities, requestEntity, earthCell, cellGeometry, geometryPart)
                        }
                    }
                }
            }
        }

        requestCellRepository.saveAll(requestCellEntities)
        log.info("Grid projection rebuilt: requestId={}, fragments={}", requestEntity.id, requestCellEntities.size)
    }

    private fun addProjectionPiece(
        requestCellEntities: MutableList<RequestCellEntity>,
        requestEntity: RequestEntity,
        earthCell: EarthCellEntity,
        cellGeometry: Geometry,
        intersectionPolygon: Polygon,
    ) {
        if (intersectionPolygon.isEmpty || cellGeometry.area <= 0.0) {
            return
        }

        /** Процент покрытия нужен trigger'у importance и compat DTO CellRequestDOT. */
        val coveragePercent = (intersectionPolygon.area / cellGeometry.area) * 100.0

        val normalizedForCellBand = LongitudeWrapGeometry.alignToReference(intersectionPolygon, cellGeometry)
        val intersectionWkt = geometryService.toWkt(normalizedForCellBand)

        val requestCellEntity = RequestCellEntity(
            id = null,
            requestId = requestEntity.id,
            coveragePercent = coveragePercent,
            importance = requestEntity.importance,
            contour = intersectionWkt,
            cell = earthCell,
        )
        requestCellEntities.add(requestCellEntity)
    }
}
