package core

import (
	"dbweb/lib/bill"
	"dbweb/lib/model"
	"dbweb/lib/zip"
	"fmt"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/linlexing/dbx/ddb"
	"github.com/linlexing/dbx/render"

	zipw "archive/zip"
	"io/ioutil"
	"os"

	"gopkg.in/yaml.v2"
)

var (
	institutionModelList = map[string]*institutionModel{}
)

//MarshalInstitutioner 是制度包导出时转换用
type MarshalInstitutioner interface {
	MarshalInstitutioner(rec *bill.Record) ([]byte, error)
}

//UnmarshalInstitutioner 是制度包导入时转换用
type UnmarshalInstitutioner interface {
	UnmarshalInstitutioner([]byte) (interface{}, error)
}

type institutionModel struct {
	dept      string
	modelName string
	where     string
	before    string
	after     string
}
type institutionConfigModel struct {
	Model    string
	Before   string
	After    string
	Ver      int64
	RowCount int64
}
type institutionConfig struct {
	Models   []institutionConfigModel
	LastTime time.Time
}

//DumpInstitutionTaskParam 是dump任务的参数
type DumpInstitutionTaskParam struct {
	DumpType string
}

//RegisterInstitutionModel 注册一个制度model
func RegisterInstitutionModel(dept, modelName, where, beforeSQL, afterSQL string) {
	institutionModelList[modelName] = &institutionModel{
		dept:      dept,
		modelName: modelName,
		where:     where,
		before:    beforeSQL,
		after:     afterSQL,
	}
}

//InstitutionModelNames 返回当前制度包的Model名称列表,需要传入一个部门代码，返回当前及上级的制度
func InstitutionModelNames(dept *Dept) []string {
	rev := []string{}
	for k, v := range institutionModelList {
		if dept.IsParentOrSelf(v.dept) {
			rev = append(rev, k)
		}
	}
	sort.Strings(rev)
	return rev
}

//buildInstitutionRecordFileName 根据主键值构建文件名，采用如下方式
//主键1,主键2
func buildInstitutionRecordFileName(b *bill.Bill, rec *bill.Record) string {
	rev := []string{}
	for _, k := range b.Main.PrimaryKeys {
		rev = append(rev, b.Main.ColumnByName(k).Type.ToString(rec.Main[k]))
	}
	return strings.Join(rev, ",")
}

//DumpInstitution 导出制度包
func DumpInstitution(t *TaskRun) string {
	cfg := institutionConfig{
		Models: []institutionConfigModel{},
	}
	var err error
	dumpInstitution := t.Param.(DumpInstitutionTaskParam).DumpType == "institution"
	institutionPath := DataFile("institution")
	var zipW *zipw.Writer
	if !dumpInstitution {
		if institutionPath, err = ioutil.TempDir(TempDir(), "dump"); err != nil {
			LOG.Panic(err)
		}
		zipW = t.NeedZip()
	}
	if err := os.RemoveAll(institutionPath); err != nil {
		LOG.Panic(err)
	}
	for _, k := range InstitutionModelNames(t.User.Dept) {
		v := institutionModelList[k]
		outPath := filepath.Join(institutionPath, k)
		if err := os.MkdirAll(outPath, os.ModePerm); err != nil {
			LOG.Panic(err)
		}
		strWhere, err := render.RenderSQL(v.where, map[string]interface{}{
			"User": t.User,
		})
		if err != nil {
			LOG.Panic(err)
		}
		md := LoadModel(v.modelName)
		rows, err := md.Query(strWhere)
		if err != nil {
			LOG.Panic(err)
		}
		defer rows.Close()
		var icount int64
		for rows.Next() {
			icount++
			out, rec, err := rows.ScanVal()
			if err != nil {
				LOG.Panic(err)
			}
			//如果有定制的制度包导出接口，则调用定制的，否则用yaml
			var bys []byte
			if mi, ok := out.(MarshalInstitutioner); ok {
				bys, err = mi.MarshalInstitutioner(rec)
			} else {
				bys, err = yaml.Marshal(out)
			}
			if err != nil {
				LOG.Panic(err)
			}
			fileName := filepath.Join(outPath, buildInstitutionRecordFileName(md.Bill(), rec)+".yaml")
			if err := ioutil.WriteFile(fileName, bys, os.ModePerm); err != nil {
				LOG.Panic(err)
			}
		}
		cfg.Models = append(cfg.Models, institutionConfigModel{
			Model:    v.modelName,
			Before:   v.before,
			After:    v.after,
			Ver:      ModelVersion(v.modelName),
			RowCount: icount,
		})
	}
	cfg.LastTime = time.Now()
	fileName := filepath.Join(institutionPath, "confg.yaml")
	bys, err := yaml.Marshal(cfg)
	if err != nil {
		LOG.Panic(err)
	}
	if err := ioutil.WriteFile(fileName, bys, os.ModePerm); err != nil {
		LOG.Panic(err)
	}
	//将数据压缩成zip文件
	if dumpInstitution {
		zipFileName := DataFile("institution.zip")
		if err = zip.ZipDirectory(institutionPath, zipFileName); err != nil {
			LOG.Panic(err)
		}
		if err = os.Chtimes(zipFileName, cfg.LastTime, cfg.LastTime); err != nil {
			LOG.Panic(err)
		}
		//设置配置文件中的applytime
		lstTime := ReadPatchTime()
		lstTime.InstitutionApplyTime = cfg.LastTime
		if err = lstTime.Save(); err != nil {
			LOG.Panic(err)
		}
	} else {
		if err = zip.ZipDir(institutionPath, institutionPath, zipW); err != nil {
			LOG.Panic(err)
		}
		zipW.Close()
		//移除临时目录
		if err = os.RemoveAll(institutionPath); err != nil {
			LOG.Panic(err)
		}
	}
	return fmt.Sprintf("成功导出%d个Model", len(institutionModelList))
}

