package com.ga.external.bank;

import com.fasterxml.jackson.databind.JsonNode;
import com.ga.common.adapter.bank.BankTransferAdapter;
import com.ga.common.adapter.bank.BankTransferRequest;
import com.ga.common.adapter.bank.BankTransferResponse;
import com.ga.common.adapter.bank.BankTransferStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * KFTC(금융결제원) 오픈뱅킹 <b>입금이체</b> 어댑터 — 실구현.
 * <p>
 * 활성 조건: <code>app.adapter.bank=kftc</code> (기본은 mock). 키/약정계좌는 {@link KftcProperties} 로 외부화.
 * <p>
 * GA→설계사 지급은 "이용기관 약정 출금계좌 → 설계사 입금계좌" 흐름이므로 KFTC <b>입금이체</b>
 * (<code>POST /v2.0/transfer/deposit/acnt_num</code>) 를 사용한다. 단건을 req_list 1건으로 호출한다.
 * <ul>
 *   <li>requestTransfer: 입금이체 요청 → rsp_code 정상 시 ACCEPTED, api_tran_id 를 txId 로 반환</li>
 *   <li>queryStatus: 이체결과조회(<code>GET /v2.0/transfer/result</code>) → 정상 시 SETTLED</li>
 * </ul>
 * 필수 설정 누락 시 fail-fast. 외부호출 감사로그는 ga-api 의 AOP가 남긴다.
 * <p>
 * <b>주의(운영)</b>: Access Token(2-legged 이용기관 토큰) 재발급, 약정계좌 등록, 실명확인 옵션 정책은
 * 금융결제원 계약·이용기관 설정에 따른다. 본 어댑터는 문서화된 API 형식에 맞춰 호출하며 실거래 검증은
 * 테스트베드/운영 키 주입 후 수행한다.
 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "app.adapter", name = "bank", havingValue = "kftc")
@EnableConfigurationProperties(KftcProperties.class)
public class KftcBankTransferAdapter implements BankTransferAdapter {

    private static final DateTimeFormatter TS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    private final KftcProperties props;
    private final RestClient client;

    public KftcBankTransferAdapter(KftcProperties props) {
        this.props = props;
        this.client = RestClient.builder().baseUrl(props.getBaseUrl()).build();
    }

    @Override
    public BankTransferResponse requestTransfer(BankTransferRequest request) {
        require(props.getAccessToken(), "app.adapter.kftc.access-token");
        require(props.getCntrAccountNum(), "app.adapter.kftc.cntr-account-num");

        String bankTranId = buildBankTranId(request.getWithdrawRequestId());
        String tranDtime = LocalDateTime.now().format(TS);

        // 입금이체 단건 (req_list 1건). 문서화된 KFTC 입금이체 필드명 사용.
        Map<String, Object> item = new LinkedHashMap<>();
        item.put("tran_no", "1");
        item.put("bank_tran_id", bankTranId);
        item.put("bank_code_std", request.getBankCode());                  // 수취 은행 표준코드
        item.put("account_num", request.getAccountNo());                   // 수취 계좌번호
        item.put("account_holder_name", request.getAccountHolderName());   // 수취인 성명
        item.put("print_content", trim(request.getMemo(), 20));            // 입금계좌 인자내역
        item.put("tran_amt", request.getAmount().toBigInteger().toString());
        item.put("req_client_name", request.getAccountHolderName());
        item.put("req_client_bank_code", request.getBankCode());
        item.put("req_client_account_num", request.getAccountNo());
        item.put("req_client_num", String.valueOf(request.getWithdrawRequestId()));
        item.put("transfer_purpose", props.getTransferPurpose());

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("cntr_account_type", props.getCntrAccountType());
        body.put("cntr_account_num", props.getCntrAccountNum());
        body.put("wd_pass_phrase", "NONE");
        body.put("wd_print_content", trim(props.getWdPrintContent(), 20));
        body.put("name_check_option", props.getNameCheckOption());
        body.put("tran_dtime", tranDtime);
        body.put("req_cnt", "1");
        body.put("req_list", List.of(item));

        try {
            JsonNode res = client.post()
                    .uri("/v2.0/transfer/deposit/acnt_num")
                    .header("Authorization", "Bearer " + props.getAccessToken())
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(body)
                    .retrieve()
                    .body(JsonNode.class);
            return toResponse(res, BankTransferStatus.ACCEPTED);
        } catch (RestClientException e) {
            log.warn("[KFTC] 입금이체 실패 withdrawId={} bank={} err={}",
                    request.getWithdrawRequestId(), request.getBankCode(), e.getMessage());
            return BankTransferResponse.builder()
                    .status(BankTransferStatus.FAILED)
                    .resultCode("ERROR").resultMessage(e.getMessage())
                    .processedAt(LocalDateTime.now()).build();
        }
    }

    @Override
    public BankTransferResponse queryStatus(String txId) {
        require(props.getAccessToken(), "app.adapter.kftc.access-token");
        try {
            JsonNode res = client.get()
                    .uri(uri -> uri.path("/v2.0/transfer/result")
                            .queryParam("bank_tran_id", txId)
                            .queryParam("inquiry_bank_tran_date",
                                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))
                            .build())
                    .header("Authorization", "Bearer " + props.getAccessToken())
                    .retrieve()
                    .body(JsonNode.class);
            return toResponse(res, BankTransferStatus.SETTLED);
        } catch (RestClientException e) {
            log.warn("[KFTC] 이체결과조회 실패 txId={} err={}", txId, e.getMessage());
            return BankTransferResponse.builder()
                    .txId(txId).status(BankTransferStatus.FAILED)
                    .resultCode("ERROR").resultMessage(e.getMessage())
                    .processedAt(LocalDateTime.now()).build();
        }
    }

    /**
     * KFTC 응답(rsp_code/rsp_message/api_tran_id) → BankTransferResponse.
     * rsp_code "A0000" 이 API 정상. 정상이면 호출 의미에 따른 status(요청=ACCEPTED/조회=SETTLED), 아니면 FAILED.
     */
    private BankTransferResponse toResponse(JsonNode res, BankTransferStatus okStatus) {
        String rspCode = res == null ? "" : res.path("rsp_code").asText("");
        String rspMsg = res == null ? "no response" : res.path("rsp_message").asText("");
        String apiTranId = res == null ? "" : res.path("api_tran_id").asText("");
        boolean ok = "A0000".equals(rspCode);
        return BankTransferResponse.builder()
                .txId(apiTranId)
                .status(ok ? okStatus : BankTransferStatus.FAILED)
                .resultCode(rspCode)
                .resultMessage(rspMsg)
                .processedAt(LocalDateTime.now())
                .build();
    }

    /** 은행거래고유번호: 이용기관코드(운영주입) 미보유 시 withdrawId 기반 고유키 생성(멱등 추적용). */
    private String buildBankTranId(Long withdrawRequestId) {
        String seq = String.format("%09d", withdrawRequestId == null ? 0 : withdrawRequestId % 1_000_000_000L);
        return "GA" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "U" + seq;
    }

    private String trim(String s, int max) {
        if (s == null) return "";
        return s.length() <= max ? s : s.substring(0, max);
    }

    private void require(String value, String key) {
        if (!StringUtils.hasText(value)) {
            throw new IllegalStateException("KFTC 설정 누락: " + key + " 가 필요합니다.");
        }
    }
}
