package com.kingdee.eas.custom.integration.base.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kingdee.eas.custom.integration.base.config.AppConfig;
import com.kingdee.eas.custom.integration.base.config.CommonConfig;
import com.kingdee.eas.custom.integration.base.config.OAuthConfig;
import com.kingdee.eas.custom.integration.base.config.OpenApiConfig;
import com.kingdee.eas.custom.integration.base.util.AppSecretDecryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * /**
 * OAuth 应用配置管理服务。
 *
 * <p>基于 Spring MVC 5.3 实现，不依赖 Spring Boot 自动配置。</p>
 *
 * <h3>核心功能</h3>
 * <ul>
 *   <li><b>配置加载</b>：通过 Jackson 解析 JSON 配置文件，支持外部路径和 classpath 两种来源</li>
 *   <li><b>热更新</b>：基于 JDK {@link WatchService} 监听外部配置文件变更，自动重载</li>
 *   <li><b>缓存查询</b>：使用 {@link ConcurrentHashMap} 提供按 {@code platform:appType} 或
 *       {@code appId} 的线程安全配置查询</li>
 *   <li><b>密钥解密</b>：通过 AES-256-GCM 在加载时一次性解密 appSecret，明文常驻内存</li>
 *   <li><b>重定向校验</b>：支持可配置的重定向 URL 白名单校验</li>
 *   <li><b>扩展配置</b>：暴露 {@code mobileBosUrl} 和 {@code shrServerName} 读取接口</li>
 * </ul>
 *
 * <h3>配置文件路径优先级</h3>
 * <ol>
 *   <li>环境变量 {@code OAUTH_CONFIG_PATH} 指定的外部路径（生产推荐）</li>
 *   <li>classpath 下的 {@code oauth-config-{env}.json}（通过 JVM 参数 {@code -Denv} 指定）</li>
 *   <li>classpath 下的 {@code oauth-config.json}（默认兜底）</li>
 * </ol>
 *
 * <h3>生命周期</h3>
 * <p>实现 {@link InitializingBean} 和 {@link DisposableBean}，由 Spring 容器管理初始化和资源释放。</p>
 *
 * @see AppConfig
 * @see AppSecretDecryptor
 */
@Service
public class IntegrationConfigService implements InitializingBean, DisposableBean {

    private static final Logger log = LoggerFactory.getLogger(IntegrationConfigService.class);

    /**
     * classpath 下默认配置文件名
     */
    private static final String CLASSPATH_CONFIG = "integration-config.json";

    /**
     * 外部配置文件路径，由环境变量 OAUTH_CONFIG_PATH 指定
     */
    private final String externalConfigPath =
            System.getenv("INTEGRATION_CONFIG_PATH");

    /**
     * AES-256-GCM 解密器，密钥来自环境变量 OAUTH_ENCRYPT_KEY
     */
    private final AppSecretDecryptor decryptor =
            new AppSecretDecryptor(System.getenv("OAUTH_ENCRYPT_KEY"));

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 应用配置缓存，key 格式为 {@code platform:appType}。
     * 使用 volatile 保证重载时的可见性。
     */
    private volatile Map<String, AppConfig> appConfigMap = new ConcurrentHashMap<>();

    private volatile OAuthConfig oauthConfig = new OAuthConfig();

    private volatile CommonConfig commonConfig = new CommonConfig();

    private volatile OpenApiConfig openApiConfig = new OpenApiConfig();

    private WatchService watchService;
    private ExecutorService watchExecutor;

    /**
     * Spring 容器初始化回调：加载配置并启动文件监听。
     *
     * @throws IOException 配置文件加载失败时抛出
     */
    @Override
    public void afterPropertiesSet() throws IOException {
        reload();
        startWatching();
    }

    /**
     * Spring 容器销毁回调：关闭文件监听线程池和 WatchService，释放资源。
     */
    @Override
    public void destroy() {
        if (watchExecutor != null) {
            watchExecutor.shutdownNow();
        }
        if (watchService != null) {
            try {
                watchService.close();
            } catch (IOException ignored) {
            }
        }
        log.info("IntegrationConfigService 已销毁");
    }

