package com.zioinfo.kintex.system.message;

import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 메시지 템플릿 조회/관리 + 렌더링.
 * <p>렌더: template 의 {key} 를 params[key] 로 치환한다. 파라미터가 없으면 원본 {key} 를 유지하고(안전),
 * 값이 null 이면 빈 문자열로 치환한다. 알림/이메일(W10)·통지에서 {@link #render}/{@link #renderTemplate} 재사용.
 */
@Service("sysMessageService")
public class MessageService {

    /** {key} — 영문/숫자/밑줄/점만 허용(중괄호 오탐 방지). */
    private static final Pattern PLACEHOLDER = Pattern.compile("\\{([a-zA-Z0-9_.]+)}");
    private static final String DEFAULT_TENANT = "KINTEX";
    private static final String DEFAULT_LOCALE = "ko";

    private final SysMessageMapper mapper;

    public MessageService(SysMessageMapper mapper) {
        this.mapper = mapper;
    }

    public List<MessageDto> list(String tenantId, String category, String locale) {
        return mapper.findAll(tenant(tenantId), blankToNull(category), blankToNull(locale))
                .stream().map(MessageService::toDto).toList();
    }

    public List<MessageDto> byCode(String tenantId, String msgCode) {
        return mapper.findByCode(tenant(tenantId), msgCode).stream().map(MessageService::toDto).toList();
    }

    public void save(String tenantId, MessageDto dto) {
        Map<String, Object> p = new HashMap<>();
        p.put("tenantId", tenant(tenantId));
        p.put("msgCode", dto.msgCode());
        p.put("locale", blankToNull(dto.locale()));
        p.put("category", dto.category());
        p.put("title", dto.title());
        p.put("template", dto.template());
        p.put("useYn", dto.useYn());
        mapper.upsert(p);
    }

    /** locale 이 null 이면 해당 코드의 전 로케일 삭제. */
    public void delete(String tenantId, String msgCode, String locale) {
        mapper.delete(tenant(tenantId), msgCode, blankToNull(locale));
    }

    /**
     * 코드+로케일 템플릿을 조회해 파라미터로 치환한 최종 문자열을 반환.
     * 요청 로케일이 없으면 ko 로 폴백, 그래도 없으면 빈 문자열.
     */
    public String render(String tenantId, String msgCode, String locale, Map<String, Object> params) {
        String loc = (locale == null || locale.isBlank()) ? DEFAULT_LOCALE : locale;
        Map<String, Object> row = mapper.findOne(tenant(tenantId), msgCode, loc);
        if (row == null && !DEFAULT_LOCALE.equals(loc)) {
            row = mapper.findOne(tenant(tenantId), msgCode, DEFAULT_LOCALE);
        }
        if (row == null) {
            return "";
        }
        return renderTemplate(String.valueOf(row.get("template")), params);
    }

    /** template 의 {key} 를 params 로 치환(파라미터 없으면 원본 유지, null 이면 빈 문자열). */
    public String renderTemplate(String template, Map<String, Object> params) {
        if (template == null || template.isEmpty()) {
            return template == null ? "" : template;
        }
        Map<String, Object> safe = params == null ? Map.of() : params;
        Matcher m = PLACEHOLDER.matcher(template);
        StringBuilder sb = new StringBuilder();
        while (m.find()) {
            String key = m.group(1);
            String replacement;
            if (safe.containsKey(key)) {
                Object v = safe.get(key);
                replacement = v == null ? "" : String.valueOf(v);
            } else {
                replacement = m.group(0); // 원본 {key} 유지
            }
            m.appendReplacement(sb, Matcher.quoteReplacement(replacement));
        }
        m.appendTail(sb);
        return sb.toString();
    }

    private static String tenant(String tenantId) {
        return (tenantId == null || tenantId.isBlank()) ? DEFAULT_TENANT : tenantId;
    }

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

    private static MessageDto toDto(Map<String, Object> r) {
        return new MessageDto(
                str(r.get("msgCode")), str(r.get("locale")), str(r.get("category")),
                str(r.get("title")), str(r.get("template")), str(r.get("useYn")), str(r.get("updatedAt")));
    }

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