package core

import (
	"dbweb/lib/bill"
	"encoding/json"
	"fmt"
	"time"

	"github.com/linlexing/dbx/ddb"

	log "github.com/Sirupsen/logrus"
	"github.com/linlexing/dbx/common"
	"github.com/linlexing/dbx/render"

	"github.com/jmoiron/sqlx"
)

//ReadDumpConfig 读取参数
func ReadDumpConfig(fileName string) (*DumpConfig, error) {
	db, err := sqlx.Open("sqlite3", fileName)
	if err != nil {
		return nil, err
	}
	defer db.Close()
	var out string
	strSQL := "select value from DUMP_CONFIG where name='config'"
	if err = db.QueryRow(strSQL).Scan(&out); err != nil {
		err = common.NewSQLError(err, strSQL)
		return nil, err
	}
	r := &DumpConfig{}
	if err = json.Unmarshal([]byte(out), r); err != nil {
		return nil, err
	}
	return r, nil
}

//导入一个单据,在一个单独的事务中运行，因为多个单据，可能在不同的db上
func (b *DumpBill) restore(db, dumpDB ddb.DB, user *User,
	progressFunc func(message string)) error {
	startTime := time.Now()
	var icount int64
	return ddb.RunAtTx(LoadOuterDB(db, b.DBName), func(billDB ddb.Txer) (err error) {

		//运行before
		if len(b.BeforeSQL) > 0 {
			progressFunc("\texecute before sql script")
			strSQL, err := render.RenderSQL(b.BeforeSQL, map[string]interface{}{"User": user})
			if err != nil {
				return err
			}
			if _, err := billDB.Exec(strSQL); err != nil {
				return err
			}
		}

		srcBill, err := bill.OpenBill(billDB, b.Tables[0], b.Tables[1:]...)
		if err != nil {
			return err
		}
		dumpBill, err := bill.OpenBill(dumpDB, b.Tables[0], b.Tables[1:]...)
		if err != nil {
			return err
		}
		rows, err := dumpBill.Query("")
		if err != nil {
			return err
		}
		for rows.Next() {
			record, err := rows.Scan()
			if err != nil {
				return err
			}
			if b.ActionExists == EXISTS_SKIP {
				ok, err := srcBill.Main.KeyExists(srcBill.Main.KeyValues(record.Main)...)
				if err != nil {
					return err
				}
				if ok {
					continue
				}
				err = srcBill.Insert(record)
				if err != nil {
					return err
				}

			} else if b.ActionExists == EXISTS_OVERWRITE {
				if err = srcBill.Save(record); err != nil {
					LOG.Error("save", err.Error())
					return err
				}
			} else {
				LOG.Panic("not imple")
			}
			icount++

		}
		if err = rows.Err(); err != nil {
			return err
		}

		//运行after
		if len(b.AfterSQL) > 0 {
			progressFunc("\texecute after sql script")
			strSQL, err := render.RenderSQL(b.AfterSQL, map[string]interface{}{"User": user})
			if err != nil {
				return err
			}
			if _, err := billDB.Exec(strSQL); err != nil {
				return err
			}
		}
		progressFunc(fmt.Sprintf("%d %s records imported at %.2fs,", icount, b.Name, time.Since(startTime).Seconds()))
		return nil
	})

}

//RestoreFile 从文件中恢复
func RestoreFile(fileName, dumpName string, billNames []string, db ddb.DB, user *User, progressFunc func(message string)) (err error) {
	dumpConfig, err := ReadDumpConfig(fileName)
	if err != nil {
		return
	}
	if err = dumpConfig.Check(dumpName); err != nil {
		return
	}
	dumpConfig.Param.FilterBill(billNames)
	dumpDb, err := sqlx.Open("sqlite3", fileName)
	if err != nil {
		return
	}
	defer func() {
		//如果出错，则关闭临时文件
		e := dumpDb.Close()
		if err == nil {
			err = e
		}
	}()
	//run before script
	if len(dumpConfig.Param.BeforeSQL) > 0 {
		progressFunc("execute before sql script")
		strSQL, err := render.RenderSQL(dumpConfig.Param.BeforeSQL, map[string]interface{}{"User": user})
		if err != nil {
			return err
		}
		if _, err := db.Exec(strSQL); err != nil {
			LOG.Panic(err)
		}
	}
	//下面开始导入各个单据
	for _, v := range dumpConfig.Param.Bills {
		LOG.WithFields(log.Fields{
			"bill":  v.Name,
			"count": v.RowCount,
		}).Info("start import...")
		if err = v.restore(db, dumpDb, user, progressFunc); err != nil {
			LOG.WithFields(log.Fields{
				"bill":  v.Name,
				"count": v.RowCount,
				"err":   err.Error(),
			}).Panic(err)
		}

	}
	//run after script
	if len(dumpConfig.Param.AfterSQL) > 0 {
		progressFunc("execute after sql script")
		strSQL, err := render.RenderSQL(dumpConfig.Param.AfterSQL, map[string]interface{}{"User": user})
		if err != nil {

			return err
		}
		if _, err := db.Exec(strSQL); err != nil {
			LOG.Panic(err)
		}
	}
	return nil
}
