package space.nstart.pcp.slots_service.model


import org.locationtech.jts.geom.Geometry
import org.locationtech.jts.geom.GeometryCollection
import org.locationtech.jts.geom.GeometryFactory
import org.locationtech.jts.geom.MultiPolygon
import org.locationtech.jts.geom.Polygon
import org.locationtech.jts.geom.PrecisionModel
import org.locationtech.jts.io.WKTReader
import org.locationtech.jts.operation.overlayng.OverlayNG
import org.locationtech.jts.operation.union.CascadedPolygonUnion
import org.locationtech.jts.precision.GeometryPrecisionReducer
import org.locationtech.jts.simplify.TopologyPreservingSimplifier
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotStatus
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotDTO
import java.time.Duration
import java.time.LocalDateTime
import kotlin.math.abs

data class TimedPolygon(
    val geometry: Geometry,
    val time: LocalDateTime,
    val id: String
)

data class PolygonCoverage(
    val selectedPolygons: List<TimedPolygon>,
    val coverageArea: Double,
    val coveragePercentage: Double,
    val uncoveredArea: Geometry?
)

class PolygonCoverageSolver {

    private val geometryFactory = GeometryFactory()
    private val wktReader = WKTReader()


    // Вспомогательный объект для безопасных геометрических операций
    private object GeometryUtils {
        private const val SNAP_TOLERANCE = 1e-6
        private const val MIN_AREA = 1e-8
        private val geometryFactory = GeometryFactory()

        // Безопасное пересечение через OverlayNG
        fun safeIntersection(g1: Geometry, g2: Geometry): Geometry {
            return try {
                val result = OverlayNG.overlay(g1, g2, OverlayNG.INTERSECTION)
                validateAndNormalize(result)
            } catch (e: Exception) {
                recoverTopology(g1, g2) { a, b -> OverlayNG.overlay(a, b, OverlayNG.INTERSECTION) }
            }
        }

        // Безопасная разность
        fun safeDifference(g1: Geometry, g2: Geometry): Geometry {
            return try {
                val result = OverlayNG.overlay(g1, g2, OverlayNG.DIFFERENCE)
                validateAndNormalize(result)
            } catch (e: Exception) {
                recoverTopology(g1, g2) { a, b -> OverlayNG.overlay(a, b, OverlayNG.DIFFERENCE) }
            }
        }

        // Безопасное объединение списка геометрий (рекомендуемый подход для JTS 1.19+)
        fun safeUnion(geometries: List<Geometry>): Geometry {
            if (geometries.isEmpty()) return geometryFactory.createPolygon()
            if (geometries.size == 1) return validateAndNormalize(geometries[0])

            // Используем CascadedPolygonUnion — оптимальный для больших наборов
            return try {
                val result = CascadedPolygonUnion.union(geometries)
                validateAndNormalize(result)
            } catch (e: Exception) {
                // Резерв: поэлементное объединение
                var union = validateAndNormalize(geometries[0])
                for (i in 1 until geometries.size) {
                    union = safeUnion(union, geometries[i])
                }
                union
            }
        }

        // Безопасное объединение двух геометрий
        fun safeUnion(g1: Geometry, g2: Geometry): Geometry {
            return try {
                val result = OverlayNG.overlay(g1, g2, OverlayNG.UNION)
                validateAndNormalize(result)
            } catch (e: Exception) {
                recoverTopology(g1, g2) { a, b -> OverlayNG.overlay(a, b, OverlayNG.UNION) }
            }
        }

        // Механизм восстановления топологии при ошибках
        private fun recoverTopology(
            g1: Geometry,
            g2: Geometry,
            operation: (Geometry, Geometry) -> Geometry
        ): Geometry {
            // Уровень 1: снэппинг к сетке
            try {
                val snapped1 = snapToGrid(g1, SNAP_TOLERANCE)
                val snapped2 = snapToGrid(g2, SNAP_TOLERANCE)
                return validateAndNormalize(operation(snapped1, snapped2))
            } catch (e1: Exception) {
                // Уровень 2: упрощение с сохранением топологии
                try {
                    val simplified1 = TopologyPreservingSimplifier.simplify(snapToGrid(g1, SNAP_TOLERANCE), SNAP_TOLERANCE * 0.1)
                    val simplified2 = TopologyPreservingSimplifier.simplify(snapToGrid(g2, SNAP_TOLERANCE), SNAP_TOLERANCE * 0.1)
                    return validateAndNormalize(operation(simplified1, simplified2))
                } catch (e2: Exception) {
                    // Уровень 3: буферизация как крайняя мера
                    return validateAndNormalize(g1.buffer(SNAP_TOLERANCE * 0.01).union(g2.buffer(SNAP_TOLERANCE * 0.01)))
                }
            }
        }

