package shop.alien.store.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import shop.alien.entity.store.StoreInfo;
import shop.alien.entity.store.StorePaymentConfig;
import shop.alien.store.service.StoreInfoService;
import shop.alien.store.service.StorePaymentConfigCertService;
import shop.alien.store.service.StorePaymentConfigService;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 店铺支付证书落盘服务实现：删除店铺证书目录下原有文件后重新写入，并将完整证书路径回写配置表。
 *
 * @author system
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class StorePaymentConfigCertServiceImpl implements StorePaymentConfigCertService {

    private static final Pattern NON_ALNUM = Pattern.compile("[^0-9a-zA-Z]");

    @Value("${payment.cert.base-path:${user.dir}/payment-certs}")
    private String certBasePath;

    private final StorePaymentConfigService storePaymentConfigService;
    private final StoreInfoService storeInfoService;

    @Override
    public List<String> refreshCertByStoreId(Integer storeId) {
        if (storeId == null) {
            return new ArrayList<>();
        }
        StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
        if (config == null) {
            log.warn("店铺支付配置不存在 storeId={}", storeId);
            return new ArrayList<>();
        }
        try {
            return writeCertFilesForConfigWithClean(config);
        } catch (Exception e) {
            log.error("店铺证书落盘失败 storeId={}", storeId, e);
            throw new RuntimeException("证书落盘失败：" + e.getMessage());
        }
    }

    /**
     * 先清空该店铺证书目录（删除目录下所有文件及目录本身再重建），再写入证书并回写路径。
     */
    private List<String> writeCertFilesForConfigWithClean(StorePaymentConfig config) throws Exception {
        Integer storeId = config.getStoreId();
        if (storeId == null) {
            return new ArrayList<>();
        }
        String storeTel = null;
        try {
            StoreInfo store = storeInfoService.getById(storeId);
            if (store != null && StringUtils.hasText(store.getStoreTel())) {
                storeTel = store.getStoreTel();
            }
        } catch (Exception e) {
            log.debug("获取店铺手机号失败 storeId={}", storeId, e);
        }
        String dirSegment = sanitizeDirSegment(storeTel);
        String storeDirName = storeId + "_" + dirSegment;
        Path baseDir = Paths.get(certBasePath).resolve(storeDirName);

        // 删除店铺证书目录下原有内容：若目录存在则删除后重建
        if (Files.exists(baseDir)) {
            deleteDirectoryRecursively(baseDir);
        }
        Files.createDirectories(baseDir);

        List<String> writtenPaths = new ArrayList<>();

        // 支付宝：应用公钥、支付宝公钥、根证书
        writeIfPresent(baseDir, config.getAppPublicCert(),
                defaultName(config.getAppPublicCertName(), "app_public_cert.crt"),
                (path, name) -> {
                    config.setAppPublicCertPath(path);
                    config.setAppPublicCertName(name);
                }, writtenPaths);
        writeIfPresent(baseDir, config.getAlipayPublicCert(),
                defaultName(config.getAlipayPublicCertName(), "alipay_public_cert.crt"),
                (path, name) -> {
                    config.setAlipayPublicCertPath(path);
                    config.setAlipayPublicCertName(name);
                }, writtenPaths);
        writeIfPresent(baseDir, config.getAlipayRootCert(),
                defaultName(config.getAlipayRootCertName(), "alipay_root_cert.crt"),
                (path, name) -> {
                    config.setAlipayRootCertPath(path);
                    config.setAlipayRootCertName(name);
                }, writtenPaths);

        // 微信：商户私钥、微信公钥
        writeIfPresent(baseDir, config.getWechatPrivateKeyFile(),
                defaultName(config.getWechatPrivateKeyName(), "wechat_private_key.pem"),
                (path, name) -> {
                    config.setWechatPrivateKeyPath(path);
                    config.setWechatPrivateKeyName(name);
                }, writtenPaths);
        writeIfPresent(baseDir, config.getWechatPayPublicKeyFile(),
                defaultName(config.getWechatPayPublicKeyFileName(), "wechat_public_key.pem"),
                (path, name) -> {
                    config.setWechatPayPublicKeyFilePath(path);
                    config.setWechatPayPublicKeyFileName(name);
                }, writtenPaths);

        storePaymentConfigService.updateById(config);
        log.info("店铺支付证书已刷新落盘 storeId={}, dir={}, 写入文件数={}", storeId, baseDir.toAbsolutePath(), writtenPaths.size());
        return writtenPaths;
    }

    private static void deleteDirectoryRecursively(Path path) throws Exception {
        if (!Files.exists(path)) {
            return;
        }
        if (Files.isDirectory(path)) {
            try (Stream<Path> stream = Files.list(path)) {
                for (Path entry : stream.collect(Collectors.toList())) {
                    deleteDirectoryRecursively(entry);
                }
            }
        }
        Files.delete(path);
    }

    private static String sanitizeDirSegment(String storeTel) {
        if (!StringUtils.hasText(storeTel)) {
            return "no_tel";
        }
        return NON_ALNUM.matcher(storeTel.trim()).replaceAll("_");
    }

    private static String defaultName(String name, String defaultName) {
        return StringUtils.hasText(name) ? name.trim() : defaultName;
    }

    private interface PathUpdater {
        void set(String certFilePath, String fileName);
    }

    private void writeIfPresent(Path baseDir, byte[] content, String fileName, PathUpdater updater, List<String> writtenPaths) throws Exception {
        if (content == null || content.length == 0) {
            return;
        }
        Path file = baseDir.resolve(fileName);
        Files.write(file, content);
        String certFilePath = file.toAbsolutePath().toString();
        writtenPaths.add(certFilePath);
        if (updater != null) {
            updater.set(certFilePath, fileName);
        }
    }
}
