package kr.or.bok.rtgs.louisvuitton

import kr.or.bok.rtgs.common.ledger.Account
import org.slf4j.LoggerFactory
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.transaction.annotation.Transactional

data class InstitutionReq(val code: String, val name: String, val balance: Long = 1_000_000_000)
data class UserReq(val username: String, val displayName: String = "", val role: String = "ORG_S", val orgCode: String? = null)

/**
 * 관리자(Louis Vuitton) API. 인증 강제는 후속 단계이므로 현재는 관리 마스터/운영 기능만.
 */
@RestController
class AdminController(
    private val jdbc: JdbcTemplate,
    private val accounts: AccountRepository,
    private val users: AppUserRepository,
) {
    private val log = LoggerFactory.getLogger(javaClass)

    // ---------- 1) 테스트용 DB 초기화 ----------
    @PostMapping("/admin/reset")
    @Transactional
    fun reset(@RequestParam(defaultValue = "1000000000") initialBalance: Long): Map<String, Any?> {
        jdbc.execute("TRUNCATE transfer, journal_log, settlement_view, raw_message, notification")
        val updated = jdbc.update("UPDATE account SET balance = ?", initialBalance)
        log.warn("ADMIN RESET: cleared ledger tables, reset {} accounts to {}", updated, initialBalance)
        return mapOf("ok" to true, "accountsReset" to updated, "initialBalance" to initialBalance,
            "cleared" to listOf("transfer", "journal_log", "settlement_view", "raw_message", "notification"),
            "note" to "Kafka 저널 이력은 보존(오프셋 커밋으로 재처리 안 함). DB만 초기화.")
    }

    // ---------- 2) 코드 관리: 참가기관(당좌계좌) 마스터 ----------
    @GetMapping("/admin/institutions")
    fun institutions() = accounts.findAll().sortedBy { it.code }
        .map { mapOf("code" to it.code, "name" to it.name, "balance" to it.balance) }

    @PostMapping("/admin/institutions")
    fun upsertInstitution(@RequestBody r: InstitutionReq): Map<String, Any?> {
        require(r.code.matches(Regex("[0-9]{4}"))) { "기관코드는 숫자 4자리" }
        accounts.save(Account(code = r.code, name = r.name, balance = r.balance))
        return mapOf("ok" to true, "code" to r.code)
    }

    @DeleteMapping("/admin/institutions/{code}")
    fun deleteInstitution(@PathVariable code: String): Map<String, Any?> {
        accounts.deleteById(code)
        return mapOf("ok" to true, "deleted" to code)
    }

    /** 처리상태 코드 데이터 사전. */
    @GetMapping("/admin/status-codes")
    fun statusCodes() = listOf(
        mapOf("code" to "RCVD", "name" to "접수", "desc" to "Received - 접수됨"),
        mapOf("code" to "ACTC", "name" to "승인", "desc" to "AcceptedTechnicalValidation - 검증 통과"),
        mapOf("code" to "PDNG", "name" to "대기", "desc" to "Pending - 타 센터 수신/결제 전"),
        mapOf("code" to "ACSP", "name" to "예약", "desc" to "AcceptedSettlementInProcess - 결제 반영, 결과 대기"),
        mapOf("code" to "ACCC", "name" to "입금처리완료", "desc" to "AcceptedSettlementCompleted - 최종 완결"),
        mapOf("code" to "RJCT", "name" to "반려", "desc" to "Rejected - 검증 실패/잔액부족 등"),
    )

    // ---------- 3) 사용자 권한 관리 (관리 마스터) ----------
    @GetMapping("/admin/users")
    fun listUsers() = users.findAll().sortedBy { it.username }
        .map { mapOf("username" to it.username, "displayName" to it.displayName, "role" to it.role, "orgCode" to it.orgCode) }

    @PostMapping("/admin/users")
    fun upsertUser(@RequestBody r: UserReq): Map<String, Any?> {
        require(r.role in setOf("ADMIN", "ORG_S", "ORG_R")) { "role은 ADMIN/ORG_S/ORG_R" }
        users.save(AppUser(username = r.username, displayName = r.displayName, role = r.role,
            orgCode = r.orgCode, createdAt = System.currentTimeMillis()))
        return mapOf("ok" to true, "username" to r.username)
    }

    @DeleteMapping("/admin/users/{username}")
    fun deleteUser(@PathVariable username: String): Map<String, Any?> {
        users.deleteById(username)
        return mapOf("ok" to true, "deleted" to username)
    }

    // ---------- 4) 대사(정합성) 대시보드 ----------
    @GetMapping("/admin/summary")
    fun summary(): Map<String, Any?> = mapOf(
        "byStatus" to jdbc.queryForList("SELECT status, count(*) AS cnt FROM transfer GROUP BY status ORDER BY status"),
        "totalBalance" to jdbc.queryForObject("SELECT COALESCE(sum(balance),0) FROM account", Long::class.java),
        "accounts" to jdbc.queryForObject("SELECT count(*) FROM account", Long::class.java),
        "transfers" to jdbc.queryForObject("SELECT count(*) FROM transfer", Long::class.java),
        "maxSeq" to jdbc.queryForObject("SELECT COALESCE(max(global_seq),0) FROM transfer", Long::class.java),
    )

    // ---------- 5) 서비스 헬스(B5) — 하트비트 기반 liveness(헤드리스 포함) ----------
    @GetMapping("/admin/health")
    fun health(): List<Map<String, Any?>> {
        val now = System.currentTimeMillis()
        return jdbc.queryForList("SELECT service, center, last_seen, pid FROM service_heartbeat ORDER BY service")
            .map { r ->
                val last = (r["last_seen"] as? Number)?.toLong() ?: 0
                val ageMs = now - last
                mapOf(
                    "service" to r["service"], "center" to r["center"], "pid" to r["pid"],
                    "lastSeen" to last, "ageMs" to ageMs,
                    "status" to if (ageMs <= 15000) "UP" else "STALE",
                )
            }
    }

    // ---------- 6) 통계 현황 보고서 — 전체 거래 집계(성능·대사·기관별) ----------
    @GetMapping("/admin/report")
    fun report(): Map<String, Any?> {
        val byStatus = jdbc.queryForList("SELECT status, count(*) AS cnt FROM transfer GROUP BY status ORDER BY status")
        val transfers = jdbc.queryForObject("SELECT count(*) FROM transfer", Long::class.java) ?: 0
        val accounts = jdbc.queryForObject("SELECT count(*) FROM account", Long::class.java) ?: 0
        val maxSeq = jdbc.queryForObject("SELECT COALESCE(max(global_seq),0) FROM transfer", Long::class.java) ?: 0
        val totalBalance = jdbc.queryForObject("SELECT COALESCE(sum(balance),0) FROM account", Long::class.java) ?: 0

        // 대사(정합성): 원장=선저널=조회사본 건수 일치 + 총액 보존
        val journalCount = jdbc.queryForObject("SELECT count(*) FROM journal_log", Long::class.java) ?: 0
        val viewCount = jdbc.queryForObject("SELECT count(*) FROM settlement_view", Long::class.java) ?: 0
        val initialTotal = accounts * 1_000_000_000L

        // 금액 통계(ACCC 완결 대상)
        val amt = jdbc.queryForMap(
            "SELECT count(*) AS count, COALESCE(sum(amount),0) AS sum, COALESCE(round(avg(amount)),0) AS avg, " +
                "COALESCE(min(amount),0) AS min, COALESCE(max(amount),0) AS max FROM transfer WHERE status='ACCC'")

        // 처리시간(지연, ms) — ACCC & 전 단계 시각 존재 건. created_at=순번, updated_at=완결, received_at=접수(raw_message)
        val lat = jdbc.queryForMap(
            """
            SELECT count(*) AS count,
              COALESCE(round(avg(t.updated_at - r.received_at)),0) AS e2e_avg,
              COALESCE(round(percentile_cont(0.5) WITHIN GROUP (ORDER BY t.updated_at - r.received_at)),0) AS e2e_p50,
              COALESCE(round(percentile_cont(0.95) WITHIN GROUP (ORDER BY t.updated_at - r.received_at)),0) AS e2e_p95,
              COALESCE(max(t.updated_at - r.received_at),0) AS e2e_max,
              COALESCE(round(avg(t.created_at - r.received_at)),0) AS recv_to_actc,
              COALESCE(round(avg(t.pdng_at - t.created_at)),0) AS actc_to_pdng,
              COALESCE(round(avg(t.acsp_at - t.pdng_at)),0) AS pdng_to_acsp,
              COALESCE(round(avg(t.updated_at - t.acsp_at)),0) AS acsp_to_accc
            FROM transfer t JOIN raw_message r ON r.bmi = t.bmi
            WHERE t.status='ACCC' AND r.received_at > 0 AND t.pdng_at IS NOT NULL AND t.acsp_at IS NOT NULL
              AND t.created_at >= r.received_at   -- 인과 정합(접수≤순번) 행만: BMI 재사용 등 시계 역전 건 제외
            """.trimIndent())

        // 처리량(TPS) — 초 단위 버킷의 최대/평균. 접수(수용) vs 완결(처리) 관측치.
        // active_secs = 완결이 있었던 초 개수 → settled_avg는 활성 구간 평균(유휴 제외).
        val tps = jdbc.queryForMap(
            """
            WITH recv AS (SELECT received_at/1000 AS s, count(*) c FROM raw_message WHERE received_at > 0 GROUP BY received_at/1000),
                 done AS (SELECT updated_at/1000 AS s, count(*) c FROM transfer WHERE status='ACCC' GROUP BY updated_at/1000)
            SELECT COALESCE((SELECT max(c) FROM recv),0) AS received_peak,
                   COALESCE((SELECT max(c) FROM done),0) AS settled_peak,
                   COALESCE((SELECT round(avg(c)) FROM done),0) AS settled_avg,
                   COALESCE((SELECT count(*) FROM done),0) AS active_secs
            """.trimIndent())

        // 기관별 송·수신 집계(ACCC 기준) + 현재 잔액
        val byInstitution = jdbc.queryForList(
            """
            SELECT a.code, a.name, a.balance,
              COALESCE(s.cnt,0) AS sent_cnt, COALESCE(s.amt,0) AS sent_amt,
              COALESCE(v.cnt,0) AS recv_cnt, COALESCE(v.amt,0) AS recv_amt,
              (COALESCE(v.amt,0) - COALESCE(s.amt,0)) AS net
            FROM account a
            LEFT JOIN (SELECT sender_code c, count(*) cnt, sum(amount) amt FROM transfer WHERE status='ACCC' GROUP BY sender_code) s ON s.c = a.code
            LEFT JOIN (SELECT receiver_code c, count(*) cnt, sum(amount) amt FROM transfer WHERE status='ACCC' GROUP BY receiver_code) v ON v.c = a.code
            ORDER BY a.code
            """.trimIndent())

        return mapOf(
            "generatedAt" to System.currentTimeMillis(),
            "totals" to mapOf("transfers" to transfers, "accounts" to accounts, "maxSeq" to maxSeq, "byStatus" to byStatus),
            "reconciliation" to mapOf(
                "transferCount" to transfers, "journalCount" to journalCount, "viewCount" to viewCount,
                "consistent" to (transfers == journalCount && transfers == viewCount),
                "totalBalance" to totalBalance, "initialTotal" to initialTotal,
                "balancePreserved" to (totalBalance == initialTotal),
            ),
            "amount" to amt,
            "latency" to lat,
            "throughput" to tps,
            "byInstitution" to byInstitution,
        )
    }

    private fun lim(n: Int) = n.coerceIn(1, 200).toString()

    @GetMapping("/admin/ledger/transfers")
    fun transfers(@RequestParam(defaultValue = "20") limit: Int) = jdbc.queryForList(
        "SELECT bmi, global_seq, status, sender_code, receiver_code, amount, origin_center, updated_at " +
            "FROM transfer ORDER BY global_seq DESC NULLS LAST LIMIT ${lim(limit)}")

    @GetMapping("/admin/ledger/journal")
    fun journal(@RequestParam(defaultValue = "20") limit: Int) = jdbc.queryForList(
        "SELECT global_seq, bmi, applied FROM journal_log ORDER BY global_seq DESC LIMIT ${lim(limit)}")

    @GetMapping("/admin/ledger/views")
    fun views(@RequestParam(defaultValue = "20") limit: Int) = jdbc.queryForList(
        "SELECT bmi, global_seq, final_status, debtor_balance_after, creditor_balance_after, finalized_at " +
            "FROM settlement_view ORDER BY global_seq DESC NULLS LAST LIMIT ${lim(limit)}")
}