        // Снэппинг к сетке
        private fun snapToGrid(geom: Geometry, tolerance: Double): Geometry {
            val gridScale = 1.0 / tolerance
            return GeometryPrecisionReducer.reduce(geom, PrecisionModel(gridScale))
        }

        // Валидация и нормализация геометрии
        fun validateAndNormalize(geom: Geometry): Geometry {
            if (geom.isEmpty || geom.area < MIN_AREA) {
                return geometryFactory.createPolygon()
            }

            // Исправление невалидных геометрий
            val validGeom = if (!geom.isValid) {
                try {
                    // Для JTS 1.18+ доступен makeValid()
                    geom.buffer(0.0)
                } catch (e: Exception) {
                    // Резервный вариант для всех версий
                    geom.buffer(0.0)
                }
            } else {
                geom
            }

            // Очистка вырожденных частей для MultiPolygon
            return when (validGeom) {
                is Polygon -> if (validGeom.area > MIN_AREA) validGeom else geometryFactory.createPolygon()
                is MultiPolygon -> {
                    val validParts = (0 until validGeom.numGeometries)
                        .map { validGeom.getGeometryN(it) }
                        .filter { it is Polygon && it.area > MIN_AREA }
                        .map { it as Polygon }

                    when {
                        validParts.isEmpty() -> geometryFactory.createPolygon()
                        validParts.size == 1 -> validParts[0]
                        else -> geometryFactory.createMultiPolygon(validParts.toTypedArray())
                    }
                }
                else -> {
                    // Для других типов (LineString, Point) — преобразуем в буфер
                    if (validGeom is GeometryCollection) {
                        // Рекурсивная обработка коллекции
                        val validGeoms = (0 until validGeom.numGeometries)
                            .map { validateAndNormalize(validGeom.getGeometryN(it)) }
                            .filter { !it.isEmpty && it.area > MIN_AREA }
                        if (validGeoms.isEmpty()) geometryFactory.createPolygon()
                        else safeUnion(validGeoms)
                    } else {
                        // Преобразуем в полигон через буфер
                        validGeom.buffer(SNAP_TOLERANCE * 0.1)
                    }
                }
            }
        }
    }

    // Вспомогательный класс для обработки слотов
    private data class SlotGeometry(
        val slot: SlotDTO,
        val geometry: Geometry,
        val intersectionArea: Double
    )

