package redis

import (
	"fmt"
	"github.com/linmadan/egglib-go/utils/json"
	"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
	"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
)

const (
	TemporaryFileExpire = 3600 * 10
)

func KeyTemporaryFileInfo(fileId int) string {
	return fmt.Sprintf("%v:file:temporary:%v", constant.CACHE_PREFIX, fileId)
}

type TemporaryFileInfo struct {
	OriginalFileId int             `json:"originalFileId"`
	FileId         int             `json:"fileId"`
	FileType       string          `json:"fileType"`
	Total          int             `json:"total"`
	HeaderRow      int             `json:"headerRow"`
	Fields         []*domain.Field `json:"fields"`
	// 编辑表错误,有错误不允许保存成校验文件
	// 行记录错误
	// 列类型有错误
	// 表整体错误
	ConvertTypeErrors []ConvertTypeError `json:"convertTypeErrors"`
}

func (f *TemporaryFileInfo) MatchFields(columns []string) []*domain.Field {
	mapFields := (domain.Fields)(f.Fields).ToMap()
	var result = make([]*domain.Field, 0)
	for _, c := range columns {
		if v, ok := mapFields[c]; ok {
			result = append(result, v)
		}
	}
	return result
}

func (f *TemporaryFileInfo) SetFile(file *domain.File) *TemporaryFileInfo {
	f.FileId = file.FileId
	f.FileType = file.FileType
	return f
}

func (f *TemporaryFileInfo) SetFields(fields []*domain.Field) *TemporaryFileInfo {
	// 保留原有字段的类型(底层拆分的时候类型会变掉,无法处理,此处做特殊处理)
	for i := range fields {
		for j := range f.Fields {
			if f.Fields[j].Name == fields[i].Name {
				if f.Fields[j].SQLType != fields[i].SQLType {
					fields[i].SQLType = f.Fields[j].SQLType
					break
				}
			}
		}
	}
	f.Fields = fields
	return f
}

func (f *TemporaryFileInfo) SetTotal(total int) *TemporaryFileInfo {
	f.Total = total
	return f
}

func (f *TemporaryFileInfo) SetHeaderRow(headerRow int) *TemporaryFileInfo {
	f.HeaderRow = headerRow
	return f
}

func (f *TemporaryFileInfo) AddConvertTypeError(e ConvertTypeError) *TemporaryFileInfo {
	f.RemoveConvertTypeError(e)
	f.addConvertTypeError(e)
	return f
}

func (f *TemporaryFileInfo) addConvertTypeError(e ConvertTypeError) *TemporaryFileInfo {
	f.RemoveConvertTypeError(e)
	f.ConvertTypeErrors = append(f.ConvertTypeErrors, e)
	return f
}

func (f *TemporaryFileInfo) RemoveConvertTypeError(e ConvertTypeError) *TemporaryFileInfo {
	var newErrors = make([]ConvertTypeError, 0)
	for _, item := range f.ConvertTypeErrors {
		if item.FieldName == e.FieldName {
			continue
		}
		newErrors = append(newErrors, item)
	}
	f.ConvertTypeErrors = newErrors
	return f
}

type ConvertTypeError struct {
	FieldName string `json:"fieldName"`
	ErrMsg    string `json:"errMsg"`
	ToType    string `json:"toType"`
}

type FileCacheService struct {
}

