package kr.or.bok.rtgs.louisvuitton

import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Service
import java.io.File
import java.net.InetSocketAddress
import java.net.Socket

/** 서비스 메타(고정). controllable=false는 관리자 화면에서 제어 불가(선행 필수: 자기자신/관문). */
data class ServiceMeta(val id: String, val name: String, val port: Int, val controllable: Boolean)

/**
 * 서비스 프로세스 제어(기동/중지/재기동) — **로컬 프로토타입 전용**.
 * 중지는 service_heartbeat에 기록된 pid를 ProcessHandle로 강제종료(선저널+Kafka 재생으로 무손실 승계).
 * 기동은 LouisVuitton이 도는 동일 JDK(java.home)로 <svc>-0.1.0.jar를 ProcessBuilder로 실행.
 */
@Service
class ServiceControlService(
    private val jdbc: JdbcTemplate,
    @Value("\${rtgs.center-id:DC1}") private val centerId: String,
    @Value("\${rtgs.center-count:1}") private val centerCount: String,
    @Value("\${rtgs.control.enabled:true}") val enabled: Boolean,
    @Value("\${rtgs.control.backend-dir:C:/ai-dev/workspace/rtgs/backend}") private val backendDir: String,
    @Value("\${rtgs.control.log-dir:C:/ai-dev/home/temp/rtgs-logs}") private val logDir: String,
) {
    private val log = LoggerFactory.getLogger(javaClass)

    private val services = listOf(
        ServiceMeta("sequencer", "Sequencer", 8090, true),
        ServiceMeta("chanel", "Chanel", 8091, true),
        ServiceMeta("dior", "Dior", 8092, true),
        ServiceMeta("hermes", "Hermes", 8093, true),
        ServiceMeta("prada", "Prada", 8094, true),
        ServiceMeta("gucci", "Gucci", 8095, false),
        ServiceMeta("louisvuitton", "Louis Vuitton", 8099, false),
    )
    private val byId = services.associateBy { it.id }
    private val upThresholdMs = 15_000L

    /** 서비스 7 + 인프라(PostgreSQL/Kafka) 통합 상태. */
    fun status(): Map<String, Any?> {
        val now = System.currentTimeMillis()
        val hb = jdbc.queryForList("SELECT service, last_seen, pid FROM service_heartbeat WHERE center = ?", centerId)
            .associateBy { it["service"] as String }
        val svc = services.map { m ->
            val row = hb[m.id]
            val last = (row?.get("last_seen") as? Number)?.toLong()
            val ageMs = last?.let { now - it }
            val up = ageMs != null && ageMs <= upThresholdMs
            mapOf(
                "id" to m.id, "name" to m.name, "port" to m.port, "controllable" to m.controllable,
                "status" to if (up) "UP" else "DOWN",
                "pid" to (row?.get("pid") as? Number)?.toLong(), "ageMs" to ageMs,
            )
        }
        val infra = listOf(
            mapOf("id" to "postgres", "name" to "PostgreSQL", "port" to 5433, "reachable" to tcpReachable(5433)),
            mapOf("id" to "kafka", "name" to "Kafka", "port" to 9092, "reachable" to tcpReachable(9092)),
        )
        return mapOf("services" to svc, "infra" to infra, "controlEnabled" to enabled)
    }

    fun start(id: String): Map<String, Any?> {
        val m = requireControllable(id)
        if (isUp(id)) return mapOf("ok" to false, "message" to "${m.name} 이미 실행 중")
        val jar = File("$backendDir/${m.id}/build/libs/${m.id}-0.1.0.jar")
        require(jar.exists()) { "jar 없음: ${jar.path} (먼저 빌드 필요)" }

        File(logDir).mkdirs()
        val javaExe = File(System.getProperty("java.home"), "bin/java.exe")
        val pb = ProcessBuilder(javaExe.path, "-jar", jar.path)
        pb.environment()["CENTER_ID"] = centerId
        pb.environment()["CENTER_COUNT"] = centerCount
        pb.directory(File(backendDir))
        pb.redirectErrorStream(true)
        pb.redirectOutput(ProcessBuilder.Redirect.appendTo(File(logDir, "${m.id}.log")))
        val proc = pb.start()
        log.warn("SERVICE START {} pid={} jar={}", m.id, proc.pid(), jar.path)
        return mapOf("ok" to true, "message" to "${m.name} 기동 요청됨 (pid ${proc.pid()})", "pid" to proc.pid())
    }

    fun stop(id: String): Map<String, Any?> {
        val m = requireControllable(id)
        val pid = pidOf(id) ?: return mapOf("ok" to false, "message" to "${m.name} 이미 중지(하트비트 없음)")
        check(pid != ProcessHandle.current().pid()) { "자기 자신은 종료 불가" }
        val killed = ProcessHandle.of(pid).map { it.destroyForcibly() }.orElse(false)
        jdbc.update("DELETE FROM service_heartbeat WHERE service = ? AND center = ?", id, centerId)
        log.warn("SERVICE STOP {} pid={} killed={}", m.id, pid, killed)
        return mapOf("ok" to true, "message" to "${m.name} 중지 (pid $pid)", "killed" to killed)
    }

    fun restart(id: String): Map<String, Any?> {
        val m = requireControllable(id)
        val pid = pidOf(id)
        if (pid != null && pid != ProcessHandle.current().pid()) {
            ProcessHandle.of(pid).ifPresent { h ->
                h.destroyForcibly()
                var waited = 0
                while (h.isAlive && waited < 10_000) { Thread.sleep(200); waited += 200 }
            }
            jdbc.update("DELETE FROM service_heartbeat WHERE service = ? AND center = ?", id, centerId)
        }
        Thread.sleep(500)
        val r = start(id)
        return r + ("message" to "${m.name} 재기동됨")
    }

    fun tailLog(id: String, lines: Int): Map<String, Any?> {
        byId[id] ?: throw IllegalArgumentException("알 수 없는 서비스: $id")
        val logFile = File(logDir, "$id.log")
        if (!logFile.exists()) return mapOf("id" to id, "found" to false, "lines" to emptyList<String>())
        val all = logFile.readLines()
        val n = lines.coerceIn(1, 1000)
        val tail = if (all.size <= n) all else all.subList(all.size - n, all.size)
        return mapOf("id" to id, "found" to true, "lines" to tail, "path" to logFile.path)
    }

    private fun requireControllable(id: String): ServiceMeta {
        val m = byId[id] ?: throw IllegalArgumentException("알 수 없는 서비스: $id")
        require(m.controllable) { "제어 불가(선행 필수 서비스): ${m.name}" }
        return m
    }

    private fun isUp(id: String): Boolean {
        val last = jdbc.queryForList("SELECT last_seen FROM service_heartbeat WHERE service = ? AND center = ?", id, centerId)
            .firstOrNull()?.let { (it["last_seen"] as? Number)?.toLong() } ?: return false
        return System.currentTimeMillis() - last <= upThresholdMs
    }

    private fun pidOf(id: String): Long? =
        jdbc.queryForList("SELECT pid FROM service_heartbeat WHERE service = ? AND center = ?", id, centerId)
            .firstOrNull()?.let { (it["pid"] as? Number)?.toLong() }

    private fun tcpReachable(port: Int): Boolean = try {
        Socket().use { it.connect(InetSocketAddress("localhost", port), 1000); true }
    } catch (e: Exception) { false }
}
