// Package excel -----------------------------
// @file      : templateInter.go
// @author    : JJXu
// @contact   : wavingbear@163.com
// @time      : 2022/7/23 15:34
// -------------------------------------------
package excel

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"sync"

	"github.com/tealeg/xlsx"
	"github.com/xuri/excelize/v2"
)

var (
	ErrSheetNotExist             = errors.New("sheet does not exist")
	ErrSheetDataFormatNotSupport = errors.New("sheet data format not support")
	//ErrSheetNameNotInTemplate = errors.New("sheet name not in emailTemplate")
)

// ======================================================================================================================
//
//	Sheet define
type HeaderRow struct {
	RowNum int //行号
	Values []string
}

// 抽象工作簿
type Sheet interface {
	GetData() any               //数据，支持struct、[]struct、[]*struct三种类型
	SheetName() string          //表名称
	SheetHeaders() []string     //表头名称，没有则跳过插入表头的步骤
	GetJsonFiledList() []string //指定字段的排列顺序，默认按照结构体中的顺序排列(表头与数据没有关联，使用这个指定字段插入顺序)
}

// 实例化工作簿,使用模板生成excel的话用这个
func NewTemplateSheet(sheetName string, datas any) Sheet {
	return &newSheetDefine{
		Datas: datas,
		Name:  sheetName,
	}
}

// 实例化工作簿，不通过模板生成excel的话用这个
func NewSheet(sheetName string, datas any, headers []string, jsonFiledList ...[]string) Sheet {
	st := &newSheetDefine{
		Datas:   datas,
		Name:    sheetName,
		Headers: headers,
	}
	if jsonFiledList != nil {
		st.JsonFiledList = jsonFiledList[0]
	}
	return st
}

// 定义一个能够通用的工作簿结构，此结构必须遵循Sheet接口规范
type newSheetDefine struct {
	Datas         any
	Name          string
	Headers       []string
	JsonFiledList []string
}

func (s *newSheetDefine) GetData() any {
	return s.Datas
}
func (s *newSheetDefine) SheetName() string {
	return s.Name
}
func (s *newSheetDefine) SheetHeaders() []string {
	return s.Headers
}
func (s *newSheetDefine) GetJsonFiledList() []string {
	return s.JsonFiledList
}

//======================================================================================================================
// 												Sheet define

// WriteToExcel 通过模板文件写入数据并另存为
// param fileName : 文件名
// param filesSuffix : 文件后缀名生成函数
// param fileRoot : 导出目录
// param templatePath : 模板文件路径
// param sheets : Sheet类型的数据，类型为[]Sheet
// return path : 文件路径
// return exfileName : 导出后的文件名
// return err
func WriteToExcel(fileName string, fileRoot string, templatePath string, sheets ...Sheet) (path string, exFileName string, err error) {
	var exc *Excel
	exc, err = NewExcelCreatorFromTemplate(fileName, fileRoot, templatePath, sheets...)
	if err != nil {
		return
	}
	exc.UseOption(OptionFileNameSuffixWithUnixTime)
	return exc.WriteToFile()
}

// ReadDataFromExcel 从excel文件读取数据
func ReadDataFromExcel(filepath string, sheetName string, handler func(rowIndex int, rows []string)) error {
	ex := Excel{OriginFilePath: filepath}
	return ex.ReadSheetData(sheetName, handler)
}

// ReadDataFromBytes 从io口读取数据，用户http上传的附件
func ReadDataFromBytes(file io.Reader, sheetName string, handler func(rowIndex int, row []string)) error {
	exce, err := excelize.OpenReader(file)
	if err != nil {
		return err
	}
	var ex = Excel{ex: exce}
	return ex.ReadSheetData(sheetName, handler)
}

// 读取模板并创建工作表生成器
func NewExcelCreatorFromTemplate(fileName string, fileRoot string, templatePath string, sheets ...Sheet) (exc *Excel, err error) {
	exc = &Excel{
		SaveRoot:       fileRoot,
		SaveName:       fileName,
		OriginFilePath: templatePath,
		rwLock:         sync.RWMutex{},
	}
	if sheets != nil {
		err = exc.AddSheets(sheets...)
		if err != nil {
			return
		}
	}
	return exc, nil
}

// 新建文件并创建工作表生成器
func NewExcelCreatorWithNewFile(fileName string, fileRoot string, sheets ...Sheet) (exc *Excel, err error) {
	return NewExcelCreatorFromTemplate(fileName, fileRoot, "", sheets...)
}

type Excel struct {
	ex             *excelize.File
	SaveRoot       string
	SaveName       string
	OriginFilePath string
	Sheets         map[string]Sheet
	rwLock         sync.RWMutex
	Opts           []Option
	After          []Option
}

// UseOption 使用可选项
func (s *Excel) UseOption(opts ...Option) {
	if opts != nil {
		s.Opts = append(s.Opts, opts...)
	}
}
func (s *Excel) AfterAddData(after ...Option) {
	if after != nil {
		s.After = append(s.After, after...)
	}
}

// 添加工作簿
// 注意如果添加相同的工作簿，之前的会被覆盖
func (s *Excel) AddSheets(sheets ...Sheet) (err error) {
	if s.Sheets == nil {
		s.Sheets = make(map[string]Sheet, 0)
	}
	for _, sheet := range sheets {
		var sheetName = sheet.SheetName()
		s.Sheets[sheetName] = sheet
	}
	return
}

// 删除工作簿
func (s *Excel) DeleteSheets(sheetName string) error {
	if s.Sheets == nil {
		return nil
	} else if s.Sheets[sheetName] != nil {
		delete(s.Sheets, sheetName)
	} else {
		return ErrSheetNotExist
	}
	return nil
}

