package space.nstart.pcp.routepassportconsumer.service

import org.apache.kafka.clients.producer.ProducerRecord
import org.slf4j.LoggerFactory
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.stereotype.Service
import space.nstart.pcp.routepassportconsumer.entity.RoutePassportOutboxEntity
import java.nio.charset.StandardCharsets
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.nanoseconds

/**
 * Сервис физической публикации одной готовой outbox-записи в Kafka.
 */
@Service
class RoutePassportKafkaPublisherService(
    /** KafkaTemplate отправляет готовую outbox-запись в её целевой topic. */
    private val kafkaTemplate: KafkaTemplate<String, String>,
) {
    private val log = LoggerFactory.getLogger(this::class.java)

    /**
     * Публикует ровно одну outbox-запись и пробрасывает ошибку отправки вызывающему коду.
     */
    fun publish(outbox: RoutePassportOutboxEntity) {
        val payload = outbox.payload.toString()
        val record = ProducerRecord(outbox.topic, outbox.messageKey, payload)
        record.headers().add(TYPE_HEADER, outbox.eventType.toByteArray(StandardCharsets.UTF_8))
        val startedAt = System.nanoTime()

        try {
            val result = kafkaTemplate.send(record)
                .get(DEFAULT_PUBLISH_TIMEOUT_SECONDS, TimeUnit.SECONDS)
            val sendDurationMs = (System.nanoTime() - startedAt).nanoseconds.inWholeMilliseconds

            log.info(
                "Kafka outbox message published: outboxId={}, routeId={}, eventType={}, topic={}, messageKey={}, partition={}, offset={}, sendDurationMs={}",
                outbox.id,
                outbox.routeId,
                outbox.eventType,
                outbox.topic,
                outbox.messageKey,
                result.recordMetadata.partition(),
                result.recordMetadata.offset(),
                sendDurationMs,
            )
        } catch (exception: InterruptedException) {
            Thread.currentThread().interrupt()
            logPublishFailure(outbox, exception)
            throw exception
        } catch (exception: ExecutionException) {
            val failure = exception.cause
            logPublishFailure(outbox, failure ?: exception)

            when (failure) {
                is Exception -> throw failure
                else -> throw exception
            }
        } catch (exception: Exception) {
            logPublishFailure(outbox, exception)
            throw exception
        }
    }

    private fun logPublishFailure(outbox: RoutePassportOutboxEntity, exception: Throwable) {
        log.error(
            "Failed to publish kafka outbox message: outboxId={}, routeId={}, eventType={}, topic={}, messageKey={}, exceptionClass={}, exceptionMessage={}",
            outbox.id,
            outbox.routeId,
            outbox.eventType,
            outbox.topic,
            outbox.messageKey,
            exception::class.java.name,
            exception.message,
            exception,
        )
    }

    private companion object {
        const val TYPE_HEADER = "type"
        const val DEFAULT_PUBLISH_TIMEOUT_SECONDS = 10L
    }
}
