package network

import AutoTradeItem
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import model.CandleData
import model.RankingResponse
import model.RankingStock
import model.RankingType
import model.StockBalanceResponse

import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import model.*
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter

object KisTradeService {
    private val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json(Json {
                ignoreUnknownKeys = true
                encodeDefaults = true // 기본값이 포함된 요청 바디를 정확히 전송하기 위해 필요
            })
        }
        // [수정] 모든 로그(Headers + Body)를 찍도록 설정
        install(Logging) {
            logger = Logger.DEFAULT
            level = LogLevel.BODY
        }
    }

    private val prodUrl = "https://openapi.koreainvestment.com:9443"
    private val vtsUrl = "https://openapivts.koreainvestment.com:29443"

    /**
     * [1] 통합 잔고 조회 (국내 + 해외 합산)
     */
    suspend fun fetchIntegratedBalance(): Result<UnifiedBalance> = coroutineScope {
        val config = KisSession.config

        // 국내와 해외 잔고를 비동기로 동시 호출
        val domesticJob = async { fetchDomesticRawBalance() }
        val overseasJob = async { fetchOverseasRawBalance() }

        try {
            val domRes = domesticJob.await().getOrNull()
            val ovsRes = overseasJob.await().getOrNull()

            val combinedHoldings = mutableListOf<UnifiedStockHolding>()

            // 국내 종목 매핑
            domRes?.output1?.forEach {
                combinedHoldings.add(UnifiedStockHolding(
                    code = it.pdno, name = it.prdt_name, quantity = it.hldg_qty,
                    avgPrice = it.pchs_avg_pric, currentPrice = it.prpr,
                    profitRate = it.evlu_pfls_rt, evalAmount = it.evlu_amt, isDomestic = true
                ))
            }

            // 해외 종목 매핑 (해외 API 응답 모델 구조에 따라 필드 매핑)
            ovsRes?.output1?.forEach {
                combinedHoldings.add(UnifiedStockHolding(
                    code = it.pdno, name = it.prdt_name, quantity = it.hldg_qty,
                    avgPrice = it.pchs_avg_pric, currentPrice = it.prpr,
                    profitRate = it.evlu_pfls_rt, evalAmount = it.evlu_amt, isDomestic = false
                ))
            }

            val totalAmt = (domRes?.output2?.firstOrNull()?.tot_evlu_amt?.toLongOrNull() ?: 0L) +
                    (ovsRes?.output2?.firstOrNull()?.tot_evlu_amt?.toLongOrNull() ?: 0L)

            Result.success(UnifiedBalance(
                totalAsset = String.format("%,d", totalAmt),
                totalProfitRate = domRes?.output2?.firstOrNull()?.evlu_pfls_rt ?: "0.0",
                holdings = combinedHoldings
            ))
        } catch (e: Exception) { Result.failure(e) }
    }

    /**
     * [통합 순위 조회] 국내/해외 분기 처리
     */
    suspend fun fetchMarketRanking(type: RankingType, isDomestic: Boolean): Result<List<RankingStock>> {
        return if (isDomestic) {
            fetchDomesticRanking(type)
        } else {
            fetchOverseasRanking(type)
        }
    }


    /**
     * [국내 주식 순위] 명세서 기반 파라미터 최적화
     */
    private suspend fun fetchDomesticRanking(type: RankingType): Result<List<RankingStock>> {
        val config = KisSession.config
        return try {
            val response = client.get("$prodUrl${type.path}") {
                header("authorization", "Bearer ${config.marketToken}")
                header("appkey", config.realAppKey)
                header("appsecret", config.realSecretKey)
                header("tr_id", type.trId)
                header("custtype", "P")

                parameter("FID_COND_MRKT_DIV_CODE", "J")
                parameter("FID_COND_SCR_DIV_CODE", type.scrNo)
                parameter("FID_INPUT_ISCD", "0000") // 전체 시장
                parameter("FID_DIV_CLS_CODE", "0")   // 전체

                parameter("FID_ETC_CLS_CODE", "0")
                parameter("FID_PRC_CLS_CODE", "0")
                when(type) {
                    RankingType.VALUE -> {
                        parameter("FID_BLNG_CLS_CODE", type.sortCode)
                    }
                    RankingType.VOLUME -> {
                        parameter("FID_BLNG_CLS_CODE",type.sortCode)
                    }
                    RankingType.FALL -> {
                        parameter("FID_RANK_SORT_CLS_CODE", type.sortCode)

                    }
                    RankingType.RISE -> {
                        parameter("FID_RANK_SORT_CLS_CODE", type.sortCode)
                    }
//                    RankingType.AFTER -> {
//                        parameter("FID_MKOP_CLS_CODE", type.sortCode)
//                    }
//                    RankingType.BEFORE -> {
//                        parameter("FID_MKOP_CLS_CODE", type.sortCode)
//                    }
                    else -> {

                    }
                }
                parameter("FID_PBMN", "")
                parameter("FID_APLY_RANG_PRC_1", "")
                parameter("FID_TRGT_CLS_CODE", "11111111")
                parameter("FID_TRGT_EXLS_CLS_CODE", "000000")
                parameter("FID_RSFL_RATE2", "")
                parameter("FID_RSFL_RATE1", "")
                parameter("FID_INPUT_CNT_1", "0")
                parameter("FID_INPUT_PRICE_1", "")
                parameter("FID_INPUT_PRICE_2", "")
                parameter("FID_VOL_CNT", "")
                parameter("FID_INPUT_DATE_1", "")







                // 상승/하락률 순위(HHPST01710000)일 경우 추가 파라미터
                if (type.trId == "HHPST01710000") {
                    parameter("fid_diff_div_code", "00") // 00: 전일 대비
                }
            }
            val body = response.body<RankingResponse>()
            if (body.rt_cd == "0") Result.success(body.list) else Result.failure(Exception(body.msg1))
        } catch (e: Exception) { Result.failure(e) }
    }

    /**
     * [추가] 기간별(일/주/월) 차트 데이터 조회
     * @param periodCode "D"(일), "W"(주), "M"(월)
     */
    suspend fun fetchPeriodChartData(
        stockCode: String,
        periodCode: String = "D",
        isDomestic: Boolean = true
    ): Result<List<CandleData>> {
        val config = KisSession.config
        val path = if (isDomestic) "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice"
        else "/uapi/overseas-stock/v1/quotations/inquire-daily-itemchartprice"

        val today = LocalDate.now()
        val formatter = DateTimeFormatter.ofPattern("yyyyMMdd")
        val endDate = today.format(formatter)

        // [수정] 100개를 가져오기 위해 시작일을 너무 멀지 않게 설정 (약 6개월 전)
        // 이렇게 하면 종료일(오늘)부터 소급하여 최대 100개의 최신 데이터를 안전하게 가져옵니다.
        val startDate = when (periodCode) {
            "D" -> today.minusMonths(6).format(formatter) // 일봉: 6개월치면 100개 충분
            "W" -> today.minusYears(2).format(formatter)  // 주봉: 2년치
            "M" -> today.minusYears(8).format(formatter)  // 월봉: 8년치
            else -> today.minusYears(1).format(formatter)
        }
        return try {
            val response = client.get("$prodUrl$path") {
                header("authorization", "Bearer ${config.marketToken}")
                header("appkey", config.realAppKey)
                header("appsecret", config.realSecretKey)
                header("tr_id", if (isDomestic) "FHKST03010100" else "HHDFS76240000")
                header("custtype", "P")

                parameter("FID_INPUT_DATE_1", startDate)
                parameter("FID_INPUT_DATE_2", endDate)
                parameter("FID_COND_MRKT_DIV_CODE", "J")
                parameter("FID_INPUT_ISCD", stockCode)
                parameter("FID_PERIOD_DIV_CODE", periodCode) // D, W, M
                parameter("FID_ORG_ADJ_PRC", "0")
            }

            val body = response.body<JsonObject>()
            val output2 = body["output2"]?.jsonArray

            val candles = output2?.map { element ->
                val obj = element.jsonObject
                CandleData(
                    stck_bsop_date = obj["stck_bsop_date"]?.jsonPrimitive?.content ?: "",
                    stck_prpr = obj["stck_prpr"]?.jsonPrimitive?.content ?: "0", // 분봉/시간 데이터는 stck_prpr이 종가
                    stck_oprc = obj["stck_oprc"]?.jsonPrimitive?.content ?: "0",
                    stck_hgpr = obj["stck_hgpr"]?.jsonPrimitive?.content ?: "0",
                    stck_lwpr = obj["stck_lwpr"]?.jsonPrimitive?.content ?: "0",
                    cntg_vol = obj["cntg_vol"]?.jsonPrimitive?.content ?: "0",
                    acml_tr_pbmn = obj["acml_tr_pbmn"]?.jsonPrimitive?.content ?: "0",
                    stck_cntg_hour = obj["stck_cntg_hour"]?.jsonPrimitive?.content ?: "0",
                )
            }?.reversed() ?: emptyList()

            Result.success(candles)
        } catch (e: Exception) { Result.failure(e) }
    }

    /**
     * [해외 주식 순위] 모델 매핑 오류 수정
     */
    private suspend fun fetchOverseasRanking(type: RankingType): Result<List<RankingStock>> {
        val config = KisSession.config
        val path = "/uapi/overseas-stock/v1/quotations/rank-fluctuation"
        val trId = "HHDFS76240000"

        return try {
            val response = client.get("$prodUrl$path") {
                header("authorization", "Bearer ${config.marketToken}")
                header("appkey", config.realAppKey)
                header("appsecret", config.realSecretKey)
                header("tr_id", trId)
                header("custtype", "P")

                parameter("EXCD", "NAS") // 기본 나스닥

                val gubn = when (type) {
                    RankingType.RISE -> "0"
                    RankingType.FALL -> "1"
                    RankingType.VOLUME -> "2"
                    RankingType.VALUE -> "3"
                    else -> "0"
                }
                parameter("GUBN", gubn)
            }

            // [수정] OverseasRankingResponse로 정확히 파싱 후 변환
            val body = response.body<OverseasRankingResponse>()
            if (body.rt_cd == "0") {
                Result.success(body.output.map { it.toRankingStock() })
            } else {
                Result.failure(Exception("해외 랭킹 에러: ${body.msg1}"))
            }
        } catch (e: Exception) { Result.failure(e) }
    }

    /**
     * [3] 통합 주문 (지정가/시장가 매수/매도)
     */
    suspend fun postOrder(
        stockCode: String,
        qty: String,
        price: String,
        isBuy: Boolean
    ): Result<String> {
        val config = KisSession.config
        val isDomestic = stockCode.length == 6 && stockCode.all { it.isDigit() }
        val baseUrl = if (config.isSimulation) vtsUrl else prodUrl

        // 계좌번호 처리: 8자리면 01 자동 추가
        var pureAccount = config.accountNo.replace("-", "").trim()
        if (pureAccount.length == 8) pureAccount += "01"

        val cano = pureAccount.take(8)
        val acntPrdtCd = pureAccount.takeLast(2)

        val trId = when {
            isDomestic && config.isSimulation -> if (isBuy) "VTTC0802U" else "VTTC0801U"
            isDomestic && !config.isSimulation -> if (isBuy) "TTTC0802U" else "TTTC0801U"
            else -> if (isBuy) "TTTS3002U" else "TTTS3001U"
        }

        return try {
            val response = client.post("$baseUrl/uapi/${if(isDomestic) "domestic" else "overseas"}-stock/v1/trading/order-cash") {
                header("authorization", "Bearer ${config.tradeToken}")
                header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey)
                header("appsecret", if (config.isSimulation) config.vtsSecretKey else config.realSecretKey)
                header("tr_id", trId)
                header("custtype", "P") // [해결] 필수 헤더 추가
                header("Content-Type", "application/json")

                setBody(mapOf(
                    "CANO" to cano,
                    "ACNT_PRDT_CD" to acntPrdtCd,
                    "PDNO" to stockCode,
                    "ORD_DVSN" to if (price == "0" || price.isEmpty()) "01" else "00",
                    "ORD_QTY" to qty,
                    "ORD_UNPR" to if (price.isEmpty() || price == "0") "0" else price
                ))
            }

            val body = response.body<JsonObject>() // [해결] Polymorphic 직렬화 에러 방지
            val rtCd = body["rt_cd"]?.jsonPrimitive?.content
            val msg = body["msg1"]?.jsonPrimitive?.content ?: "메시지 없음"

            if (rtCd == "0") {
                // 응답의 output 객체에서 주문 번호(ODNO) 추출
                val orderNo = body["output"]?.jsonObject?.get("ODNO")?.jsonPrimitive?.content
                    ?: body["output"]?.jsonObject?.get("odno")?.jsonPrimitive?.content // API마다 대소문자가 다를 수 있음
                    ?: ""
                Result.success(orderNo) // 성공 시 주문 번호 반환
            } else {
                val msg = body["msg1"]?.jsonPrimitive?.content ?: "메시지 없음"
                Result.failure(Exception("❌ 오류 ($rtCd): $msg"))
            }
        } catch (e: Exception) { Result.failure(e) }
    }



    /**
     * [추가] 국내 미체결 내역 조회
     */
    suspend fun fetchUnfilledOrders(): Result<List<UnfilledOrder>> {
        val config = KisSession.config
        if (config.isSimulation) return Result.success(emptyList<UnfilledOrder>())
        val baseUrl = if (config.isSimulation) vtsUrl else prodUrl
        val trId = "TTTC0084R"
        var pureAccount = config.accountNo.replace("-", "").trim()
        if (pureAccount.length == 8) pureAccount += "01"

        val cano = pureAccount.take(8)
        val acntPrdtCd = pureAccount.takeLast(2)
        return try {
            val response = client.get("$baseUrl/uapi/domestic-stock/v1/trading/inquire-psbl-rvsecncl") {
                header("authorization", "Bearer ${config.tradeToken}")
                header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey)
                header("appsecret", if (config.isSimulation) config.vtsSecretKey else config.realSecretKey)
                header("tr_id", trId)
                header("custtype", "P")

                parameter("CANO", cano)
                parameter("ACNT_PRDT_CD", acntPrdtCd)
                parameter("CTX_AREA_FK100", "")
                parameter("CTX_AREA_NK100", "")
                parameter("T_GUBUN", "0")
                parameter("LOAN_DT", "")
                parameter("P_S_GUBUN", "0")
                parameter("INQR_DVSN_1", "0")
                parameter("INQR_DVSN_2", "0")
            }
            val body = response.body<UnfilledResponse>()
            if (body.rt_cd == "0") {
                var result = body
                Result.success(result.output)
            }
            else Result.failure(Exception(body.msg1))
        } catch (e: Exception) { Result.failure(e) }
    }

    /**
     * [추가] 주문 취소 (정정/취소 API)
     */
    suspend fun cancelOrder(orgNo: String, stockCode: String): Result<String> {
        val config = KisSession.config
        val baseUrl = if (config.isSimulation) vtsUrl else prodUrl
        val trId = if (config.isSimulation) "VTTC0803U" else "TTTC0803U"
        var pureAccount = config.accountNo.replace("-", "").trim()
        if (pureAccount.length == 8) pureAccount += "01"

        val cano = pureAccount.take(8)
        val acntPrdtCd = pureAccount.takeLast(2)
        return try {
            println("orgNo")
            val response = client.post("$baseUrl/uapi/domestic-stock/v1/trading/order-rvsecncl") {
                header("authorization", "Bearer ${config.tradeToken}")
                header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey)
                header("appsecret", if (config.isSimulation) config.vtsSecretKey else config.realSecretKey)
                header("tr_id", trId)
                header("Content-Type", "application/json")

                setBody(mapOf(
                    "CANO" to cano,
                    "ACNT_PRDT_CD" to acntPrdtCd,
                    "KRX_FWDG_ORD_ORGNO" to "", // 공란 혹은 지점번호
                    "ORGN_ODNO" to orgNo,      // 취소할 원주문번호
                    "RVSE_CNCL_DVSN_CD" to "02",    // 01: 정정, 02: 취소
                    "ORD_DVSN" to "00",          // 지정가
                    "ORD_QTY" to "0",            // 0이면 전량 취소
                    "ORD_UNPR" to "0",
                    "QTY_ALL_ORD_YN" to "Y",
                ))
            }
            val body = response.body<JsonObject>()
            if (body["rt_cd"]?.jsonPrimitive?.content == "0") Result.success("취소 완료")
            else Result.failure(Exception(body["msg1"]?.jsonPrimitive?.content))
        } catch (e: Exception) { Result.failure(e) }
    }

    /**
     * [4] 웹소켓 승인키(Approval Key) 발급
     */
    suspend fun refreshWebsocketKey(): Boolean {
        val config = KisSession.config
        return try {
            val response = client.post("$prodUrl/oauth2/Approval") {
                header("Content-Type", "application/json")
                setBody(mapOf("grant_type" to "client_credentials", "appkey" to config.realAppKey, "secretkey" to config.realSecretKey))
            }
            if (response.status == HttpStatusCode.OK) {
                val approvalKey = response.body<Map<String, String>>()["approval_key"]
                if (approvalKey != null) {
                    KisSession.config = KisSession.config.copy(websocketToken = approvalKey)
                    true
                } else false
            } else false
        } catch (e: Exception) { false }
    }

    /**
     * [5] 차트 데이터 조회 (일봉 기준)
     */
    suspend fun fetchChartData(stockCode: String, isDomestic: Boolean): Result<List<CandleData>> {
        val config = KisSession.config
        // 국내 주식 분봉 조회 TR ID: FHKST03010200
        val trId = if (isDomestic) "FHKST03010200" else "HHDFS76240000"
        val path = if (isDomestic)
            "/uapi/domestic-stock/v1/quotations/inquire-time-itemchartprice"
        else "/uapi/overseas-stock/v1/quotations/inquire-time-itemchartprice"
        val now = LocalTime.now().minusMinutes(30)
        val searchTime = if (now.isAfter(LocalTime.of(15, 30))) {
            "150000"
        } else {
            now.format(DateTimeFormatter.ofPattern("HHmmss"))
        }
        return try {
            val response = client.get("$prodUrl$path") {
                header("authorization", "Bearer ${config.marketToken}")
                header("appkey", config.realAppKey)
                header("appsecret", config.realSecretKey)
                header("tr_id", trId)
                header("custtype", "P")
                header("content-type", "application/json; charset=utf-8")

                parameter("FID_ETC_CLS_CODE", "")
                parameter("FID_COND_MRKT_DIV_CODE", "J")
                parameter("FID_INPUT_ISCD", stockCode)
                parameter("FID_INPUT_HOUR_1", searchTime) // 장 마감 시간까지
                parameter("FID_PW_DATA_INCU_YN", "Y")   // 전일 데이터 포함 여부
            }

            // API 응답에서 output2(캔들 리스트)를 CandleData로 변환 (역순으로 오므로 reverse 필요)
            val body = response.body<JsonObject>()
            val output2 = body["output2"]?.jsonArray

            val candles = output2?.map { element ->
                val obj = element.jsonObject
                CandleData(
                    stck_bsop_date = obj["stck_bsop_date"]?.jsonPrimitive?.content ?: "",
                    stck_prpr = obj["stck_prpr"]?.jsonPrimitive?.content ?: "0", // 분봉/시간 데이터는 stck_prpr이 종가
                    stck_oprc = obj["stck_oprc"]?.jsonPrimitive?.content ?: "0",
                    stck_hgpr = obj["stck_hgpr"]?.jsonPrimitive?.content ?: "0",
                    stck_lwpr = obj["stck_lwpr"]?.jsonPrimitive?.content ?: "0",
                    cntg_vol = obj["cntg_vol"]?.jsonPrimitive?.content ?: "0",
                    acml_tr_pbmn = obj["acml_tr_pbmn"]?.jsonPrimitive?.content ?: "0",
                    stck_cntg_hour = obj["stck_cntg_hour"]?.jsonPrimitive?.content ?: "0",
                )
            }?.reversed() ?: emptyList()

            Result.success(candles)
        } catch (e: Exception) { Result.failure(e) }
    }

    // --- 내부 Raw 호출용 (통합 잔고에서 사용) ---
    private suspend fun fetchDomesticRawBalance(): Result<StockBalanceResponse> {
        val config = KisSession.config
        val baseUrl = if (config.isSimulation) vtsUrl else prodUrl
        val trId = if (config.isSimulation) "VTTC8434R" else "TTTC8434R"
        var pureAccount = config.accountNo.replace("-", "").trim()
        if (pureAccount.length == 8) pureAccount += "01"

        val cano = pureAccount.take(8)
        val acntPrdtCd = pureAccount.takeLast(2)
        return try {
            val response = client.get("$baseUrl/uapi/domestic-stock/v1/trading/inquire-balance") {
                header("authorization", "Bearer ${config.tradeToken}")
                header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey)
                header("appsecret", if (config.isSimulation) config.vtsSecretKey else config.realSecretKey)
                header("tr_id", trId)
                parameter("CANO", cano)
                parameter("ACNT_PRDT_CD", acntPrdtCd)
                parameter("AFHR_FLPR_YN", "N")
                parameter("OFL_YN", "N")
                parameter("INQR_DVSN", "02")
                parameter("UNPR_DVSN", "01")
                parameter("FUND_STTL_ICLD_YN", "N")
                parameter("FNCG_AMT_AUTO_RDPT_YN", "N")
                parameter("PRCS_DVSN", "00")
                parameter("CTX_AREA_FK100", "")
                parameter("CTX_AREA_NK100", "")
            }
            Result.success(response.body())
        } catch (e: Exception) { Result.failure(e) }
    }

    private suspend fun fetchOverseasRawBalance(): Result<StockBalanceResponse> {
        // 해외 잔고 조회 API 명세에 맞춰 구현 (국내와 유사하나 TR ID 및 파라미터 다름)
        return Result.failure(Exception("Not Implemented"))
    }
}