package dingtalk

import (
	"encoding/json"
	"fmt"
	"net/url"
	"strings"
)

type iDingMsg interface {
	Marshaler() []byte
}

type atOption interface {
	apply(model *atModel)
}

type funcAtOption struct {
	f func(model *atModel)
}

func (fdo *funcAtOption) apply(do *atModel) {
	fdo.f(do)
}

func newFuncAtOption(f func(model *atModel)) *funcAtOption {
	return &funcAtOption{f: f}
}

func WithAtAll() atOption {
	return newFuncAtOption(func(o *atModel) {
		o.IsAtAll = true
	})
}

func WithAtMobiles(mobiles []string) atOption {
	return newFuncAtOption(func(o *atModel) {
		o.AtMobiles = mobiles
	})
}

func WithAtUserIds(userids []string) atOption {
	return newFuncAtOption(func(o *atModel) {
		o.AtUserIds = userids
	})
}

type textMsg struct {
	MsgType msgTypeType `json:"msgtype,omitempty"`
	Text    textModel   `json:"text,omitempty"`
	At      atModel     `json:"at,omitempty"`
}

func (t textMsg) Marshaler() []byte {
	b, _ := json.Marshal(t)
	return b
}

func NewTextMsg(content string, opts ...atOption) *textMsg {
	msg := &textMsg{MsgType: TEXT, Text: textModel{Content: content}}
	for _, opt := range opts {
		opt.apply(&msg.At)
	}
	return msg
}

type linkMsg struct {
	MsgType msgTypeType `json:"msgtype,omitempty"`
	Link    linkModel   `json:"link,omitempty"`
}

func (l linkMsg) Marshaler() []byte {
	b, _ := json.Marshal(l)
	return b
}

func NewLinkMsg(title, text, picUrl, msgUrl string, opts ...linkOption) *linkMsg {
	msg := &linkMsg{MsgType: LINK, Link: linkModel{
		Text:       text,
		Title:      title,
		PicUrl:     picUrl,
		MessageUrl: msgUrl,
	}}
	for _, opt := range opts {
		opt.apply(&msg.Link)
	}
	return msg
}

type markDownMsg struct {
	MsgType  msgTypeType   `json:"msgtype,omitempty"`
	Markdown markDownModel `json:"markdown,omitempty"`
	At       atModel       `json:"at,omitempty"`
}

func (m markDownMsg) Marshaler() []byte {
	b, _ := json.Marshal(m)
	return b
}

func NewDTMDMsg(title string, dtmdMap *dingMap, opts ...atOption) *markDownMsg {
	text := ""
	for _, v := range dtmdMap.l {
		text = text + "\n - " + fmt.Sprintf(dtmdFormat, v, dtmdMap.m[v])
	}
	return NewMarkDownMsg(title, text, opts...)
}

func NewMarkDownMsg(title string, text interface{}, opts ...atOption) *markDownMsg {

	msg := &markDownMsg{MsgType: MARKDOWN, Markdown: markDownModel{Title: title, Text: text.(string)}}
	for _, opt := range opts {
		opt.apply(&msg.At)
	}
	// markdown格式需要在文本内写入被at的人
	if len(msg.At.AtMobiles) > 0 {
		var atStr = "\n -"
		for _, mobile := range msg.At.AtMobiles {
			// 为@设置默认颜色
			atStr = fmt.Sprintf("<font color=#0089ff>%s @%s</font>", atStr, mobile)
		}
		msg.Markdown.Text = msg.Markdown.Text + atStr
	}

	if len(msg.At.AtUserIds) > 0 {
		var atStr = "\n -"
		for _, userid := range msg.At.AtUserIds {
			// 为@设置默认颜色
			atStr = fmt.Sprintf("<font color=#0089ff>%s @%s</font>", atStr, userid)
		}
		msg.Markdown.Text = msg.Markdown.Text + atStr
	}
	return msg
}

type actionCardOption interface {
	apply(model *actionCardModel)
}

type funcActionCardOption struct {
	f func(model *actionCardModel)
}

func (fdo *funcActionCardOption) apply(do *actionCardModel) {
	fdo.f(do)
}

func newFuncActionCardOption(f func(model *actionCardModel)) *funcActionCardOption {
	return &funcActionCardOption{f: f}
}

