package space.nstart.pcp.slots_service.repository

import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Repository
import java.sql.Timestamp

@Repository
class SlotRepositoryImpl(
    private val jdbcTemplate: JdbcTemplate
) : SlotRepositoryCustom {

    override fun findTargetSlotRows(
        searchRequests: List<BatchCoverageSearchRequest>,
        chunkSize: Int
    ): List<BatchCoverageSlotRow> {
        if (searchRequests.isEmpty()) {
            return emptyList()
        }

        val effectiveChunkSize = chunkSize.coerceAtLeast(1)

        return searchRequests
            .distinct()
            .chunked(effectiveChunkSize)
            .flatMap { chunk ->
                jdbcTemplate.query(
                    buildBatchSpatialFetchSql(chunk.size),
                    { rs, _ ->
                        BatchCoverageSlotRow(
                            targetId = rs.getLong("target_id"),
                            slotId = rs.getLong("slot_id"),
                            slotNum = rs.getLong("slot_num"),
                            cycle = rs.getLong("cycle"),
                            satelliteId = rs.getLong("satellite_id"),
                            coveringType = rs.getInt("covering_type"),
                            // getObject(LocalDateTime) читает `timestamp without time zone` без участия
                            // зоны JVM. getTimestamp().toLocalDateTime() зоно-нейтрален лишь для фикс-офсета;
                            // на зонах с DST граничные значения сдвигались бы. Времена в системе — локальные (настенные часы, без UTC-семантики).
                            tn = rs.getObject("tn", java.time.LocalDateTime::class.java),
                            tk = rs.getObject("tk", java.time.LocalDateTime::class.java),
                            roll = rs.getDouble("roll"),
                            contour = rs.getString("contour_wkt"),
                            revolution = rs.getLong("revolution"),
                            revolutionSign = rs.getString("revolution_sign"),
                            latitude = rs.getDouble("latitude"),
                            longitude = rs.getDouble("longitude")
                        )
                    },
                    *buildBatchSpatialFetchParams(chunk)
                )
            }
    }

    private fun buildBatchSpatialFetchSql(searchRequestCount: Int): String {
        val valuesSql = List(searchRequestCount) {
            "(?::bigint, ?::bigint, ?::timestamp, ?::timestamp, ?::text)"
        }.joinToString(", ")

        return """
            with search_input(target_id, satellite_id, window_start, window_stop, polygon_wkt) as (
                values $valuesSql
            ),
            search as (
                select
                    target_id,
                    satellite_id,
                    window_start,
                    window_stop,
                    st_geomfromtext(polygon_wkt, 4326) as search_geom
                from search_input
            )
            select distinct
                search.target_id,
                slot.slot_id,
                slot.slot_num,
                slot.cycle,
                slot.satellite_id,
                slot.covering_type,
                slot.tn,
                slot.tk,
                slot.roll,
                slot.contour_wkt,
                slot.revolution,
                slot.revolution_sign,
                slot.latitude,
                slot.longitude
            from search
            join slot on slot.satellite_id = search.satellite_id
             and slot.tn >= search.window_start
             and slot.tk <= search.window_stop
             and slot.contour_geom && search.search_geom
             and st_intersects(search.search_geom, slot.contour_geom)
            order by search.target_id, slot.satellite_id, slot.slot_id
        """.trimIndent()
    }

    private fun buildBatchSpatialFetchParams(
        chunk: List<BatchCoverageSearchRequest>
    ): Array<Any> {
        val params = ArrayList<Any>(chunk.size * 5)
        chunk.forEach { request ->
            params += request.targetId
            params += request.satelliteId
            params += Timestamp.valueOf(request.windowStart)
            params += Timestamp.valueOf(request.windowStop)
            params += request.polygonWkt
        }
        return params.toTypedArray()
    }
}
