package bootstrap

import (
	"bufio"
	"encoding/json"
	"io"
	"io/fs"
	"net/http"
	"path/filepath"

	"github.com/pkg/errors"

	"github.com/cloudreve/Cloudreve/v3/pkg/conf"
	"github.com/cloudreve/Cloudreve/v3/pkg/util"

	"github.com/gin-contrib/static"
)

const StaticFolder = "statics"

type GinFS struct {
	FS http.FileSystem
}

type staticVersion struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

// StaticFS 内置静态文件资源
var StaticFS static.ServeFileSystem

// Open 打开文件
func (b *GinFS) Open(name string) (http.File, error) {
	return b.FS.Open(name)
}

// Exists 文件是否存在
func (b *GinFS) Exists(prefix string, filepath string) bool {
	if _, err := b.FS.Open(filepath); err != nil {
		return false
	}
	return true
}

// InitStatic 初始化静态资源文件
func InitStatic(statics fs.FS) {
	if util.Exists(util.RelativePath(StaticFolder)) {
		util.Log().Info("Folder with name \"statics\" already exists, it will be used to serve static files.")
		StaticFS = static.LocalFile(util.RelativePath("statics"), false)
	} else {
		// 初始化静态资源
		embedFS, err := fs.Sub(statics, "assets/build")
		if err != nil {
			util.Log().Panic("Failed to initialize static resources: %s", err)
		}

		StaticFS = &GinFS{
			FS: http.FS(embedFS),
		}
	}
	// 检查静态资源的版本
	f, err := StaticFS.Open("version.json")
	if err != nil {
		util.Log().Warning("Missing version identifier file in static resources, please delete \"statics\" folder and rebuild it.")
		return
	}

	b, err := io.ReadAll(f)
	if err != nil {
		util.Log().Warning("Failed to read version identifier file in static resources, please delete \"statics\" folder and rebuild it.")
		return
	}

	var v staticVersion
	if err := json.Unmarshal(b, &v); err != nil {
		util.Log().Warning("Failed to parse version identifier file in static resources: %s", err)
		return
	}

	staticName := "cloudreve-frontend"
	if conf.IsPro == "true" {
		staticName += "-pro"
	}

	if v.Name != staticName {
		util.Log().Warning("Static resource version mismatch, please delete \"statics\" folder and rebuild it.")
		return
	}

	if v.Version != conf.RequiredStaticVersion {
		util.Log().Warning("Static resource version mismatch [Current %s, Desired: %s]，please delete \"statics\" folder and rebuild it.", v.Version, conf.RequiredStaticVersion)
		return
	}
}

// Eject 抽离内置静态资源
func Eject(statics fs.FS) {
	// 初始化静态资源
	embedFS, err := fs.Sub(statics, "assets/build")
	if err != nil {
		util.Log().Panic("Failed to initialize static resources: %s", err)
	}

	var walk func(relPath string, d fs.DirEntry, err error) error
	walk = func(relPath string, d fs.DirEntry, err error) error {
		if err != nil {
			return errors.Errorf("Failed to read info of %q: %s, skipping...", relPath, err)
		}

		if !d.IsDir() {
			// 写入文件
			out, err := util.CreatNestedFile(filepath.Join(util.RelativePath(""), StaticFolder, relPath))
			defer out.Close()

			if err != nil {
				return errors.Errorf("Failed to create file %q: %s, skipping...", relPath, err)
			}

			util.Log().Info("Ejecting %q...", relPath)
			obj, _ := embedFS.Open(relPath)
			if _, err := io.Copy(out, bufio.NewReader(obj)); err != nil {
				return errors.Errorf("Cannot write file %q: %s, skipping...", relPath, err)
			}
		}
		return nil
	}

	// util.Log().Info("开始导出内置静态资源...")
	err = fs.WalkDir(embedFS, ".", walk)
	if err != nil {
		util.Log().Error("Error occurs while ejecting static resources: %s", err)
		return
	}
	util.Log().Info("Finish ejecting static resources.")
}
