package cd.casic.plugin;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.server.ServerWebInputException;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


@Slf4j
@Component
public class BufferedPluginBundleResource implements DisposableBean {

    private final AtomicReference<FileSystemResource> jsBundle = new AtomicReference<>();
    private final AtomicReference<FileSystemResource> cssBundle = new AtomicReference<>();

    private final ReadWriteLock jsLock = new ReentrantReadWriteLock();
    private final ReadWriteLock cssLock = new ReentrantReadWriteLock();

    private Path tempDir;

    public FileSystemResource getJsBundle(String version, List<DataBuffer> dataBuffers) {
        String fileName = tempFileName(version, ".js");
        jsLock.readLock().lock();
        try {
            FileSystemResource jsBundleResource = jsBundle.get();
            if (getResourceIfNotChange(fileName, jsBundleResource) != null) {
                return jsBundleResource;
            }
        } finally {
            jsLock.readLock().unlock();
        }

        jsLock.writeLock().lock();
        try {
            FileSystemResource oldJsBundle = jsBundle.get();
            FileSystemResource newJsBundle = writeBundle(fileName, dataBuffers);
            jsBundle.compareAndSet(oldJsBundle, newJsBundle);
            return newJsBundle;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            jsLock.writeLock().unlock();
        }
    }

    public FileSystemResource getCssBundle(String version, List<DataBuffer> dataBuffers) {
        String fileName = tempFileName(version, ".css");
        try {
            cssLock.readLock().lock();
            FileSystemResource cssBundleResource = cssBundle.get();
            if (getResourceIfNotChange(fileName, cssBundleResource) != null) {
                return cssBundleResource;
            }
        } finally {
            cssLock.readLock().unlock();
        }
        cssLock.writeLock().lock();
        try {
            FileSystemResource oldCssBundle = cssBundle.get();
            FileSystemResource newCssBundle = writeBundle(fileName, dataBuffers);
            cssBundle.compareAndSet(oldCssBundle, newCssBundle);
            return newCssBundle;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            cssLock.writeLock().unlock();
        }
    }

    @Nullable
    private Resource getResourceIfNotChange(String fileName, Resource resource) {
        if (resource != null && resource.exists() && fileName.equals(resource.getFilename())) {
            return resource;
        }
        return null;
    }

    private FileSystemResource writeBundle(String fileName, List<DataBuffer> dataBuffers) throws IOException {
        Path filePath = createTempFileToStore(fileName);
        // 打开文件输出流
        try (FileOutputStream fileOutputStream = new FileOutputStream(filePath.toFile());
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)) {

            // 遍历 DataBuffer 列表并写入文件
            for (DataBuffer dataBuffer : dataBuffers) {
                // 将 DataBuffer 转换为字节数组
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                // 写入文件
                bufferedOutputStream.write(bytes);
                // 释放 DataBuffer
                DataBufferUtils.release(dataBuffer);
            }
        }
        return new FileSystemResource(filePath);
    }


    private Path createTempFileToStore(String fileName) {
        try {
            if (tempDir == null || !Files.exists(tempDir)) {
                this.tempDir = Files.createTempDirectory("ops-plugin-bundle");
            }
            Path path = tempDir.resolve(fileName);
            log.info("Create temp file: {}", path);
            Files.deleteIfExists(path);
            return Files.createFile(path);
        } catch (IOException e) {
            throw new ServerWebInputException("Failed to create temp file.", null, e);
        }
    }

    /**
     * 生成临时文件名
     *
     * @param v
     * @param suffix
     * @return
     */
    private String tempFileName(String v, String suffix) {
        Assert.notNull(v, "Version must not be null");
        Assert.notNull(suffix, "Suffix must not be null");
        return v + suffix;
    }

    @Override
    public void destroy() throws Exception {
        if (tempDir != null && Files.exists(tempDir)) {
            FileSystemUtils.deleteRecursively(tempDir);
        }
        this.jsBundle.set(null);
        this.cssBundle.set(null);
    }
}