package com.ruoyi.file.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.PathUtils;
import com.ruoyi.file.domain.Project;
import com.ruoyi.file.request.CommonAttachReq;
import com.ruoyi.file.request.CommonUploadReq;
import com.ruoyi.file.object.ProjectState;
import com.ruoyi.file.object.StateMachine;
import com.ruoyi.file.object.UploadException;
import com.ruoyi.file.request.FinanceVoucherUploadReq;
import com.ruoyi.file.request.UploadForwardReq;
import com.ruoyi.file.object.UploadLog;
import com.ruoyi.file.request.UploadRedirectReq;
import com.ruoyi.file.object.UploadResp;
import com.ruoyi.file.object.UploadResult;
import com.ruoyi.file.object.UploadSession;
import com.ruoyi.file.object.UploadTask;
import com.ruoyi.file.utils.FileUploadFunc;
import com.ruoyi.file.utils.ProjectUtils;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;

@Service
public class FileService
{
    private UploadSession Upload(MultipartFile file, String directory, String token)
    {
        Project project = ProjectUtils.Get(token);
        if(null == project)
            throw new UploadException(HttpStatus.FORBIDDEN.value(), "禁止上传");

        UploadTask task = new UploadTask();
        task.setFile(file)
                .setDirectory(directory)
                .setDiskPath(project.getDiskPath())
        ;

        UploadResult result = FileUploadFunc.Upload(task);
        if(!result.isSuccess())
            throw new UploadException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "禁止上传");