    /**
     * Оптимальное покрытие полигона с балансом площади и количества слотов
     *
     * @param slots список доступных слотов с полигонами в формате WKT
     * @param targetWkt целевой полигон в формате WKT
     * @param minCoverageRatio минимальная доля покрытия (0.0 - 1.0), по умолчанию 0.95 (95%)
     * @param minRelativeGain минимальный относительный прирост площади для продолжения отбора
     * @return список оптимально отобранных слотов
     */
    fun optimalPolygonCoverage(
        slots: List<SlotDTO>,
        targetWkt: String,
        minCoverageRatio: Double = 0.95,
        minRelativeGain: Double = 0.005
    ): List<SlotDTO> {

        val wktReader = WKTReader()
        val geometryFactory = GeometryFactory()

        // Валидация целевого полигона
        val targetGeom: Polygon = try {
            val raw = wktReader.read(targetWkt)
            require(raw is Polygon) { "Target must be Polygon, got ${raw.geometryType}" }
            GeometryUtils.validateAndNormalize(raw) as Polygon
        } catch (e: Exception) {
            throw IllegalArgumentException("Invalid target WKT: ${e.message}", e)
        }
        val targetArea = targetGeom.area
        require(targetArea > 1e-6) { "Target polygon has near-zero area: $targetArea" }




        // Предварительная обработка слотов с защитой от ошибок
        val validSlots = slots.mapNotNull { slot ->
            try {
                val rawGeom = wktReader.read(slot.contour)
                val validGeom = GeometryUtils.validateAndNormalize(rawGeom)

                // Быстрая проверка через envelope (ограничивающий прямоугольник)
                if (!validGeom.envelopeInternal.intersects(targetGeom.envelopeInternal)) {
                    return@mapNotNull null
                }

                val intersection = GeometryUtils.safeIntersection(validGeom, targetGeom)
                val area = intersection.area

                // Фильтрация аномалий: слишком маленькие или слишком большие пересечения
                if (area > 1e-6 && area < targetArea * 10) {
                    SlotGeometry(slot, validGeom, area)
                } else {
                    null
                }
            } catch (e: Exception) {
                // Для production замените на логирование
                println("Skipping invalid slot ${slot.slotNumber}: ${e.message?.take(100)}")
                null
            }
        }.sortedByDescending { it.intersectionArea }
            .toMutableList()

        if (validSlots.isEmpty()) return emptyList()





        // Жадный отбор с приоритетом уникальной площади
        val selected = mutableListOf<SlotGeometry>()
        var uncovered: Geometry = targetGeom // Тип Geometry, а не Polygon!

        // забронированные слоты
        val bookedSlots = validSlots.filter { it.slot.state == SlotStatus.BOOKED }
        bookedSlots.forEach { bestCandidate ->

            selected.add(bestCandidate)
            validSlots.remove(bestCandidate)
            validSlots.removeIf { slot ->
                bestCandidate.slot.satelliteId == slot.slot.satelliteId &&
                        abs(Duration.between(slot.slot.tn, bestCandidate.slot.tn).seconds) < 30
            }
            if (uncovered.area > targetArea * (1 - minCoverageRatio)){
                uncovered = GeometryUtils.safeDifference(uncovered, bestCandidate.geometry)
            }
        }


        while (uncovered.area > targetArea * (1 - minCoverageRatio)) {
            var bestGain = 0.0
            var bestCandidate: SlotGeometry? = null
            for (candidate in validSlots) {
                if (candidate in selected) continue
                val newPart = GeometryUtils.safeIntersection(candidate.geometry, uncovered)
                val gain = newPart.area
                if (gain > bestGain) {
                    bestGain = gain
                    bestCandidate = candidate
                }
            }
            // Условия остановки
//            if (bestCandidate == null || bestGain < targetArea * minRelativeGain || bestGain < 1e-6) break
            if (bestCandidate == null || bestGain < 1e-8) break
            selected.add(bestCandidate)
            validSlots.remove(bestCandidate)
            validSlots.removeIf { slot ->
                slot.slot.satelliteId == bestCandidate.slot.satelliteId &&
                        abs(Duration.between(slot.slot.tn, bestCandidate.slot.tn).seconds) < 30
            }
            uncovered = GeometryUtils.safeDifference(uncovered, bestCandidate.geometry)
            // Защита от вырожденных геометрий
            if (uncovered.isEmpty) break
        }

        // Постоптимизация: удаление избыточных слотов
        return removeRedundantSlots(selected, targetGeom, targetArea).map { it.slot }
    }

    private fun removeRedundantSlots(
        slots: List<SlotGeometry>,
        targetGeom: Polygon,
        targetArea: Double
    ): List<SlotGeometry> {
        val essential = slots.filter { it.slot.state == SlotStatus.BOOKED }.toMutableList()
        val candidates = slots.filter { it.slot.state == SlotStatus.AVAILABLE }.sortedByDescending { it.intersectionArea }.toMutableList()

        while (candidates.isNotEmpty()) {
            val candidate = candidates.removeFirst()
            val others = essential + candidates

            val othersUnion = if (others.isNotEmpty()) {
                GeometryUtils.safeUnion(others.map { it.geometry })
            } else {
                GeometryFactory().createPolygon()
            }

            // Вычисляем покрытие с и без кандидата
            val coverageWith = GeometryUtils.safeIntersection(
                GeometryUtils.safeUnion(listOf(othersUnion, candidate.geometry)),
                targetGeom
            )
            val coverageWithout = GeometryUtils.safeIntersection(othersUnion, targetGeom)

            val areaLoss = coverageWith.area - coverageWithout.area

            // Сохраняем только если потеря площади значима (>0.1% от целевой)
            if (areaLoss > targetArea * 0.001) {
                essential.add(candidate)
            }
        }

        return essential
    }













}