package core

import (
	"archive/zip"
	"dbweb/lib/lsession"
	"encoding/base64"
	"errors"
	"fmt"
	"html"
	"net/http"
	"os"
	"path/filepath"
	"runtime/debug"
	"time"

	"github.com/linlexing/dbx/ddb"

	log "github.com/Sirupsen/logrus"

	"github.com/pborman/uuid"
	"gopkg.in/yaml.v2"
)

//后台运行的任务
type TaskRun struct {
	Db            ddb.TxDB
	id            string
	zipFile       *os.File
	zipWriter     *zip.Writer
	maxProgressNo uint64
	Name          string
	User          *User
	LSession      *lsession.Session
	Req           *http.Request
	ClientIP      string
	Param         interface{}
	Func          func(t *TaskRun) string
}

func (t *TaskRun) ID() string {
	return t.id
}
func (t *TaskRun) callFunc() (result string, err error) {
	defer func() {
		if e := recover(); e != nil {
			var strMessage string
			switch tv := e.(type) {
			case error:
				strMessage = tv.Error()
			case string:
				strMessage = tv
			case []byte:
				strMessage = string(tv)
			case *log.Entry:
				strMessage = tv.Message
			default:
				strMessage = fmt.Sprintf("%T\n%s", e, e)
			}
			//加上调用堆栈
			strMessage += "\n" + string(debug.Stack())
			err = errors.New(strMessage)
		}
	}()
	result = t.Func(t)
	if t.zipFile != nil {
		if err = t.zipFile.Close(); err != nil {
			return
		}
	}
	return
}
func (t *TaskRun) NeedZip() *zip.Writer {
	var err error
	fileName := DataFile(filepath.Join("taskfiles", t.id+".zip"))
	if err = os.MkdirAll(filepath.Dir(fileName), os.ModeDir); err != nil {
		LOG.Panic(err)
	}
	LOG.Printf("create zip file %s", fileName)
	// Create a buffer to write our archive to.
	t.zipFile, err = os.Create(fileName)
	if err != nil {
		LOG.Panic(err)
	}

	// Create a new zip archive.
	return zip.NewWriter(t.zipFile)
}

//AddProgress 增加一个进度消息
func (t *TaskRun) AddProgress(content string) {
	t.maxProgressNo++
	strSQL := "insert into taskprogress(taskid,rowno,logtime,content)values(?,?,?,?)"

	if _, err := t.Db.Exec(strSQL, t.id, t.maxProgressNo, time.Now(), content); err != nil {
		LOG.Panic(err)
	}
}

//AddProgressf 带格式的进度消息
func (t *TaskRun) AddProgressf(f string, vals ...interface{}) {
	t.AddProgress(fmt.Sprintf(f, vals...))
}

//同步运行
func (t *TaskRun) run() {
	TaskWaitGroup.Add(1)
	defer TaskWaitGroup.Done()
	//增加任务的信息
	strSql := "insert into task(id,name,status,executor,exetime,exedept,exeip,params)values(?,?,?,?,?,?,?,?)"
	var bys []byte
	var err error
	if t.Param != nil {
		bys, err = yaml.Marshal(t.Param)
		if err != nil {
			LOG.Panic(err)
		}

	}
	if _, err = t.Db.Exec(strSql,
		t.id, t.Name,
		"running",
		t.User.Name,
		time.Now(),
		t.User.Dept.Code,
		t.ClientIP,
		string(bys)); err != nil {
		LOG.Panic(err)
	}
	strSql = "update task set status=?,result=?,completedtime=? where id=?"
	var status, result string

	str, err := t.callFunc()
	if err != nil {
		status = "error"
		result = html.EscapeString(err.Error())
	} else {
		status = "completed"
		result = str
	}

	if sr, err := t.Db.Exec(strSql,
		status,
		result,
		time.Now(),
		t.id); err != nil {
		LOG.Panic(err)

	} else if ic, err := sr.RowsAffected(); err != nil {
		LOG.Panic(err)
	} else if ic != 1 {
		LOG.Panic(fmt.Errorf("can't found the task:%s", t.id))
	}
}
func (t *TaskRun) Run() {
	t.id = base64.RawURLEncoding.EncodeToString(uuid.NewUUID())
	t.run()
}

//异步运行，需要检测返回值
func (t *TaskRun) GoRun() error {
	//一个用户只能执行一个相同名称的任务
	//应该是重复点击
	rows, err := t.Db.Query("select id,params from task where "+
		"name=? and status=? and executor=? and exedept=?",
		t.Name, "running", t.User.Name, t.User.Dept.Code)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var id, params string
		if err := rows.Scan(&id, &params); err != nil {
			return err
		}
		bys, err := yaml.Marshal(t.Param)
		if err != nil {
			return err
		}
		if params == string(bys) {
			return fmt.Errorf("你已经有一个相同的任务 <a href='%s' target='_blank'>%s</a> 在执行，请等待其完成后再提交！",
				t.User.Sign("/browsetask/"+id), t.Name)
		}
	}
	//id需要作为文件名，所以要用url
	//需要马上生成，否则有可能生成稍晚，造成id取不到
	t.id = base64.RawURLEncoding.EncodeToString(uuid.NewUUID())
	go t.run()
	return nil
}