        UploadSession session = new UploadSession(project, task, result);
        DumpUpload(session);
        return session;
    }

    public UploadResp upload(MultipartFile file, String directory, String token)
    {
        UploadLog.Clear();
        UploadSession session = Upload(file, directory, token);
        UploadLog.Output();
        UploadResp resp = new UploadResp();
        resp.setFileUrl(session.result.getFileUrl());
        resp.setFileName(session.task.OriginFileName());
        resp.setFileType(session.task.getDirectory());
        resp.setThumUrl(session.result.getThumbnailFileUrl());
        return resp;
    }

    public String uploadForward(MultipartFile file, String directory, String token, UploadForwardReq req, HttpServletRequest request, HttpServletResponse response)
    {
        UploadLog.Clear();
        UploadSession session = Upload(file, directory, token);
        UploadLog.Output();
        String resp = RequestRemote(session, req, request, response);
        return resp;
    }

    public void uploadRedirect(MultipartFile file, String directory, String token, UploadRedirectReq req, HttpServletRequest request, HttpServletResponse response)
    {
        UploadLog.Clear();
        UploadSession session = Upload(file, directory, token);
        UploadLog.Output();
        req.ParseFormData(request);
        req.ParseToken(request);
        RedirectRemote(session, req, request, response);
    }

    public void commonUpload(MultipartFile file, CommonUploadReq req, HttpServletRequest request, HttpServletResponse response)
    {
        UploadRedirectReq redirectReq = new UploadRedirectReq();
        redirectReq.setRedirectUri("/file/common/upload");
        redirectReq.ParseToken(request);

        UploadLog.Clear();
        UploadSession session = Upload(file, req.getBizPath(), req.getToken());
        UploadLog.Output();
        RedirectRemote(session, redirectReq, request, response);
    }

    public void commonAttach(MultipartFile file, CommonAttachReq req, HttpServletRequest request, HttpServletResponse response)
    {
        UploadRedirectReq redirectReq = new UploadRedirectReq();
        redirectReq.setRedirectUri("/file/common/attach");
        Map<String, Object> map = BeanUtil.beanToMap(req, false, true);
        map.remove("token");
        redirectReq.setFormData(map);
        redirectReq.ParseToken(request);

        UploadLog.Clear();
        UploadSession session = Upload(file, req.getBizPath(), req.getToken());
        UploadLog.Output();
        RedirectRemote(session, redirectReq, request, response);
    }

    public void financeVoucherUpload(MultipartFile file, FinanceVoucherUploadReq req, HttpServletRequest request, HttpServletResponse response)
    {
        UploadRedirectReq redirectReq = new UploadRedirectReq();
        redirectReq.setRedirectUri("/file/finance/voucher/upload");
        Map<String, Object> map = BeanUtil.beanToMap(req, false, true);
        map.remove("token");
        map.remove("bizPath");
        redirectReq.setFormData(map);
        redirectReq.ParseToken(request);

        UploadLog.Clear();
        UploadSession session = Upload(file, req.getBizPath(), req.getToken());
        UploadLog.Output();
        RedirectRemote(session, redirectReq, request, response);
    }

    private String RequestRemote(UploadSession session, UploadForwardReq req, HttpServletRequest request, HttpServletResponse response)
    {
        String url = PathUtils.appendPaths(session.project.getRemoteHost(), req.getRequestUri());
        HttpRequest http = HttpUtil.createRequest(Method.valueOf(req.getMethod().toUpperCase()), url);
        http.setReadTimeout(60000);
        http.setConnectionTimeout(60000);
        http.setFollowRedirects(true);
        http.header("Authorization", request.getHeader("Authorization"));
        http.header("File-Url", session.result.getFileUrl());
        http.header("Thum-Url", session.result.getThumbnailFileUrl());
        http.header("File-Type", session.task.getDirectory());
        http.header("File-Name", session.task.OriginFileName());
        HttpResponse resp;
        switch(req.getMethod().toLowerCase())
        {
            case "post":
                http.header("Content-Type", req.getContentType());
                http.body(req.getData());
                break;
            case "get":
                http.header("Content-Type", "application/x-www-form-urlencoded");
                http.body(req.getData());
            default:
                break;
        }
        UploadLog.Append("远程请求: " + http.getMethod().name() + " -> " + url + " : ");
        try
        {
            resp = http.execute();
            if(resp.isOk())
            {
                UploadLog.Appendln("成功");
                response.setContentType(req.getResponseContentType());
                return resp.body();
            }
            else
            {
                UploadLog.Appendln("错误 " + resp.getStatus());
                throw new UploadException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "远程服务器请求错误: " + resp.body());
            }
        }
        catch(Exception e)
        {
            UploadLog.Appendln("异常");
            e.printStackTrace();
            throw new UploadException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "远程服务器请求错误: " + e.getMessage(), e);
        }
        finally
        {
            UploadLog.Output();
        }
    }

    private void RedirectRemote(UploadSession session, UploadRedirectReq req, HttpServletRequest request, HttpServletResponse response)
    {
        String url = PathUtils.appendPaths(session.project.getRemoteHost(), req.getRedirectUri());
        Map<String, Object> parms = new LinkedHashMap<>();
        parms.put("api_token", TokenService.getPureToken(req.getToken()));
        parms.put("fileUrl", session.result.getFileUrl());
        parms.put("fileName", session.task.OriginFileName());
        if(StringUtils.isNotEmpty(session.result.getThumbnailFileUrl()))
            parms.put("thumUrl", session.result.getThumbnailFileUrl());
        if(CollectionUtil.isNotEmpty(req.getFormData()))
            parms.putAll(req.getFormData());
        url += (url.contains("?") ? "&" : "?") + URLUtil.buildQuery(parms, StandardCharsets.UTF_8);
        UploadLog.Append("重定向地址: " + url + " -> ");

        try
        {
            response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
            response.sendRedirect(url);
            UploadLog.Appendln("成功");
        }
        catch(Exception e)
        {
            e.printStackTrace();
            UploadLog.Appendln("异常");
            throw new UploadException(HttpServletResponse.SC_MOVED_PERMANENTLY, "重定向错误", e);
        }
        finally
        {
            UploadLog.Output();
        }
    }

    private void DumpUpload(UploadSession session)
    {
        ProjectState state = new ProjectState(session.project.getToken());
        state.AddNumUpload(1);
        state.SetLastUploadTime(session.task.time);
        state.AddUploadSize(session.task.UploadSize());
        state.SetMaxUploadSize(state.uploadSize);
        state.SetMaxHandleDuration(DateUtil.between(session.result.getTime(), session.task.getTime(), DateUnit.MS));
        if(session.result.isSuccess())
        {
            state.AddNumWrite(1);
            state.AddUploadSize(session.task.UploadSize());
            state.AddWriteSize(session.result.getFileSize());
            state.AddWriteSize(session.result.getThumbnailFileSize());
            state.SetLastWriteTime(session.result.getTime());
            state.SetMaxWriteSize(state.writeSize);
        }
        else
        {
            state.AddNumError(1);
            state.SetLastErrorTime(session.result.getTime());
        }
        StateMachine.INSTANCE.AddState(state);
        UploadLog.Appendln(state.ToString(", "));
    }
}
