package org.lym.pom.util.github;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.jayway.jsonpath.JsonPath;
import lombok.Data;
import org.lym.pom.util.github.dto.GithubFileInfo;
import org.lym.pom.util.github.dto.GithubFileAndCommit;
import org.shoulder.core.exception.BaseRuntimeException;
import org.shoulder.core.util.JsonUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;

/**
 * github 公开文件库 API 工具
 *
 * 其他图库：gitee： https://zhuanlan.zhihu.com/p/663880869 、https://gitee.com/api/v5/swagger#/postV5ReposOwnerRepoContentsPath 、https://blog.csdn.net/qq_57581439/article/details/129251624
 * 其他：https://baijiahao.baidu.com/s?id=1764213861856778692&wfr=spider&for=pc
 *
 * @author lym
 */
public class GitHubFileUtil {
    public static final String COMMON_MAIL = "cn_lym@foxmai.com";
    public static final String GIT_AUTHOR = "lym";
    public static final String GIT_BRANCH = "main";
    public static final String GIT_REPO = "ChinaLym/images";
    public static final String GITHUB_TOKEN = "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";
    public static final String APP_NAME = "posts";
    // 从代码 API 上保证每个 project 访问隔离
    public static final String GITHUB_REPOSITORY_API_URL = "https://api.github.com/repos/" + GIT_REPO + "/contents/";
    public static final String GITHUB_JSON = "application/vnd.github+json";
    public static final String CDN_URL_PREFIX = "https://fastly.jsdelivr.net/gh/" + GIT_REPO + "/";

    /**
     * 上传文件
     *
     * @param filepath    文件地址
     * @param filePostfix 文件后缀
     * @param message     提交描述
     * @return 文件访问地址
     */
    public static GithubFileAndCommit readFileAndUpload(String filepath, String filePostfix, String message) {

        // 重置文件名防止重复
        String fileName = APP_NAME + "/" + UUID.randomUUID().toString().replace("-", "") + filePostfix;
        return uploading(fileName, encodeToBase64(filepath), message);
    }
    public static GithubFileAndCommit uploading(String targetFilePath, String fileBase64, String message) {

        CommitInfo commitInfo = new CommitInfo();
        commitInfo.setMessage(message);
        commitInfo.setContent(fileBase64);
        // 非null，可空串
        commitInfo.setSha("");

        String body = requestToGithub(targetFilePath, HttpRequest::put, commitInfo);
//        String download_url = JsonPath.read(body, "$.content.download_url");
        return JsonUtils.parseObject(body, GithubFileAndCommit.class);
    }

    /**
     * 查询所有文件的访问地址
     *
     * @param directory 要查询那个目录下的所有图片
     * @return 所有文件访问地址集合
     */
    public static List<GithubFileInfo> list(String directory) {
        String body = requestToGithub(directory, HttpRequest::get, null);
        return JsonUtils.parseObject(body, List.class, GithubFileInfo.class);
    }

    /**
     * 查询单文件
     *
     * @param filePath github库中的路径；例：img/xxx.jpg
     * @return 文件访问地址
     */
    public static GithubFileInfo queryFile(String filePath) {
        String body = requestToGithub(filePath, HttpRequest::get, null);
        return JsonUtils.parseObject(body, GithubFileInfo.class);
    }

    /**
     * 查询sha 替换、删除文件时使用
     *
     * @param fileUrl github库中的路径；例：img/xxx.jpg
     * @return sha
     */
    public static String querySha(String fileUrl) {
        String body = requestToGithub(fileUrl, HttpRequest::get, null);
        return JsonPath.read(body, "$.sha");
    }

    /**
     * 替换文件
     *
     * @param newFileLocalPath 新文件地址
     * @param message  提交描述
     * @param oldFileSha      被替换文件的标识
     * @param oldFileGithubPath     要替换的路径+文件名 例：img/12.png
     * @return 新文件访问地址
     */
    public static GithubFileAndCommit updateFile(String oldFileGithubPath, String oldFileSha, String newFileLocalPath, String message) {
        // 把上传文件内容Base64
        String fileBase64 = encodeToBase64(newFileLocalPath);
        // 重置文件名防止重复
        CommitInfo commitInfo = new CommitInfo();
        commitInfo.setMessage(message);
        commitInfo.setContent(fileBase64);
        commitInfo.setSha(oldFileSha);

        String body = requestToGithub(oldFileGithubPath, HttpRequest::put, commitInfo);
        return JsonUtils.parseObject(body, GithubFileAndCommit.class);
    }


    /**
     * 删除文件
     *
     * @param message 提交描述
     * @param sha     被删除文件的标识
     * @param path    删除文件路径 例：img/12.png
     */
    public static GithubFileAndCommit delFile(String path, String sha, String message) {
        CommitInfo commitInfo = new CommitInfo();
        commitInfo.setMessage(message);
        commitInfo.setSha(sha);

        String body = requestToGithub(path, HttpRequest::delete, commitInfo);
        return JsonUtils.parseObject(body, GithubFileAndCommit.class);
    }

    /**
     * 提交信息
     */
    @Data
    public static class CommitInfo {
        private String message;
        private String content;
        /**
         * 删除必填
         */
        private String sha;
        private String branch = GIT_BRANCH;
        private CommitterInfo committer = CommitterInfo.DEFAULT;
    }

    /**
     * 提交者信息
     */
    @Data
    public static class CommitterInfo {

        public static final CommitterInfo DEFAULT = getCommitterInfo();

        private static CommitterInfo getCommitterInfo() {
            CommitterInfo committer = new CommitterInfo();
            committer.setName(GIT_AUTHOR);
            committer.setEmail(COMMON_MAIL);
            return committer;
        }

        private String name;
        private String email;
    }

    private static String requestToGithub(String directory, Function<String, HttpRequest> requestMethod, CommitInfo body) {
        String url = GITHUB_REPOSITORY_API_URL.concat(directory);
        if(url.endsWith("/")) {
            url = url.substring(0, url.length() - 1);
        }
        HttpRequest req = requestMethod.apply(url)
                .header("Accept", GITHUB_JSON)
                .header("User-Agent", APP_NAME)
                .header("X-GitHub-Api-Version", "2022-11-28")
                .header("Authorization", "token " + GITHUB_TOKEN);
        if (body != null) {
            req = req.body(JsonUtils.toJson(body));
        }
        try (HttpResponse response = req.execute()) {
            if(response.getStatus() > 299) {
                throw new InvokeGitHubException(response);
            }
            return response.body();
            // status[200:成功；404:找不到资源；409:冲突；422:验证失败，参数有问题；503:暂停服务]
        } catch (Exception e) {
            throw new RuntimeException("Failed to request to GitHub", e);
        }
    }

    public static class InvokeGitHubException extends BaseRuntimeException {

        private int statusCode = -1;
        private String body;

        private Map<String, List<String>> headers = new HashMap<>();

        public InvokeGitHubException(HttpResponse response) {
            super(response.body());
            this.headers = response.headers();
            this.statusCode = response.getStatus();
        }
    }

    /**
     * 文件转base64
     *
     * @param filePath 文件地址
     * @return
     */
    public static String encodeToBase64(String filePath) {

        if (filePath == null) {
            return null;
        }
        try {
            byte[] b = Files.readAllBytes(Paths.get(filePath));
            return Base64.getEncoder().encodeToString(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}