    /**
     * 根据 {@code platform} 和 {@code appType} 获取应用配置。
     *
     * @param platform 平台标识
     * @param appType  应用类型
     * @return 对应的 {@link AppConfig}，未找到时返回 {@code null} 并记录警告日志
     */
    public AppConfig get(String platform, String appType) {
        AppConfig config = appConfigMap.get(platform + ":" + appType);
        if (config == null) {
            log.warn("未找到应用配置, platform={}, appType={}", platform, appType);
        }
        return config;
    }

    /**
     * 根据 {@code platform} 和 {@code appType} 获取认证入口路径。
     * 供 {@code UnifiedAuthController} 转发使用。
     *
     * @param platform 平台标识
     * @param appType  应用类型
     * @return 认证入口路径，未找到配置时返回 {@code null}
     */
    public String getAuthEntry(String platform, String appType) {
        AppConfig config = get(platform, appType);
        return config != null ? config.getAuthEntry() : null;
    }


    public OAuthConfig getOauthConfig() {
        return oauthConfig;
    }

    /**
     * 根据 {@code appId} 查找应用配置。
     *
     * <p>遍历所有缓存配置进行匹配，适用于无法确定 platform 和 appType 的场景。</p>
     *
     * @param appId 应用 ID
     * @return 匹配的 {@link AppConfig}，未找到时返回 {@code null}
     */
    public AppConfig getByAppId(String appId) {
        if (appId == null || appId.trim().isEmpty()) return null;
        for (AppConfig config : appConfigMap.values()) {
            if (appId.equals(config.getAppId())) {
                return config;
            }
        }
        log.warn("appId not found: {}", appId);
        return null;
    }


    /**
     * 根据 {@code platform} 获取主应用配置。
     *
     * <p>遍历所有缓存配置进行匹配，适用于无法确定 appType 的场景。</p>
     *
     * @param platform 平台标识
     * @return 匹配的 {@link AppConfig}，未找到时返回 {@code null}
     */
    public AppConfig getPrimaryApp(String platform) {
        if (platform == null || platform.trim().isEmpty()) return null;
        platform = platform.toLowerCase();
        for (AppConfig config : appConfigMap.values()) {
            if (platform.equals(config.getPlatform()) && config.isPrimaryApp()) {
                return config;
            }
        }
        log.warn("platform not found: {}", platform);
        return null;
    }

    /**
     * 获取通用配置。
     *
     * @return {@link CommonConfig} 实例
     */
    public CommonConfig getCommonConfig() {
        return commonConfig;
    }

    public OpenApiConfig getOpenApiConfig() {
        return openApiConfig;
    }

    /**
     * 重新加载配置文件。
     *
     * <p>完整流程：加载 JSON → 解析重定向白名单 → 解析应用列表 → 解密 appSecret → 构建缓存。
     * 加载失败时保留原有配置不变，仅记录错误日志。
     * </p>
     */
    @SuppressWarnings("unchecked")
    private void reload() {
        try {
            Map<String, Object> root = loadConfig();

            Map<String, String> common = (Map<String, String>) root.get("common");
            if (common != null) {
                this.commonConfig = new CommonConfig(common);
                log.info("CommonConfig 加载完成: {}", this.commonConfig);
            }

            Map<String, Object> oauth = (Map<String, Object>) root.get("oauth");
            if (oauth == null) {
                throw new IllegalStateException("JSON 缺少 oauth 节点");
            }

            this.oauthConfig = new OAuthConfig(oauth);
            log.info("OAuthConfig 加载完成: {}", this.oauthConfig);

            List<Map<String, String>> apps = (List<Map<String, String>>) oauth.get("apps");
            if (apps == null || apps.isEmpty()) {
                throw new IllegalStateException("JSON 中 oauth.apps 为空");
            }

            Map<String, AppConfig> appMap = new ConcurrentHashMap<>();
            for (Map<String, String> item : apps) {
                String platform = item.get("platform");
                String appType = item.get("appType");

                AppConfig config = new AppConfig(decryptor, item);
                appMap.put(platform + ":" + appType, config);
                log.info("已加载应用配置: {}", config);
            }

            this.appConfigMap = appMap;
            log.info("integration 配置加载完成，共 {} 个应用", appMap.size());

            Map<String, Object> openApi = (Map<String, Object>) root.get("openApi");
            if (openApi != null) {
                this.openApiConfig = new OpenApiConfig(openApi);
                log.info("openApi 配置加载完成: {}", this.openApiConfig);
            }
        } catch (Exception e) {
            log.error("integration 配置加载失败，继续使用旧配置: {}", e.getMessage(), e);
        }
    }

