package core

import (
	"dbweb/lib/bill"
	"dbweb/lib/lsession"
	"dbweb/lib/render"
	"dbweb/lib/uri"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/linlexing/dbx/ddb"

	log "github.com/Sirupsen/logrus"

	"github.com/patrickmn/go-cache"
)

var NotFoundElement = errors.New("can't find element")
var regNumberLabel, _ = regexp.Compile(`^\d{2}\.`)
var elementCache = cache.New(time.Second*10, time.Second*3)

//对应数据库中element表的记录
type Element struct {
	Name       string
	Public     bool
	Label      string
	Category   string
	Controller *Controller
	Params     []byte
	Owner      int64
	UserName   string
	HelpText   string
}
type ElementHandleArgs struct {
	Element  *Element
	Env      string //当前运行的环境，一般有dev runtime
	User     *User
	Render   *render.Render
	Req      *http.Request
	Res      http.ResponseWriter
	Cache    *cache.Cache
	PKS      []string
	LSession *lsession.Session
	DB       ddb.TxDB
	More     map[string]interface{}
}

func RemoveElementFromCache(eleName string) {
	elementCache.Delete(eleName)
}

//LoadElement 调入一个工作元素，有缓存
func LoadElement(db ddb.DB, eleName string) (result *Element) {
	if v, ok := elementCache.Get(eleName); ok {
		elementCache.Set(eleName, v, 0)
		return v.(*Element)
	}
	md := LoadModel(ModelElementName)
	ele := modelElement{}
	if err := md.Get(&ele, eleName); err != nil {
		LOG.Panic(err, eleName)
	}
	ctrl, ok := controllers[ele.Controller]
	if !ok {
		LOG.Panic(fmt.Errorf("can't found the controller:%s", ele.Controller))
	}
	result = &Element{
		Name:       ele.Name,
		Category:   ele.Category,
		Label:      ele.Label,
		Params:     []byte(ele.Params),
		Public:     ele.Pub > 0,
		Controller: ctrl,
		Owner:      ele.Owner,
		UserName:   ele.UserName,
		HelpText:   ele.HelpText,
	}

	elementCache.Set(eleName, result, 0)
	return result
}
func (e *Element) DisplayLabel() string {
	r := e.Label
	if e.Label == "" {
		r = e.Name
	}
	if regNumberLabel.MatchString(r) {
		return r[3:]
	}
	return r
}
func (e *Element) ClearCategory() []string {
	r := strings.Split(e.Category, "/")
	for i, _ := range r {
		if regNumberLabel.MatchString(r[i]) {
			r[i] = r[i][3:]
		}
	}
	return r
}

//执行一个工作元素的http请求
func (e *Element) Handle(args *ElementHandleArgs) {
	if !e.Public && !args.auth() {
		LOG.WithFields(log.Fields{
			"url":  args.Req.URL.String(),
			"user": args.User.Name,
			"sid":  args.LSession.SessionIDString(),
			"csid": args.LSession.CrypterSessionID(),
		}).Warn("redirect")
		args.Redirect("/login?next=" + url.QueryEscape(args.Req.URL.String()))
		return
	}
	e.Controller.Handle(args)
}
func (e *ElementHandleArgs) DBName() string {
	if !e.Element.Controller.Bill {
		LOG.Panic("not is bill")
	}
	if evt, ok := e.Element.Controller.AppController.(GetDBNameEvent); ok {
		return evt.DBName(e)
	} else {
		return e.Element.Controller.DBName()
	}
}