//ReadInstitutionConfigTime 返回制度包中最后时间
func ReadInstitutionConfigTime(institutionPath string) (rev time.Time, err error) {
	cfgFileName := filepath.Join(institutionPath, "confg.yaml")
	var bys []byte
	bys, err = ioutil.ReadFile(cfgFileName)
	if err != nil {
		return
	}
	var cfg institutionConfig
	if err = yaml.Unmarshal(bys, &cfg); err != nil {
		return
	}

	rev = cfg.LastTime
	return
}

//RestoreInstitution 从一个目录中导入制度包内容
func RestoreInstitution(institutionPath string, metadb ddb.TxDB, tm time.Time) (apply bool,
	cfgTime time.Time, err error) {
	cfgFileName := filepath.Join(institutionPath, "confg.yaml")
	var bys []byte
	bys, err = ioutil.ReadFile(cfgFileName)
	if os.IsNotExist(err) {
		err = nil
		return
	}
	if err != nil {
		return
	}
	var cfg institutionConfig
	if err = yaml.Unmarshal(bys, &cfg); err != nil {
		return
	}
	//如果传入的时间比配置文件中早，说明不需更新
	if !cfg.LastTime.After(tm) {
		return
	}
	//在事务中完成
	err = ddb.RunAtTx(metadb, func(tx ddb.Txer) (err error) {
		for _, one := range cfg.Models {
			var md *model.Model
			interMD := mustFindModel(one.Model)
			if one.Ver != interMD.dbver {
				err = fmt.Errorf("model %s ver %d <> file ver:%d", one.Model, interMD.dbver, one.Ver)
			}
			if md, err = interMD.new(tx); err != nil {
				return
			}
			var fileList []os.FileInfo
			fileList, err = ioutil.ReadDir(filepath.Join(institutionPath, one.Model))
			//如果目录不存在，则跳过
			if os.IsNotExist(err) {
				err = nil
				continue
			}
			if err != nil {
				return
			}
			//运行before
			if len(one.Before) > 0 {
				if _, err = tx.Exec(one.Before); err != nil {
					return
				}
			}
			for _, afile := range fileList {
				var bys []byte
				recFileName := filepath.Join(institutionPath, one.Model,
					afile.Name())
				if bys, err = ioutil.ReadFile(recFileName); err != nil {
					return
				}
				//如果有定制的导入制度接口，则调用定制的，否则是yaml
				val := reflect.New(reflect.TypeOf(interMD.mtype)).Interface()
				if umi, ok := val.(UnmarshalInstitutioner); ok {
					if val, err = umi.UnmarshalInstitutioner(bys); err != nil {
						LOG.Println(recFileName, err)
						return
					}
				} else {
					if err = yaml.Unmarshal(bys, val); err != nil {
						LOG.Println(recFileName, err)
						return
					}
				}
				//保存到数据库中
				if err = md.Set(val); err != nil {
					return
				}
				apply = true
			}
			//运行after
			if len(one.After) > 0 {
				if _, err = tx.Exec(one.After); err != nil {
					return
				}
			}
			LOG.Printf("restore model %s %d rows", one.Model, one.RowCount)
		}
		return
	})
	if err != nil {
		return
	}

	cfgTime = cfg.LastTime
	return
}
