package cn.qaiu.lz.web.controller;


import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.lz.common.cache.CacheManager;
import cn.qaiu.lz.common.util.AuthParamCodec;
import cn.qaiu.lz.common.util.ParserAuthUtil;
import cn.qaiu.lz.common.util.URLParamUtil;
import cn.qaiu.lz.web.model.AuthParam;
import cn.qaiu.lz.web.model.CacheLinkInfo;
import cn.qaiu.lz.web.model.ClientLinkResp;
import cn.qaiu.lz.web.model.LinkInfoResp;
import cn.qaiu.lz.web.model.StatisticsInfo;
import cn.qaiu.lz.web.service.DbService;
import cn.qaiu.parser.PanDomainTemplate;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.ParserCreate;
import cn.qaiu.parser.clientlink.ClientLinkType;
import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.enums.RouteMethod;
import cn.qaiu.vx.core.model.JsonResult;
import cn.qaiu.vx.core.util.AsyncServiceUtil;
import cn.qaiu.vx.core.util.CommonUtil;
import cn.qaiu.vx.core.util.ResponseUtil;
import cn.qaiu.vx.core.util.SharedDataUtil;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@RouteHandler(value = "/v2", order = 10)
@Slf4j
public class ParserApi {

    private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);

    /**
     * 获取链接前缀：优先用配置的 domainName，未配置则从请求头推断
     * 支持反向代理：优先读 X-Forwarded-Host/X-Forwarded-Proto，再回退到 Host 头
     */
    private static String getLinkPrefix(HttpServerRequest request) {
        String domainName = SharedDataUtil.getJsonConfig("server").getString("domainName");
        if (StringUtils.isNotBlank(domainName)) {
            return domainName;
        }
        if (request != null) {
            // 反向代理场景：优先从转发头获取原始域名
            String forwardedHost = request.getHeader("X-Forwarded-Host");
            if (StringUtils.isNotBlank(forwardedHost)) {
                String proto = request.getHeader("X-Forwarded-Proto");
                if (StringUtils.isBlank(proto)) {
                    proto = request.scheme();
                }
                return proto + "://" + forwardedHost;
            }
            return request.scheme() + "://" + request.host();
        }
        return "";
    }


    @RouteMapping(value = "/statisticsInfo", method = RouteMethod.GET, order = 99)
    public Future<StatisticsInfo> statisticsInfo() {
        return dbService.getStatisticsInfo();
    }

    private static final CacheManager cacheManager = new CacheManager();
    private static final ServerApi serverApi = new ServerApi();

    @RouteMapping(value = "/check/:type/:key", method = RouteMethod.GET)
    public void check(HttpServerResponse response, String type, String key) {
        response.putHeader("Content-Type", "text/plain; charset=utf-8")
                .setStatusCode(200)
                .end("ok");
    }

    @RouteMapping(value = "/check/:type/:key", method = RouteMethod.HEAD)
    public void checkHead(HttpServerResponse response, String type, String key) {
        response.putHeader("Content-Type", "text/plain; charset=utf-8")
                .setStatusCode(200)
                .end();
    }

    @RouteMapping(value = "/linkInfo", method = RouteMethod.GET)
    public Future<LinkInfoResp> parse(HttpServerRequest request, String pwd, String auth) {
        Promise<LinkInfoResp> promise = Promise.promise();
        String url = URLParamUtil.parserParams(request);
        ParserCreate parserCreate;
        try {
            parserCreate = ParserCreate.fromShareUrl(url).setShareLinkInfoPwd(pwd);
        } catch (Exception e) {
            return Future.failedFuture(e);
        }
        ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
        
        // 构建链接信息响应，如果有 auth 参数则附加到链接中
        String authSuffix = (auth != null && !auth.isEmpty()) ? "&auth=" + auth : "";
        shareLinkInfo.getOtherParam().put("_requestOrigin", getLinkPrefix(request));
        LinkInfoResp build = LinkInfoResp.builder()
                .downLink(getDownLink(parserCreate, false, request) + authSuffix)
                .apiLink(getDownLink(parserCreate, true, request) + authSuffix)
                .viewLink(getViewLink(parserCreate, request) + authSuffix)
                .shareLinkInfo(shareLinkInfo).build();
        // 解析次数统计
        shareLinkInfo.getOtherParam().put("UA",request.headers().get("user-agent"));
        cacheManager.getShareKeyTotal(shareLinkInfo.getCacheKey()).onSuccess(res -> {
            if (res != null) {
                build.setCacheHitTotal(res.get("hit_total") == null ? 0: res.get("hit_total"));
                build.setParserTotal(res.get("parser_total") == null ? 0: res.get("parser_total"));
                build.setSumTotal(build.getCacheHitTotal() + build.getParserTotal());
            }
            promise.complete(build);
        }).onFailure(t->{
            log.error("获取统计信息失败", t);
            promise.complete(build);
        });
        return promise.future();
    }

    private static String getDownLink(ParserCreate create, boolean isJson, HttpServerRequest request) {
        String linkPrefix = getLinkPrefix(request);
        if (StringUtils.isBlank(linkPrefix)) {
            linkPrefix = "http://127.0.0.1:" + SharedDataUtil.getJsonConfig("server").getInteger("port", 6400);
        }
        // 下载短链前缀 /d
        return linkPrefix + (isJson ? "/json/" : "/d/") + create.genPathSuffix();
    }

    private static String getViewLink(ParserCreate create, HttpServerRequest request) {
        String linkPrefix = getLinkPrefix(request);
        if (StringUtils.isBlank(linkPrefix)) {
            return "";
        }
        return linkPrefix + "/v2/view/" + create.genPathSuffix();
    }

    /**
     * 获取支持的网盘列表
     * @return list-map: name: 网盘名, type: 网盘标识, url: 网盘域名地址
     */
    @RouteMapping("/getPanList")
    public List<Map<String, String>> getPanList() {
        return Arrays.stream(PanDomainTemplate.values()).map(pan -> new TreeMap<String, String>() {{
            put("name", pan.getDisplayName());
            put("type", pan.name().toLowerCase());
            put("shareUrlFormat", pan.getStandardUrlTemplate());
            put("url", pan.getPanDomain());
        }}).collect(Collectors.toList());
    }

    @RouteMapping("/getFileList")
    public Future<List<FileInfo>> getFileList(HttpServerRequest request, String pwd, String dirId, String uuid,
                                             String auth) {
        String url = URLParamUtil.parserParams(request);
        ParserCreate parserCreate;
        try {
            parserCreate = ParserCreate.fromShareUrl(url).setShareLinkInfoPwd(pwd);
        } catch (Exception e) {
            return Future.failedFuture(e);
        }
        String linkPrefix = getLinkPrefix(request);
        JsonObject otherParam = ParserAuthUtil.buildOtherParam(request, auth, linkPrefix);
        parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
        parserCreate.getShareLinkInfo().getOtherParam().put("_requestOrigin", linkPrefix);
        if (StringUtils.isNotBlank(dirId)) {
            parserCreate.getShareLinkInfo().getOtherParam().put("dirId", dirId);
        }
        if (StringUtils.isNotBlank(uuid)) {
            parserCreate.getShareLinkInfo().getOtherParam().put("uuid", uuid);
        }
        return ParserAuthUtil.applyAuthParamsAndDonatedFallback(parserCreate, otherParam, dbService)
                .compose(v -> {
                    URLParamUtil.addParam(parserCreate);
                    IPanTool tool = parserCreate.createTool();
                    return IPanTool.closeAfter(tool, tool::parseFileList)
                            .onFailure(t -> {
                                ParserAuthUtil.recordDonatedAccountFailureIfNeeded(dbService, otherParam, t);
                                ParserAuthUtil.recordAutoDonatedFailureIfNeeded(dbService,
                                        parserCreate.getShareLinkInfo(), t);
                            });
                });
    }

    // 目录解析下载文件
    // @RouteMapping("/getFileDownUrl/:type/:param")
    public Future<String> getFileDownUrl(HttpServerRequest request, String type, String param, String auth) {
        ParserCreate parserCreate = ParserCreate.fromType(type).shareKey("-") // shareKey not null
                .setShareLinkInfoPwd("-");

        if (param.isEmpty()) {
            Promise<String> promise = Promise.promise();
            promise.fail("下载参数为空");
            return promise.future();
        }

        String paramStr = new String(Base64.getDecoder().decode(param));
        ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
        shareLinkInfo.getOtherParam().put("paramJson", new JsonObject(paramStr));

        // domainName
        String linkPrefix = getLinkPrefix(request);
        JsonObject otherParam = ParserAuthUtil.buildOtherParam(request, auth, linkPrefix, true);
        shareLinkInfo.getOtherParam().put("domainName", linkPrefix);
        shareLinkInfo.getOtherParam().put("_requestOrigin", linkPrefix);
        return ParserAuthUtil.applyAuthParamsAndDonatedFallback(parserCreate, otherParam, dbService)
                .compose(v -> {
                    URLParamUtil.addParam(parserCreate);
                    IPanTool tool = parserCreate.createTool();
                    return IPanTool.closeAfter(tool, tool::parseById)
                            .onFailure(t -> {
                                ParserAuthUtil.recordDonatedAccountFailureIfNeeded(dbService, otherParam, t);
                                ParserAuthUtil.recordAutoDonatedFailureIfNeeded(dbService,
                                        parserCreate.getShareLinkInfo(), t);
                            });
                });
    }

    @RouteMapping("/redirectUrl/:type/:param")
    public Future<Void> redirectUrl(HttpServerRequest request, HttpServerResponse response, String type, String param,
                                    String auth) {
        Promise<Void> promise = Promise.promise();

        getFileDownUrl(request, type, param, auth)
                .onSuccess(res -> {
                    ResponseUtil.redirect(response, res, promise);
                })
                .onFailure(promise::tryFail);
        return promise.future();
    }


    /**
     * 预览媒体文件
     */
    @RouteMapping(value = "/view/:type/:key", method = RouteMethod.GET, order = 2)
    public void view(HttpServerRequest request, HttpServerResponse response, String type, String key) {
        // WPS 网盘类型特殊处理：直接使用原分享链接（WPS 支持在线预览）
        if ("pwps".equalsIgnoreCase(type)) {
            try {
                // 重建原分享链接
                ParserCreate parserCreate = ParserCreate.fromType(type).shareKey(key);
                String originalUrl = parserCreate.getShareLinkInfo().getStandardUrl();
                if (StringUtils.isNotBlank(originalUrl)) {
                    ResponseUtil.redirect(response, originalUrl);
                    return;
                }
            } catch (Exception e) {
                log.warn("PWPS 预览链接构建失败: {}", e.getMessage());
            }
        }
        
        String previewURL = SharedDataUtil.getJsonStringForServerConfig("previewURL");
        serverApi.parseKeyJsonForRedirect(request, type, key).onSuccess(res -> {
            redirect(response, previewURL, res);
        }).onFailure(e -> {
            ResponseUtil.fireJsonResultResponse(response, JsonResult.error(e.toString()));
        });
    }

    private static void redirect(HttpServerResponse response, String previewURL, CacheLinkInfo res) {
        String directLink = res.getDirectLink();
        ResponseUtil.redirect(response, previewURL + URLEncoder.encode(directLink, StandardCharsets.UTF_8));
    }

    /**
     * 预览媒体文件-目录预览
     */
    @RouteMapping(value = "/preview", method = RouteMethod.GET, order = 9)
    public void viewURL(HttpServerRequest request, HttpServerResponse response, String pwd) {
        // WPS 网盘类型特殊处理：直接使用原分享链接（WPS 支持在线预览）
        try {
            String url = URLParamUtil.parserParams(request);
            ParserCreate parserCreate = ParserCreate.fromShareUrl(url);
            ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
            
            // 如果是 PWPS 类型，直接重定向到原分享链接
            if ("pwps".equalsIgnoreCase(shareLinkInfo.getType())) {
                String originalUrl = shareLinkInfo.getStandardUrl();
                if (StringUtils.isNotBlank(originalUrl)) {
                    ResponseUtil.redirect(response, originalUrl);
                    return;
                }
            }
        } catch (Exception e) {
            log.warn("解析预览链接失败: {}", e.getMessage());
        }
        
        String previewURL = SharedDataUtil.getJsonStringForServerConfig("previewURL");
        serverApi.parseJsonForRedirect(request, pwd, null).onSuccess(res -> {
            redirect(response, previewURL, res);
        }).onFailure(e -> {
            ResponseUtil.fireJsonResultResponse(response, JsonResult.error(e.toString()));
        });
    }


    @RouteMapping("/viewUrl/:type/:param")
    public Future<Void> viewUrl(HttpServerRequest request, HttpServerResponse response, String type, String param,
                                String auth) {
        Promise<Void> promise = Promise.promise();

        String viewPrefix = SharedDataUtil.getJsonConfig("server").getString("previewURL");
        getFileDownUrl(request, type, param, auth)
                .onSuccess(res -> {
                    String url = viewPrefix + URLEncoder.encode(res, StandardCharsets.UTF_8);
                    ResponseUtil.redirect(response, url, promise);
                })
                .onFailure(promise::tryFail);
        return promise.future();
    }

    // 获取版本号
    @RouteMapping("/build-version")
    public String getVersion() {
        return CommonUtil.getAppVersion()
        .replace("-", "")
        .replace("Z", "")
        .replace("T", "_")
        .replace("-", "")
        .replace(":", "");
    }

    /**
     * 获取客户端下载链接
     * 
     * @param request HTTP请求
     * @param pwd 提取码
     * @param auth 加密的认证参数
     * @return 客户端下载链接响应
     */
    @RouteMapping(value = "/clientLinks", method = RouteMethod.GET)
    public Future<ClientLinkResp> getClientLinks(HttpServerRequest request, String pwd, String auth) {
        Promise<ClientLinkResp> promise = Promise.promise();
        
        try {
            String shareUrl = URLParamUtil.parserParams(request);
            ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
            ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
            shareLinkInfo.getOtherParam().put("_requestOrigin", getLinkPrefix(request));

            // 处理认证参数
            if (auth != null && !auth.isEmpty()) {
                AuthParam authParam = AuthParamCodec.decode(auth);
                if (authParam != null && authParam.hasValidAuth()) {
                    URLParamUtil.addTempAuthParam(parserCreate,
                        authParam.getAuthType(),
                        authParam.getPrimaryCredential(),
                        authParam.getPassword(),
                        authParam.getExt1(),
                        authParam.getExt2(),
                        authParam.getExt3(),
                        authParam.getExt4(),
                        authParam.getExt5());
                    log.debug("客户端链接API: 已解码认证参数 authType={}", authParam.getAuthType());
                }
            } else {
                URLParamUtil.addParam(parserCreate);
            }
            
            // 使用默认方法解析并生成客户端链接
            IPanTool tool = parserCreate.createTool();
            IPanTool.closeAfter(tool, tool::parseWithClientLinks)
                .onSuccess(clientLinks -> {
                    try {
                        ClientLinkResp response = buildClientLinkResponse(shareLinkInfo, clientLinks);
                        promise.complete(response);
                    } catch (Exception e) {
                        log.error("处理客户端链接结果失败", e);
                        promise.fail(new RuntimeException("处理客户端链接结果失败: " + e.getMessage()));
                    }
                })
                .onFailure(error -> {
                    log.error("解析分享链接失败", error);
                    promise.fail(new RuntimeException("解析分享链接失败: " + error.getMessage()));
                });
                
        } catch (Exception e) {
            log.error("解析请求参数失败", e);
            promise.fail(new RuntimeException("解析请求参数失败: " + e.getMessage()));
        }
        
        return promise.future();
    }

    /**
     * 获取指定类型的客户端下载链接
     * 
     * @param request HTTP请求
     * @param pwd 提取码
     * @param clientType 客户端类型 (curl, wget, aria2, idm, thunder, bitcomet, motrix, fdm, powershell)
     * @return 指定类型的客户端下载链接
     */
    @RouteMapping(value = "/clientLink", method = RouteMethod.GET)
    public Future<String> getClientLink(HttpServerRequest request, String pwd, String clientType) {
        Promise<String> promise = Promise.promise();
        
        try {
            String shareUrl = URLParamUtil.parserParams(request);
            ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
            parserCreate.getShareLinkInfo().getOtherParam().put("_requestOrigin", getLinkPrefix(request));
            URLParamUtil.addParam(parserCreate);

            // 使用默认方法解析并生成客户端链接
            IPanTool tool = parserCreate.createTool();
            IPanTool.closeAfter(tool, tool::parseWithClientLinks)
                .onSuccess(clientLinks -> {
                    try {
                        String clientLink = extractClientLinkByType(clientLinks, clientType);
                        if (clientLink != null) {
                            promise.complete(clientLink);
                        } else {
                            promise.fail("无法生成 " + clientType + " 格式的下载链接");
                        }
                    } catch (IllegalArgumentException e) {
                        promise.fail("不支持的客户端类型: " + clientType);
                    } catch (Exception e) {
                        log.error("获取客户端链接失败", e);
                        promise.fail("获取客户端链接失败: " + e.getMessage());
                    }
                })
                .onFailure(error -> {
                    log.error("解析分享链接失败", error);
                    promise.fail("解析分享链接失败: " + error.getMessage());
                });
                
        } catch (Exception e) {
            log.error("解析请求参数失败", e);
            promise.fail("解析请求参数失败: " + e.getMessage());
        }
        
        return promise.future();
    }
    
    /**
     * 构建客户端链接响应
     * 
     * @param shareLinkInfo 分享链接信息
     * @param clientLinks 客户端链接映射
     * @return 客户端链接响应
     */
    private ClientLinkResp buildClientLinkResponse(ShareLinkInfo shareLinkInfo, Map<ClientLinkType, String> clientLinks) {
        // 从 otherParam 中获取直链
        String directLink = (String) shareLinkInfo.getOtherParam().get("downloadUrl");
        Map<String, String> supportedClients = buildSupportedClientsMap();
        FileInfo fileInfo = extractFileInfo(shareLinkInfo);
        String panType = shareLinkInfo.getType().toUpperCase();
        
        // 判断是否需要客户端下载和认证需求
        PanRequirementInfo requirementInfo = getPanRequirementInfo(panType);
        
        return ClientLinkResp.builder()
            .success(true)
            .directLink(directLink)
            .fileName(fileInfo != null ? fileInfo.getFileName() : null)
            .fileSize(fileInfo != null ? fileInfo.getSize() : null)
            .clientLinks(clientLinks)
            .supportedClients(supportedClients)
            .parserInfo(shareLinkInfo.getPanName() + " - " + shareLinkInfo.getType())
            .panType(panType)
            .requiresClient(requirementInfo.requiresClient)
            .authRequirement(requirementInfo.authRequirement)
            .authHint(requirementInfo.authHint)
            .build();
    }
    
    /**
     * 网盘需求信息内部类
     */
    private static class PanRequirementInfo {
        boolean requiresClient;
        String authRequirement;
        String authHint;
        
        PanRequirementInfo(boolean requiresClient, String authRequirement, String authHint) {
            this.requiresClient = requiresClient;
            this.authRequirement = authRequirement;
            this.authHint = authHint;
        }
    }
    
    /**
     * 获取网盘需求信息
     * 
     * @param panType 网盘类型代码（大写）
     * @return 网盘需求信息
     */
    private PanRequirementInfo getPanRequirementInfo(String panType) {
        // 需要使用客户端下载的网盘类型（直链需要特殊头部，浏览器无法直接下载）
        boolean requiresClient = switch (panType) {
            case "UC", "QK", "PCX", "COW" -> true;
            default -> false;
        };
        
        // 认证需求判断
        String authRequirement;
        String authHint;
        switch (panType) {
            case "UC", "QK":
                authRequirement = "required";
                authHint = "此网盘必须配置认证信息（Cookie/Token）才能正常解析和下载";
                break;
            case "FJ":
                authRequirement = "optional";
                authHint = "小飞机网盘大文件（>100MB）需要配置认证信息";
                break;
            case "IZ":
                authRequirement = "optional";
                authHint = "蓝奏优享大文件需要配置认证信息";
                break;
            default:
                authRequirement = "none";
                authHint = null;
                break;
        }
        
        return new PanRequirementInfo(requiresClient, authRequirement, authHint);
    }
    
    /**
     * 构建支持的客户端类型映射
     * 
     * @return 客户端类型映射
     */
    private Map<String, String> buildSupportedClientsMap() {
        Map<String, String> supportedClients = new HashMap<>();
        for (ClientLinkType type : ClientLinkType.values()) {
            supportedClients.put(type.getCode(), type.getDisplayName());
        }
        return supportedClients;
    }
    
    /**
     * 从ShareLinkInfo中提取文件信息
     * 
     * @param shareLinkInfo 分享链接信息
     * @return 文件信息，如果不存在则返回null
     */
    private FileInfo extractFileInfo(ShareLinkInfo shareLinkInfo) {
        Object fileInfo = shareLinkInfo.getOtherParam().get("fileInfo");
        return fileInfo instanceof FileInfo ? (FileInfo) fileInfo : null;
    }
    
    /**
     * 根据客户端类型提取对应的客户端链接
     * 
     * @param clientLinks 客户端链接映射
     * @param clientType 客户端类型
     * @return 客户端链接，如果不存在则返回null
     * @throws IllegalArgumentException 如果客户端类型不支持
     */
    private String extractClientLinkByType(Map<ClientLinkType, String> clientLinks, String clientType) {
        ClientLinkType type = ClientLinkType.valueOf(clientType.toUpperCase());
        return clientLinks.get(type);
    }

    // ========== 捐赠账号 API ==========

    /**
     * 捐赠网盘账号
     */
    @RouteMapping(value = "/donateAccount", method = RouteMethod.POST)
    public Future<JsonObject> donateAccount(RoutingContext ctx) {
        JsonObject body = ctx.body().asJsonObject();
        if (body == null || StringUtils.isBlank(body.getString("panType"))
                || StringUtils.isBlank(body.getString("authType"))) {
            return Future.succeededFuture(JsonResult.error("panType and authType are required").toJsonObject());
        }
        String ip = ctx.request().remoteAddress().host();
        body.put("ip", ip);
        return dbService.saveDonatedAccount(body);
    }

    /**
     * 获取各网盘捐赠账号数量
     */
    @RouteMapping(value = "/donateAccountCounts", method = RouteMethod.GET)
    public Future<JsonObject> getDonateAccountCounts() {
        return dbService.getDonatedAccountCounts();
    }

    /**
     * 随机获取指定网盘类型的捐赠账号（内部使用，返回加密后的 auth 参数）
     */
    @RouteMapping(value = "/randomAuth", method = RouteMethod.GET)
    public Future<JsonObject> getRandomAuth(String panType) {
        return dbService.getRandomDonatedAccount(panType).map(res -> {
            if (Integer.valueOf(200).equals(res.getInteger("code")) && res.getJsonObject("data") != null) {
                JsonObject data = res.getJsonObject("data");
                String encryptedAuth = AuthParamCodec.encode(data);
                JsonObject safeData = new JsonObject();
                safeData.put("encryptedAuth", encryptedAuth);
                res.put("data", safeData);
            }
            return res;
        });
    }
}