func (e *ElementHandleArgs) Bill() *bill.Bill {
	if !e.Element.Controller.Bill {
		LOG.Panic("not is bill")
	}
	if evt, ok := e.Element.Controller.AppController.(BuildBillEvent); ok {
		return evt.Bill(e)
	} else {
		return e.Element.Controller.NewBill(e.DB)
	}
}
func (e *ElementHandleArgs) auth() bool {
	user, ok := e.LSession.Get("$user").(*User)
	if !ok || user.Name == "" {
		return false
	}
	if !user.IsSign(e.Req.URL) {
		LOG.WithFields(log.Fields{
			"url":  e.Req.URL.String(),
			"user": user.Name,
		}).Warn("not sign")
		return false
	}
	return true
}
func (a *ElementHandleArgs) RenderError(mes string) {
	a.More["Message"] = mes
	a.Render.HTML(200, "_error", a, "_layout/blank")
}
func (e *ElementHandleArgs) ElementHasHelp(eleName string) bool {
	ele := LoadElement(e.DB, eleName)
	return len(ele.HelpText) > 0 ||
		e.Render.Template().Lookup("_help/"+ele.Controller.Name) != nil
}
func (a *ElementHandleArgs) RenderTimeout() {
	a.RenderMessage("此操作已经超时，请关闭窗口后重新打开")
}
func (a *ElementHandleArgs) RenderMessage(mes string) {
	a.More["Message"] = mes
	a.Render.HTML(200, "_info", a, "_layout/blank")
}
func (a *ElementHandleArgs) GotoMessage(mes string) {
	a.More["Message"] = mes
	a.Render.HTML(200, "_message", a, a.Element.Controller.Layout)
}
func (a *ElementHandleArgs) Redirect(nav string) {
	a.Render.Redirect(nav)
}
func (a *ElementHandleArgs) HTML(layout ...string) {
	if len(a.Element.Controller.Layout) > 0 {
		a.Render.HTML(200, a.Element.Controller.Name, a, a.Element.Controller.Layout)
	} else if len(layout) > 0 {
		a.Render.HTML(200, a.Element.Controller.Name, a, layout[0])
	} else if a.Element.Public { //如果是公共的，则不能有标题，否则会出错，因为没有登录的用户
		a.Render.HTML(200, a.Element.Controller.Name, a, "_layout/blank")
	} else {
		a.Render.HTML(200, a.Element.Controller.Name, a)
	}
}
func (b *ElementHandleArgs) ProjectLabel() string {
	l, ok := b.Cache.Get("ProjectLabel")
	if !ok {
		l = GetOption(b.DB, "ProjectLabel")
		b.Cache.Set("ProjectLabel", l, 0)
	}
	return l.(string)
}
func NewElementHandleArgs(ele *Element, res http.ResponseWriter, req *http.Request,
	db ddb.TxDB, r *render.Render, c *cache.Cache, ls *lsession.Session) *ElementHandleArgs {
	var user *User
	if vu := ls.Get("$user"); vu == nil {
		user = &User{}
	} else {
		user = vu.(*User)
	}
	pks := []string{}

	for _, str := range strings.Split(req.URL.Path, "/")[2:] {
		if s, err := url.QueryUnescape(str); err != nil {
			LOG.Panic(err)
		} else {
			pks = append(pks, s)

		}
		//pks = append(pks, str)
	}

	return &ElementHandleArgs{
		Element: ele,
		User:    user,
		Render:  r,
		Req:     req,
		Res:     res,
		Cache:   c,
		DB:      db,
		//Session:  s,
		LSession: ls,
		More:     map[string]interface{}{},
		PKS:      pks,
	}
}
func ElementHandle(w http.ResponseWriter, r *http.Request) {
	st := time.Now()
	db := DB
	ls := lsession.NewSession(r, w)
	paths := strings.Split(r.URL.Path, "/")
	eleName := paths[1]
	ele := LoadElement(db, eleName)
	rnd := render.NewRender(w, r)
	args := NewElementHandleArgs(ele, w, r, db, rnd, GlobalCache, ls)
	ele.Handle(args)
	if time.Since(st).Seconds() > 3 {
		LOG.WithFields(log.Fields{
			"url":  r.URL.String(),
			"used": time.Since(st).String(),
		}).Info("process")
	}
}

//返回一个正确编码的element的url
func ElementUrl(eleName string, pks ...string) string {
	//elename不能编码，否则会出错
	ustr := "/" + eleName
	if strings.HasSuffix(eleName, "/") {
		LOG.Panic(fmt.Errorf("ele:%s 含有斜杠", eleName))
	}
	for _, v := range pks {
		//先用QueryEscape将/ ? 等特殊符号转化成%2f等符号，然后用EncodeUriComponent
		//再次编码，将特殊符号双次编码，虽然浪费一点空间，但是不会出错
		/*交叉汇总时，主键可以为空
			if len(v) == 0 {
			LOG.Panic(fmt.Errorf("主键%d不能为空", i))
		}*/
		ustr += "/" + uri.EncodeUriComponent(url.QueryEscape(v))
	}
	return ustr
}