func WithCardBtnVertical() actionCardOption {
	return newFuncActionCardOption(func(o *actionCardModel) {
		o.BtnOrientation = vertical
	})
}

func WithCardSingleTitle(title string) actionCardOption {
	return newFuncActionCardOption(func(o *actionCardModel) {
		o.SingleTitle = title
	})
}

func WithCardSingleURL(url string) actionCardOption {
	return newFuncActionCardOption(func(o *actionCardModel) {
		o.SingleURL = url
	})
}

func WithCardBtns(btns []ActionCardMultiBtnModel) actionCardOption {
	return newFuncActionCardOption(func(o *actionCardModel) {
		o.Btns = btns
	})
}

type actionCardMsg struct {
	MsgType    msgTypeType     `json:"msgtype,omitempty"`
	ActionCard actionCardModel `json:"actionCard,omitempty"`
}

func (a actionCardMsg) Marshaler() []byte {
	if a.ActionCard.PcSlideOpen != nil {
		a.ActionCard.SingleURL = fmt.Sprintf(pcSlideOpenUrlFormat, url.QueryEscape(a.ActionCard.SingleURL), a.ActionCard.PcSlideOpen)
	}
	for i, v := range a.ActionCard.Btns {
		if v.PcSlideOpen != nil {
			a.ActionCard.Btns[i].ActionURL = fmt.Sprintf(pcSlideOpenUrlFormat, url.QueryEscape(v.ActionURL), v.PcSlideOpen)
		}
	}
	b, _ := json.Marshal(a)
	return b
}

func NewActionCardMsg(title, text string, opts ...actionCardOption) *actionCardMsg {
	card := &actionCardMsg{MsgType: ACTION_CARD, ActionCard: actionCardModel{
		Title:          title,
		Text:           text,
		BtnOrientation: horizontal,
	}}
	for _, opt := range opts {
		opt.apply(&card.ActionCard)
	}
	return card
}

type feedCardMsg struct {
	MsgType  msgTypeType   `json:"msgtype,omitempty"`
	FeedCard feedCardModel `json:"feedCard,omitempty"`
}

func (f feedCardMsg) Marshaler() []byte {
	for i, v := range f.FeedCard.Links {
		if v.PcSlideOpen != nil {
			f.FeedCard.Links[i].MessageURL = fmt.Sprintf(pcSlideOpenUrlFormat, url.QueryEscape(f.FeedCard.Links[i].MessageURL), v.PcSlideOpen)
		}
	}
	b, _ := json.Marshal(f)
	return b
}

func NewFeedCardMsg(feedCard []FeedCardLinkModel) *feedCardMsg {
	return &feedCardMsg{MsgType: FEED_CARD, FeedCard: feedCardModel{Links: feedCard}}
}

type MarkType string

// 有序map
type dingMap struct {
	m map[string]MarkType
	l []string
}

func DingMap() *dingMap {
	return &dingMap{m: make(map[string]MarkType), l: make([]string, 0, 0)}
}

func (d *dingMap) Set(val string, t MarkType) *dingMap {
	d.l = append(d.l, val)
	d.m[val] = t
	return d
}

func (d *dingMap) Remove(val string) {
	if _, ok := d.m[val]; ok {
		for i, v := range d.l {
			if v == val {
				d.l = append(d.l[:i], d.l[i+1:]...)
				break
			}
		}
		delete(d.m, val)
	}
}

func (d *dingMap) Slice() []string {
	resList := make([]string, 0, len(d.l))
	for _, val := range d.l {
		content := d.formatVal(val, d.m[val])
		resList = append(resList, content)
	}
	return resList
}

func (d *dingMap) formatVal(val string, t MarkType) (res string) {
	var ok bool
	if res, ok = hMap[t]; ok {
		vl := strings.Split(val, formatSpliter)
		if len(vl) == 3 {
			res = fmt.Sprintf(res, vl[1])
			res = vl[0] + res + vl[2]
		} else {
			res = fmt.Sprintf(res, val)
		}
	} else {
		res = val
	}
	if !strings.HasPrefix(res, "- ") && !strings.HasPrefix(res, "#") {
		res = "- " + res
	}
	return
}

type responseMsg struct {
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
}
