package excel_export

import (
	"encoding/json"
	"fmt"
	"gitea.cdlsxd.cn/self-tools/l_excel_export/export_err"
	"gitea.cdlsxd.cn/self-tools/l_excel_export/pkg"
	"gitea.cdlsxd.cn/self-tools/l_excel_export/types"
	"github.com/xuri/excelize/v2"

	"io"
	"math"
	"net/http"
	"os"
	"sort"
	"time"
)

type ExportExcel struct {
	config    *Config
	password  string
	saveFunc  func(file *excelize.File) (url string, err error)
	sheetName string
	logPath   string
	jobName   string
	task      *types.Task

	exportData [][]interface{}
	header     []interface{}
}

func NewExport(opts ...Option) *ExportExcel {
	e := &ExportExcel{}
	for _, opt := range opts {
		opt(e) // 应用选项
	}
	e.initJobName()
	return e
}

func (e *ExportExcel) Run(config *Config) (taskId string, err error) {
	err = e.init(config)
	if err != nil {
		return
	}
	f, err := e.getFile()
	if err != nil {
		return
	}
	go func(f *excelize.File) {
		defer func() {
			if r := recover(); r != nil {
				f.Close()
				fmt.Println(r)
			}
		}()
		e.task.Status = Running
		e.updateTask()
		err = e.run(f)
		if err != nil {
			e.task.Status = Err
			e.updateTask()
			return
		}
		e.task.Ftime = time.Now().Format(time.DateTime)
		e.finish(f)
	}(f)
	return e.task.TaskId, nil
}

func (e *ExportExcel) TaskInfo(task_id string) (task *types.Task, err error) {

	e.logPath, err = pkg.DefaultLogPath(e.jobName)

	if err != nil {
		return nil, err
	}
	e.task = &types.Task{
		TaskId: task_id,
	}

	taskInfo, err := os.ReadFile(e.logFile())
	if err != nil {
		return nil, err
	}
	_ = json.Unmarshal(taskInfo, e.task)
	return e.task, nil
}

func (e *ExportExcel) DownloadExcel(w http.ResponseWriter, task_id string) (err error) {
	task, err := e.TaskInfo(task_id)
	if err != nil {
		return err
	}
	_, exist := os.Stat(task.Url)
	if exist != nil {
		return fmt.Errorf("文件不存在")
	}

	file, err := os.Open(task.Url)
	if err != nil {
		return err
	}
	defer file.Close()
	payload, err := io.ReadAll(file)
	if err != nil {
		return err
	}
	// 设置HTTP响应头
	// 打开为预览
	//ctx.Response().Header().Set("Content-Type", "image/png")
	// 打开为下载
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Disposition", "attachment; filename="+task.Url)
	// 将结果写入
	_, err = w.Write(payload)
	return
}

func (e *ExportExcel) TaskHis(page int, num int) (res *types.ResPage, err error) {
	var data []*types.Task
	e.logPath, err = pkg.DefaultLogPath(e.jobName)
	if err != nil {
		return res, err
	}

	entries := e.SortLogFileWithStatus()
	count := len(entries)

	begin := (page - 1) * num
	entEnd := begin + num
	if count < entEnd {
		entEnd = count
	}
	data = entries[begin:entEnd]

	return &types.ResPage{
		Page:     page,
		Limit:    num,
		Total:    count,
		Data:     data,
		LastPage: int(math.Ceil(float64(count) / float64(num))),
	}, nil
}

func (e *ExportExcel) run(f *excelize.File) (err error) {

	index, err := f.NewStreamWriter(e.getSheetName())
	if err != nil {
		return err
	}
	index.SetRow("A1", e.header)
	if err != nil {
		return err
	}
	count := len(e.exportData)
	for i, v := range e.exportData {
		cell, _ := excelize.CoordinatesToCellName(1, i+2)
		index.SetRow(cell, v)
		e.task.Process = i * 100 / count
		e.updateTask()
	}

	if err = e.save(f); err != nil {
		return err
	}
	return err
}

func (e *ExportExcel) finish(f *excelize.File) {
	f.Close()
	e.task.Status = Finish
	e.task.Process = 100
	e.updateTask()
}

