package space.nstart.pcp_tgu_service.service

/** Загружает метаданные платформ КА и применяет простую in-memory фильтрацию. */
import com.fasterxml.jackson.databind.ObjectMapper
import space.nstart.pcp_tgu_service.config.ClassifierProperties
import space.nstart.pcp_tgu_service.domain.PlatformCatalogItem
import space.nstart.pcp_tgu_service.dto.NsiPlatformDto
import space.nstart.pcp_tgu_service.integration.api.OrdinisUnavailableException
import space.nstart.pcp_tgu_service.integration.api.PlatformsClassifierClient
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.time.Clock
import java.time.Instant

@Service
class PlatformService(
    private val platformsClassifierClient: PlatformsClassifierClient,
    private val classifierProperties: ClassifierProperties,
    private val objectMapper: ObjectMapper,
    private val ordinisAvailabilityMetrics: OrdinisAvailabilityMetrics,
    private val clock: Clock = Clock.systemUTC()
) {
    private val log = LoggerFactory.getLogger(javaClass)
    private val cacheLock = Any()

    @Volatile
    private var cachedPlatforms: PlatformsCache? = null

    /** Возвращает платформы из классификатора с необязательной фильтрацией по статусу НСИ. */
    fun loadPlatforms(status: String? = null): List<PlatformCatalogItem> {
        val platforms = loadCachedPlatforms()

        return platforms.filter { platform -> status.isNullOrBlank() || platform.status == status }
    }

    /** Возвращает платформы, с которыми TGU разрешено работать сейчас. */
    fun loadAllowedPlatforms(now: Instant = Instant.now()): List<PlatformCatalogItem> =
        loadPlatforms().filter { platform ->
            platform.noradId != null &&
                platform.status in classifierProperties.allowedPlatformStatuses &&
                platform.validFrom != null &&
                platform.validTo != null &&
                !platform.validFrom.isAfter(now) &&
                now.isBefore(platform.validTo)
        }

    /** Replaces the in-memory platform cache from a raw NSI response payload for test-only manual runs. */
    fun loadPlatformsFromNsiPayload(payloadJson: String): List<PlatformCatalogItem> =
        loadPlatformsFromNsiPayload(objectMapper.readValue(payloadJson, Array<NsiPlatformDto>::class.java).toList())

    /** Replaces the in-memory platform cache from an already-parsed NSI payload for test-only manual runs. */
    fun loadPlatformsFromNsiPayload(platforms: List<NsiPlatformDto>): List<PlatformCatalogItem> =
        synchronized(cacheLock) {
            cachePlatforms(platforms, Instant.now(clock))
        }

    private fun loadCachedPlatforms(): List<PlatformCatalogItem> {
        val now = Instant.now(clock)
        cachedPlatforms?.takeIf { cache -> cache.isActual(now) }?.let { cache -> return cache.platforms }

        return synchronized(cacheLock) {
            val currentNow = Instant.now(clock)
            cachedPlatforms
                ?.takeIf { cache -> cache.isActual(currentNow) }
                ?.platforms
                ?: fetchAndCachePlatforms(currentNow)
        }
    }

    private fun fetchAndCachePlatforms(now: Instant): List<PlatformCatalogItem> {
        return try {
            val platforms = cachePlatforms(platformsClassifierClient.fetchPlatforms(), now)
            ordinisAvailabilityMetrics.markAvailable()
            platforms
        } catch (ex: OrdinisUnavailableException) {
            ordinisAvailabilityMetrics.markUnavailable()
            useStaleCacheOrRethrow(ex)
        }
    }

    private fun useStaleCacheOrRethrow(ex: OrdinisUnavailableException): List<PlatformCatalogItem> {
        val staleCache = cachedPlatforms
        if (staleCache != null) {
            log.warn(
                "Ordinis classifier is unavailable. Using stale platform cache. platformsUrl={}, cachedCount={}, cacheExpiredAt={}, status={}, errorType={}, errorMessage={}",
                classifierProperties.platformsUrl,
                staleCache.platforms.size,
                staleCache.expiresAt,
                ex.statusCode,
                rootErrorType(ex),
                rootErrorMessage(ex)
            )
            return staleCache.platforms
        }

        log.error(
            "Ordinis classifier is unavailable and no platform cache is available. Platform sync cannot continue. platformsUrl={}, status={}, errorType={}, errorMessage={}",
            classifierProperties.platformsUrl,
            ex.statusCode,
            rootErrorType(ex),
            rootErrorMessage(ex),
            ex
        )
        throw ex
    }

    private fun cachePlatforms(platformDtos: List<NsiPlatformDto>, now: Instant): List<PlatformCatalogItem> {
        val platforms = platformDtos
            .mapNotNull { platform -> platform.toCatalogItem() }

        cachedPlatforms = PlatformsCache(
            platforms = platforms,
            expiresAt = now.plus(classifierProperties.platformsCacheTtl)
        )

        return platforms
    }

    private fun NsiPlatformDto.toCatalogItem(): PlatformCatalogItem? {
        val nsiId = id
        if (nsiId == null) {
            log.warn("Skipping NSI platform without id. businessKey={}, noradId={}", businessKey, data?.noradId)
            return null
        }

        return PlatformCatalogItem(
            nsiId = nsiId,
            businessKey = businessKey,
            name = data?.name,
            status = data?.status,
            noradId = data?.noradId,
            mission = data?.mission,
            validFrom = validFrom,
            validTo = validTo
        )
    }

    private data class PlatformsCache(
        val platforms: List<PlatformCatalogItem>,
        val expiresAt: Instant
    ) {
        fun isActual(now: Instant): Boolean = now.isBefore(expiresAt)
    }

    private fun rootErrorType(ex: OrdinisUnavailableException): String =
        ex.cause?.javaClass?.simpleName ?: ex.javaClass.simpleName

    private fun rootErrorMessage(ex: OrdinisUnavailableException): String? =
        ex.cause?.message ?: ex.message
}