func (s *FileCacheService) Update(key string, file *domain.File, fields []*domain.Field, total int, option ...FileCacheOptionsFunc) (*TemporaryFileInfo, error) {
	options := NewFileCacheOptions(option...)
	ok, err := ZeroCoreRedis.Exists(key)
	var tmpFile = &TemporaryFileInfo{}
	if err != nil {
		return tmpFile, err
	}
	if !ok {
		tmpFile.SetFile(file).SetFields(fields).SetTotal(total)
		if options.HasSetHeaderRow {
			tmpFile.SetHeaderRow(options.HeaderRow)
		}
		return tmpFile, ZeroCoreRedis.Setex(key, json.MarshalToString(tmpFile), TemporaryFileExpire)
	}
	data, err := ZeroCoreRedis.Get(key)
	if err != nil {
		return nil, err
	}
	err = json.UnmarshalFromString(data, tmpFile)
	if err != nil {
		return nil, err
	}
	tmpFile.SetFields(fields)
	if options.HasSetHeaderRow {
		tmpFile.SetHeaderRow(options.HeaderRow)
	}

	for i := range options.AddConvertTypeErrors {
		tmpFile.AddConvertTypeError(options.AddConvertTypeErrors[i])
	}
	for i := range options.RemoveConvertTypeErrors {
		convertType := options.RemoveConvertTypeErrors[i]
		tmpFile.RemoveConvertTypeError(options.RemoveConvertTypeErrors[i])
		for j := range tmpFile.Fields {
			if tmpFile.Fields[j].Name == convertType.FieldName {
				tmpFile.Fields[j].SQLType = convertType.ToType
				break
			}
		}
	}

	err = ZeroCoreRedis.Setex(key, json.MarshalToString(tmpFile), TemporaryFileExpire)
	if err != nil {
		return nil, err
	}
	return tmpFile, err
}

func (s *FileCacheService) UpdateField(key string, file *domain.File, errors ...FileCacheOptionsFunc) (*TemporaryFileInfo, error) {
	ok, err := ZeroCoreRedis.Exists(key)
	var response = &TemporaryFileInfo{}
	if err != nil {
		return response, err
	}
	if !ok {
		return nil, fmt.Errorf("文件不存在")
	}
	data, err := ZeroCoreRedis.Get(key)
	if err != nil {
		return nil, err
	}
	err = json.UnmarshalFromString(data, response)
	if err != nil {
		return nil, err
	}

	options := NewFileCacheOptions(errors...)
	for i := range options.AddConvertTypeErrors {
		response.AddConvertTypeError(options.AddConvertTypeErrors[i])
	}
	for i := range options.RemoveConvertTypeErrors {
		convertType := options.RemoveConvertTypeErrors[i]
		response.RemoveConvertTypeError(options.RemoveConvertTypeErrors[i])
		for j := range response.Fields {
			if response.Fields[j].Name == convertType.FieldName {
				response.Fields[j].SQLType = convertType.ToType
				break
			}
		}
	}

	err = ZeroCoreRedis.Setex(key, json.MarshalToString(response), TemporaryFileExpire)
	if err != nil {
		return nil, err
	}
	return response, err
}

func (s *FileCacheService) Get(key string) (*TemporaryFileInfo, error) {
	var response = &TemporaryFileInfo{}
	ok, err := ZeroCoreRedis.Exists(key)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, fmt.Errorf("临时文件信息缓存不存在")
	}
	data, err := ZeroCoreRedis.Get(key)
	if err != nil {
		return nil, err
	}
	err = json.UnmarshalFromString(data, response)
	if err != nil {
		return nil, err
	}
	return response, nil
}

func NewFileCacheService() *FileCacheService {
	return &FileCacheService{}
}

type FileCacheOptions struct {
	//OriginalFileId          int
	RemoveConvertTypeErrors []ConvertTypeError
	AddConvertTypeErrors    []ConvertTypeError
	HeaderRow               int
	HasSetHeaderRow         bool
}

type FileCacheOptionsFunc func(o *FileCacheOptions)

func WithRemoveConvertTypeErrors(errors []ConvertTypeError) FileCacheOptionsFunc {
	return func(o *FileCacheOptions) {
		o.RemoveConvertTypeErrors = errors
	}
}

func WithAddConvertTypeErrors(errors []ConvertTypeError) FileCacheOptionsFunc {
	return func(o *FileCacheOptions) {
		o.AddConvertTypeErrors = errors
	}
}

func WithHeaderRow(headerRow int) FileCacheOptionsFunc {
	return func(o *FileCacheOptions) {
		o.HeaderRow = headerRow
		o.HasSetHeaderRow = true
	}
}

//func WithOriginalFileId(originalFileId int) FileCacheOptionsFunc {
//	return func(o *FileCacheOptions) {
//		o.OriginalFileId = originalFileId
//	}
//}

func NewFileCacheOptions(options ...FileCacheOptionsFunc) *FileCacheOptions {
	option := &FileCacheOptions{}
	for i := range options {
		options[i](option)
	}
	return option
}