func (e *ExportExcel) save(f *excelize.File) error {
	e.task.Url = e.getUrl()
	return f.SaveAs(e.task.Url)
}

func (e *ExportExcel) getUrl() string {
	return fmt.Sprintf("%s/%s_%s%s", e.config.SavePath, e.config.FileName, e.task.TaskId, e.config.Ext)
}

func (e *ExportExcel) init(config *Config) error {
	e.config = config

	err := e.check()
	if err != nil {
		return err
	}
	e.task = &types.Task{
		TaskId:  pkg.CreateTaskId(),
		Process: 0,
		Url:     "",
		Ctime:   time.Now().Format(time.DateTime),
		Ftime:   "",
		Status:  Init,
	}
	err = e.updateTask()
	if err != nil {
		return err
	}
	dataMap := pkg.GetData(e.config.Data)
	if len(dataMap) == 0 {
		return export_err.ErrDataError
	}

	if len(e.config.Head) == 0 {
		return export_err.ErrHeadNotSet
	}

	for _, v := range e.config.Head {

		e.header = append(e.header, v.ColName)
	}

	//todo:这一步目的是为了保证数据与header未知一致,但是在大数据量的时候存在性能问题
	for _, v := range dataMap {
		var (
			slice []interface{}
		)
		for _, vv := range e.config.Head {
			slice = append(slice, v[vv.FieldName])
		}
		e.exportData = append(e.exportData, slice)
	}
	//检测文件是否存在
	if _, err = os.Stat(e.config.SavePath); os.IsNotExist(err) {
		// 文件夹不存在,尝试创建
		err = os.MkdirAll(e.config.SavePath, os.ModePerm)
		if err != nil {
			return export_err.ErrCreateExcelPathFail
		}
	} else if err != nil {
		return export_err.ErrCheckExcelPathFail
	}

	return nil
}

func (e *ExportExcel) updateTask() error {

	file, err := os.OpenFile(e.logFile(), os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0766)
	if err != nil {
		return err
	}
	defer file.Close()

	taskInfo, err := json.Marshal(e.task)
	if err != nil {
		return err
	}
	jsonInfo := string(taskInfo)
	_, err = file.WriteString(jsonInfo)
	if err != nil {
		return err
	}
	return nil
}

func (e *ExportExcel) logFile() string {
	return fmt.Sprintf("%s/%s", e.logPath, e.task.TaskId)
}

func (e *ExportExcel) getFile() (*excelize.File, error) {
	f, err := excelize.OpenFile(e.path())
	if err != nil {
		f = excelize.NewFile()
	}
	return f, nil
}

func (e *ExportExcel) path() string {
	return fmt.Sprintf("%s/%s.%s", e.config.SavePath, e.config.FileName, e.config.Ext)
}

func (e *ExportExcel) check() (err error) {

	if e.config.SavePath == "" && e.saveFunc == nil {
		return export_err.ErrNotSetSaveWay
	}

	if len(e.logPath) == 0 {
		e.logPath, err = pkg.DefaultLogPath(e.jobName)
		return
	}
	return
}

func (e *ExportExcel) initJobName() {
	if len(e.jobName) == 0 {
		e.jobName = "default"
	}
}

func (e *ExportExcel) getSheetName() string {
	if len(e.sheetName) == 0 {
		e.sheetName = "Sheet1"
	}
	return e.sheetName
}

func (e *ExportExcel) SortLogFileWithStatus() (fileInfoList []*types.Task) {

	// 获取目录中的文件信息
	d, _ := os.Open(e.logPath)
	defer d.Close()
	files, _ := d.ReadDir(0)

	// 填充切片
	for _, file := range files {
		fileName := file.Name()
		bytes, _ := os.ReadFile(e.logPath + "/" + fileName)
		var info types.Task
		_ = json.Unmarshal(bytes, &info)
		fileInfoList = append(fileInfoList, &info)
	}

	// 根据修改时间对切片进行排序

	sort.Slice(fileInfoList, func(i, j int) bool {

		return fileInfoList[i].Ctime >= fileInfoList[j].Ctime
	})
	return fileInfoList
}