package com.zioinfo.kintex.visitor.paymethod;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.ticket.payment.PaymentGateway;
import com.zioinfo.kintex.visitor.paymethod.PayMethodDtos.PayMethodDto;
import com.zioinfo.kintex.visitor.paymethod.PayMethodDtos.QuickPayResultDto;
import com.zioinfo.kintex.visitor.paymethod.PayMethodDtos.RegisterRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.SecureRandom;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * F-B5 저장 결제수단 서비스 — mock PG 토큰화 등록 + 저장 수단 빠른결제.
 *
 * <p>보안(§0-3): 카드 원문(PAN)은 last4 산출·토큰화에만 잠시 사용하고 저장하지 않는다.
 * 실제 승인은 {@link PaymentGateway}(mock) 재사용.
 */
@Service
public class PayMethodService {

    private final PayMethodMapper mapper;
    private final PaymentGateway paymentGateway;
    private final SecureRandom random = new SecureRandom();

    public PayMethodService(PayMethodMapper mapper, PaymentGateway paymentGateway) {
        this.mapper = mapper;
        this.paymentGateway = paymentGateway;
    }

    @Transactional(readOnly = true)
    public List<PayMethodDto> list(String userId) {
        return mapper.listByUser(userId).stream().map(PayMethodService::toDto).toList();
    }

    /** 결제수단 등록 — last4 마스킹 + mock 토큰화(원문 미저장). */
    @Transactional
    public PayMethodDto register(String userId, RegisterRequest req) {
        String kind = normalizeKind(req.kind());
        String last4 = last4(req.cardNumber());
        String pgToken = "MOCKTOK-" + HexFormat.of().formatHex(randomBytes(6)).toUpperCase();
        boolean makeDefault = req.makeDefault() || mapper.listByUser(userId).isEmpty();

        if (makeDefault) {
            mapper.clearDefault(userId);
        }
        String id = "vpm-" + UUID.randomUUID();
        Map<String, Object> row = new java.util.HashMap<>();
        row.put("id", id);
        row.put("userId", userId);
        row.put("kind", kind);
        row.put("brand", blankToNull(req.brand()));
        row.put("last4", last4);
        row.put("pgToken", pgToken);
        row.put("isDefault", makeDefault);
        row.put("label", blankToNull(req.label()));
        mapper.insert(row);

        Map<String, Object> saved = mapper.findOwned(userId, id);
        return new PayMethodDto(id, kind, str(saved.get("brand")), last4, makeDefault, str(saved.get("label")), null);
    }

    @Transactional
    public void setDefault(String userId, String id) {
        if (mapper.findOwned(userId, id) == null) {
            throw new ApiException(ErrorCode.NOT_FOUND, "결제수단을 찾을 수 없습니다.");
        }
        mapper.clearDefault(userId);
        mapper.setDefault(userId, id);
    }

    @Transactional
    public void delete(String userId, String id) {
        if (mapper.delete(userId, id) != 1) {
            throw new ApiException(ErrorCode.NOT_FOUND, "결제수단을 찾을 수 없습니다.");
        }
    }

    /** 저장 수단 빠른결제 — mock PG 승인. */
    @Transactional
    public QuickPayResultDto quickPay(String userId, String methodId, long amount, String memo) {
        Map<String, Object> m = mapper.findOwned(userId, methodId);
        if (m == null) {
            throw new ApiException(ErrorCode.NOT_FOUND, "저장된 결제수단을 찾을 수 없습니다.");
        }
        if (amount < 0) {
            throw new ApiException(ErrorCode.VALIDATION, "결제 금액이 올바르지 않습니다.");
        }
        String orderNo = "QP-" + HexFormat.of().formatHex(randomBytes(5)).toUpperCase();
        PaymentGateway.PaymentResult pay = paymentGateway.authorize(str(m.get("kind")), amount, orderNo);
        if (!pay.approved()) {
            throw new ApiException(ErrorCode.CONFLICT, "결제 승인에 실패했습니다.");
        }
        return new QuickPayResultDto(true, pay.approvalNo(), amount,
                str(m.get("brand")), str(m.get("last4")), "간편결제 승인 완료(모의)");
    }

    // ── helpers ──
    private static PayMethodDto toDto(Map<String, Object> m) {
        return new PayMethodDto(str(m.get("id")), str(m.get("kind")), str(m.get("brand")),
                str(m.get("last4")), bool(m.get("isDefault")), str(m.get("label")), str(m.get("createdAt")));
    }

    private static String normalizeKind(String kind) {
        String k = kind == null ? "card" : kind.trim().toLowerCase(java.util.Locale.ROOT);
        return switch (k) {
            case "card", "easy", "bank" -> k;
            default -> "card";
        };
    }

    private static String last4(String cardNumber) {
        if (cardNumber == null) {
            return null;
        }
        String digits = cardNumber.replaceAll("[^0-9]", "");
        return digits.length() >= 4 ? digits.substring(digits.length() - 4) : null;
    }

    private byte[] randomBytes(int n) {
        byte[] b = new byte[n];
        random.nextBytes(b);
        return b;
    }

    private static String blankToNull(String s) {
        return s == null || s.isBlank() ? null : s.trim();
    }

    private static String str(Object o) {
        return o == null ? null : o.toString();
    }

    private static boolean bool(Object o) {
        return o instanceof Boolean b && b;
    }
}