// 读取工作簿
func (s *Excel) ReadSheetData(sheetName string, handler func(rowIndex int, row []string)) (err error) {
	if s.ex == nil {
		s.ex, err = excelize.OpenFile(s.OriginFilePath)
		if err != nil {
			return
		}
	}
	datas, err := s.ex.GetRows(sheetName)
	for i, row := range datas {
		handler(i, row)
	}
	return nil
}

// 写入到文件
func (s *Excel) WriteToFile() (path string, fileName string, err error) {
	if s.ex == nil {
		if s.OriginFilePath == "" {
			s.ex = excelize.NewFile()
		} else {
			s.ex, err = excelize.OpenFile(s.OriginFilePath)
			if err != nil {
				return
			}
		}
	}
	if s.Opts != nil {
		for _, opt := range s.Opts {
			opt(s)
		}
	}
	//插入数据
	for sheetName, st := range s.Sheets {
		//添加表头，没有定义则不插入
		if st.SheetHeaders() != nil {
			for i, c := range st.SheetHeaders() {
				err = s.ex.SetCellValue(sheetName, GetCellIndex(1, i+1), c)
				if err != nil {
					fmt.Println(err.Error())
					return
				}
			}
			headerStyleID, errs := s.ex.NewStyle(NewDefaultHeaderStyle())
			if errs != nil {
				fmt.Println(errs)
				err = errs
				return
			}
			//表头设置为默认样式，边框加粗、字体加粗
			if err = s.ex.SetCellStyle(st.SheetName(), "A1", GetCellIndex(1, len(st.SheetHeaders())), headerStyleID); err != nil {
				return
			}
		}
		//添加数据
		var firstRow = s.getFirstEmptyRowIndex(s.ex, sheetName)
		var SheetData = reflect.ValueOf(st.GetData())
		var SheetType = reflect.TypeOf(st.GetData())
		writerFunc, exists := writerMap[SheetData.Kind()]
		if exists {
			err = writerFunc(s, st, SheetData, SheetType, firstRow)
		} else {
			return "", "", ErrSheetDataFormatNotSupport
		}
	}
	if s.After != nil {
		for _, after := range s.After {
			after(s)
		}
	}
	//检测并生成目录
	_ = os.MkdirAll(s.SaveRoot, os.ModePerm)
	//保存
	path = filepath.ToSlash(filepath.Join(s.SaveRoot, s.SaveName))
	fileName = s.SaveName
	s.rwLock.Lock()
	if err = s.ex.SaveAs(path); err != nil {
		log.Println(fmt.Sprintf("save file error :%v", err))
		s.rwLock.Unlock()
		return
	}
	s.rwLock.Unlock()
	return
}

// getJsonFieldList 获取json字段列表
func (s *Excel) getJsonFieldList(sheetType reflect.Type) (tagList []string) {
	t := sheetType.Elem()
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		if t.Kind() != reflect.Struct {
			return
		}
	}
	for i := 0; i < t.NumField(); i++ {
		var tag = t.Field(i).Tag.Get("json")
		if tag != "" {
			tagList = append(tagList, tag)
		}
	}
	return
}

// dataToMap 数据转字典
func (s *Excel) dataToMap(sheet reflect.Value, sheetType reflect.Type) (dataMap map[string]any) {
	dataMap = make(map[string]any)
	t := sheetType.Elem()
	//指针类型结构体拿真实的对象
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		sheet = sheet.Elem()
	}
	for i := 0; i < t.NumField(); i++ {
		var tag = t.Field(i).Tag.Get("json")
		if tag != "" {
			dataMap[t.Field(i).Tag.Get("json")] = sheet.Field(i).Interface()
		}
	}
	return dataMap
}

// getFirstEmptyRowIndex 获取首个空行的索引位置
func (s *Excel) getFirstEmptyRowIndex(ex *excelize.File, sheetName string) (index int) {
	rows, err := ex.GetRows(sheetName)
	if err != nil {
		return 1
	}
	return len(rows)
}

func ToExcel(titleList []string, dataList []interface{}) (content io.ReadSeeker) {
	// 生成一个新的文件
	file := xlsx.NewFile()
	// 添加sheet页
	sheet, _ := file.AddSheet("Sheet1")
	// 插入表头
	titleRow := sheet.AddRow()
	for _, v := range titleList {
		cell := titleRow.AddCell()
		cell.Value = v
	}
	// 插入内容
	for _, v := range dataList {
		row := sheet.AddRow()
		row.WriteStruct(v, -1)
	}

	var buffer bytes.Buffer
	_ = file.Write(&buffer)
	file.Save("1.xlsx")
	content = bytes.NewReader(buffer.Bytes())
	return
}

// 自动设置单元格宽度
func (s *Excel) autoResetCellWidth(sheetObj Sheet) {
	// 获取最大字符宽度
	maxWidths := make(map[int]int)
	var sheetData = reflect.ValueOf(sheetObj.GetData())
	var rowLen = 1
	var columnLen = len(sheetObj.GetJsonFiledList())
	if sheetData.Kind() == reflect.Slice {
		rowLen = sheetData.Len()
	}
	limitWidth := 90
	for col := 1; col <= columnLen; col++ {
		var maxWidth int
		for row := 0; row < rowLen; row++ {
			value, _ := s.ex.GetCellValue(sheetObj.SheetName(), GetCellIndex(row+1, col))
			width := len(value)
			if width > limitWidth {
				width = limitWidth
			}
			if width > maxWidth {
				maxWidth = width
			}
		}
		maxWidths[col] = maxWidth
	}

	// 设置列宽度
	for col, width := range maxWidths {
		colChar := GetColumnIndex(col)
		s.ex.SetColWidth(sheetObj.SheetName(), colChar, colChar, float64(width+2))
	}
}