    /**
     * 加载 JSON 配置文件。
     *
     * <p>优先从环境变量 {@code OAUTH_CONFIG_PATH} 指定的外部路径加载，
     * 若未设置则从 classpath 加载默认配置文件。
     * </p>
     *
     * @return 解析后的 JSON 数据，以 Map 形式返回
     * @throws IOException 外部文件不存在或 classpath 资源未找到时抛出
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> loadConfig() throws IOException {
        if (externalConfigPath != null && !externalConfigPath.trim().isEmpty()) {
            Path path = Paths.get(externalConfigPath);
            if (!Files.exists(path)) {
                throw new IOException("外部配置文件不存在: " + externalConfigPath);
            }
            try (InputStream is = Files.newInputStream(path)) {
                log.debug("从外部文件加载配置: {}", externalConfigPath);
                return objectMapper.readValue(is, Map.class);
            }
        }

        try (InputStream is = Thread.currentThread()
                .getContextClassLoader()
                .getResourceAsStream(CLASSPATH_CONFIG)) {
            if (is == null) {
                throw new IOException("classpath:" + CLASSPATH_CONFIG + " 未找到");
            }
            log.debug("从 classpath 加载配置");
            return objectMapper.readValue(is, Map.class);
        }
    }

    /**
     * 启动配置文件监听线程。
     *
     * <p>仅当外部配置文件路径有效时启用。使用 JDK {@link WatchService} 监听文件所在目录的
     * {@code ENTRY_MODIFY} 事件，检测到目标文件变更后等待 200ms 防抖，再调用 {@link #reload()}
     * 完成热更新。监听线程为守护线程，线程名固定为 {@code oauth-config-watcher}。
     * </p>
     *
     * @throws IOException 注册 WatchService 失败时抛出
     */
    private void startWatching() throws IOException {
        if (externalConfigPath == null || externalConfigPath.trim().isEmpty()) {
            return;
        }

        Path configFile = Paths.get(externalConfigPath);
        Path dir = configFile.getParent();
        String fileName = configFile.getFileName().toString();

        watchService = FileSystems.getDefault().newWatchService();
        dir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

        watchExecutor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "integration-config-watcher");
            t.setDaemon(true);
            return t;
        });

        watchExecutor.submit(() -> {
            log.info("开始监听配置文件: {}", configFile);
            while (!Thread.currentThread().isInterrupted()) {
                WatchKey key;
                try {
                    key = watchService.take();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } catch (ClosedWatchServiceException e) {
                    break;
                }

                for (WatchEvent<?> event : key.pollEvents()) {
                    Path changed = (Path) event.context();
                    if (fileName.equals(changed.toString())) {
                        log.info("检测到配置文件变更，开始热更新: {}", configFile);
                        try {
                            // 等待 200ms 防抖，避免文件写入未完成即触发重载
                            TimeUnit.MILLISECONDS.sleep(200);
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                        reload();
                    }
                }

                if (!key.reset()) {
                    log.warn("WatchKey 已失效，停止监听");
                    break;
                }
            }
            log.info("配置文件监听已停止");
        });
    }
}
