package cn.reghao.oss.store.service;

import cn.reghao.oss.store.db.mapper.FileMetaMapper;
import cn.reghao.oss.store.model.dto.ContentRange;
import cn.reghao.oss.store.model.po.FileMeta;
import cn.reghao.oss.storeapi.dto.ObjectMeta;
import cn.reghao.jutil.web.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * @author reghao
 * @date 2023-05-10 13:28:18
 */
@Slf4j
@Service
public class GetObjectService {
    private final FileMetaMapper fileMetaMapper;
    // 1MiB
    private final int bufSize = 1024*1024;
    private final StoreLocalService storeLocalService;

    public GetObjectService(FileMetaMapper fileMetaMapper, StoreLocalService storeLocalService) {
        this.fileMetaMapper = fileMetaMapper;
        this.storeLocalService = storeLocalService;
    }
    
    public void headObject(String objectName) throws IOException {
        HttpServletResponse response = ServletUtil.getResponse();
        FileMeta fileMeta = fileMetaMapper.findByObjectName(objectName);
        if (fileMeta == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        } else {
            response.setStatus(HttpServletResponse.SC_OK);
            String contentType = fileMeta.getContentType();
            long contentLength = fileMeta.getSize();
            String eTag = fileMeta.getSha256sum();
            response.setHeader("content-type", contentType);
            response.setHeader("content-length", ""+contentLength);
            response.setHeader("eTag", eTag);
        }

        OutputStream outputStream = response.getOutputStream();
        outputStream.flush();
        outputStream.close();
    }

    public void headObject1(String sha256sum) throws IOException {
        HttpServletResponse response = ServletUtil.getResponse();
        FileMeta fileMeta = fileMetaMapper.findBySha256sum(sha256sum);
        if (fileMeta == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        } else {
            response.setStatus(HttpServletResponse.SC_OK);
            String contentType = fileMeta.getContentType();
            long contentLength = fileMeta.getSize();
            String eTag = fileMeta.getSha256sum();
            response.setHeader("content-type", contentType);
            response.setHeader("content-length", ""+contentLength);
            response.setHeader("eTag", eTag);
        }

        OutputStream outputStream = response.getOutputStream();
        outputStream.flush();
        outputStream.close();
    }

    public void getObject(ObjectMeta objectMeta) throws IOException {
        String host = ServletUtil.getRequest().getHeader("host");
        long len = objectMeta.getSize();
        String range = ServletUtil.getRequest().getHeader("range");
        if (range != null) {
            ContentRange contentRange = parseContentRange(range, len);
            writeContentRange(objectMeta, contentRange);
        } else {
            if (host.contains(storeLocalService.getDomain())) {
                writeWholeContent(objectMeta);
            } else {
                writeDownloadContent(objectMeta);
            }
        }
    }

    /*public void downloadObject(String objectName) throws IOException {
        ObjectMeta objectMeta = objectRepository.getObjectMeta(objectName);
        if (objectMeta == null) {
            writeResponse(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        writeDownloadContent(objectMeta);
    }*/

    public void writeResponse(int statusCode) throws IOException {
        HttpServletResponse response = ServletUtil.getResponse();
        response.setStatus(statusCode);
        OutputStream outputStream = response.getOutputStream();
        outputStream.flush();
        outputStream.close();
    }

    public void writeResponse(int statusCode, String payload) throws IOException {
        HttpServletResponse response = ServletUtil.getResponse();
        response.setStatus(statusCode);
        response.setContentType("text/html");
        response.setHeader("Content-Length", ""+payload.length());

        OutputStream outputStream = response.getOutputStream();
        outputStream.write(payload.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }

    private ContentRange parseContentRange(String range, long len) {
        String rangeStr = StringUtils.trimAllWhitespace(range);
        String[] arr = rangeStr.replace("bytes=", "").split("-");
        long start = Long.parseLong(arr[0]);
        long end;
        if (arr.length == 2) {
            end = Long.parseLong(arr[1]);
        } else {
            long l = len - start;
            end = Math.min(l, bufSize) + start;
        }

        return new ContentRange(start, end);
    }

    private void writeContentRange(ObjectMeta objectMeta, ContentRange contentRange) throws IOException {
        long start = contentRange.getStart();
        long end = contentRange.getEnd();
        long contentLength = objectMeta.getSize();
        HttpServletResponse response = ServletUtil.getResponse();
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        response.setContentType(objectMeta.getContentType());
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Length", ""+(end-start));
        response.setHeader("Content-Range", "bytes "+start+"-"+(end-1)+"/"+contentLength);

        String absolutePath = objectMeta.getAbsolutePath();
        OutputStream outputStream = response.getOutputStream();
        writeResponse(outputStream, absolutePath, start, end);
    }

    public void writeDownloadContent(ObjectMeta objectMeta) throws IOException {
        HttpServletResponse response = ServletUtil.getResponse();
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType(objectMeta.getContentType());
        response.setHeader("Content-Length", ""+objectMeta.getSize());
        /*response.setHeader("Content-Length", ""+(len-start));
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Range", "bytes "+start+"-"+(len-1)+"/"+len);*/
        String filename = fileMetaMapper.findByObjectId(objectMeta.getObjectId()).getFilename();
        String value = String.format("attachment;filename=%s", URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()));
        response.setHeader("Content-Disposition", value);
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");

        OutputStream outputStream = response.getOutputStream();
        writeResponse(outputStream, objectMeta.getAbsolutePath(), 0, objectMeta.getSize());
    }

    private void writeWholeContent(ObjectMeta objectMeta) throws IOException {
        HttpServletResponse response = ServletUtil.getResponse();
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType(objectMeta.getContentType());
        response.setHeader("Content-Length", ""+objectMeta.getSize());

        OutputStream outputStream = response.getOutputStream();
        writeResponse(outputStream, objectMeta.getAbsolutePath(), 0, objectMeta.getSize());
    }

    private void writeResponse(OutputStream outputStream, String absolutePath, long start, long end) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(absolutePath, "r");
        raf.seek(start);

        long len = end-start+1;
        if (len < bufSize) {
            int len1 = (int) len;
            byte[] buf1 = new byte[len1];
            int readLen1 = raf.read(buf1, 0, len1);
            outputStream.write(buf1, 0, readLen1);
        } else {
            byte[] buf = new byte[bufSize];
            long totalRead = 0;
            int readLen;
            while ((readLen = raf.read(buf, 0, bufSize)) != -1) {
                outputStream.write(buf, 0, readLen);
                totalRead += readLen;

                long left = len - totalRead;
                if (left < bufSize) {
                    int left1 = (int) left;
                    byte[] buf1 = new byte[left1];
                    int readLen1 = raf.read(buf1, 0, left1);
                    outputStream.write(buf1, 0, readLen1);
                    break;
                }
            }
        }

        outputStream.flush();
        outputStream.close();
        raf.close();
    }
}
