/**
 @author: xiaopengzhu
 @date: 2022/7/21
 @description: 审批service
**/

package v1

import (
	"bytes"
	"context"
	"encoding/json"
	"time"

	"github.com/mitchellh/mapstructure"

	"github.com/pingcap-inc/pchub/internal/apiserver/store"
	modelv1 "github.com/pingcap-inc/pchub/model/v1"
	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/log"
)

type ApprovalSrv interface {
	CreateApproval(c context.Context, user *modelv1.User, body modelv1.ApprovalBody) error
	GetAllApproval(c context.Context, authingId string, params map[string]string) (*modelv1.Page, error)
	GetApprovalFromLark(c context.Context, instanceCode string) (*modelv1.ApprovalInLark, error)
	GetApprovalByInstanceCode(c context.Context, instanceCode string) (*modelv1.Approval, error)
	Subscribe(c context.Context)
}

type approvalService struct {
	store store.Factory
}

var _ ApprovalSrv = (*approvalService)(nil)

func newApproval(str *service) *approvalService {
	return &approvalService{store: str.store}
}

// CreateApproval create approval instance in lark.
func (s *approvalService) CreateApproval(c context.Context, user *modelv1.User, body modelv1.ApprovalBody) error {
	form, msg, err := s.parseApprovalForm(c, user, body.ApprovalType, body.Form)
	if err != nil {
		return errors.Wrap(err, "Error when parse approval form")
	}
	reqBody := modelv1.LarkCreateApprovalBody{
		ApprovalCode: modelv1.ApprovalCodeMap[body.ApprovalType],
		Form:         form,
		UserId:       user.LarkUserID,
		OpenId:       user.LarkOpenID,
	}
	reqb, err := json.Marshal(&reqBody)
	if err != nil {
		log.L(c).Error(err.Error())

		return errors.Wrap(err, "Error when marshal lark create approval body")
	}
	commonService := &commonService{store: s.store}
	approvalJson, err := commonService.GetDataByReqLarkAPI(c, "POST", LarkOpenAPIBase+"/approval/v4/instances", bytes.NewReader(reqb), nil)
	if err != nil {
		return errors.Wrap(err, "failed to create approval instance in lark")
	}
	res := modelv1.LarkCreateApprovalRes{}

	if err = json.Unmarshal(approvalJson, &res); err != nil {
		log.L(c).Errorf("Error when unmarshal approval instance data: %s", err.Error())

		return errors.Wrap(err, "Error when unmarshal lark approval data")
	}

	// save into db
	bodyShadow, _ := json.Marshal(body)
	var widthMid struct {
		ManagementID string `json:"managementId" mapstructure:"managementId"`
	}
	_ = mapstructure.Decode(body.Form.(map[string]interface{}), &widthMid)
	approval := &modelv1.Approval{
		ApprovalType:      body.ApprovalType,
		Status:            modelv1.ApprStPending,
		InstanceCode:      res.InstanceCode,
		Luid:              user.LarkUserID,
		CreatedAt:         time.Now(),
		RequestBodyShadow: string(bodyShadow),
		ManagementID:      widthMid.ManagementID,
	}

	_, err = s.store.Approval().Create(c, approval)
	if err != nil {
		log.L(c).Error(err.Error())

		return errors.Wrap(err, "approval save to db failed")
	}

	go s.SendApprovalSuccMsg(c, msg(res.InstanceCode))

	return nil
}

type ApprovalAction struct {
	ActionName string `json:"action_name"`
	URL        string `json:"url"`
	AndroidURL string `json:"android_url"`
	IOSURL     string `json:"ios_url"`
	PCURL      string `json:"pc_url"`
}

type I18nResource struct {
	Locale    string            `json:"locale"`
	IsDefault bool              `json:"is_default"`
	Texts     map[string]string `json:"texts"`
}

type SummaryIt struct {
	Summary string `json:"summary"`
}

type ApprMsgContent struct {
	Summaries []SummaryIt `json:"summaries"`
}

type ApprovalSuccMsg struct {
	UUID            string           `json:"uuid"`
	TemplateID      string           `json:"template_id"`
	UserID          string           `json:"user_id"`
	TitleUserIDType string           `json:"title_user_id_type"`
	OpenID          string           `json:"open_id"`
	ApprovalName    string           `json:"approval_name"`
	Note            string           `json:"note"`
	Actions         []ApprovalAction `json:"actions"`
	I18nResources   []I18nResource   `json:"i18n_resources"`
	Content         ApprMsgContent   `json:"content"`
}

// SendApprovalSuccMsg send approval success message to user in lark.
func (s *approvalService) SendApprovalSuccMsg(c context.Context, msg *ApprovalSuccMsg) {
	log.L(c).Info("Send approval success message to user")

	reqb, err := json.Marshal(&msg)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}

	commonService := &commonService{store: s.store}
	_, err = commonService.GetDataByReqLarkAPI(c, "POST", "https://www.feishu.cn/approval/openapi/v1/message/send", bytes.NewReader(reqb), nil)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}
}

// GetAllApproval 获取user相关所有审批信息.
func (s *approvalService) GetAllApproval(c context.Context, authingId string, params map[string]string) (*modelv1.Page, error) {
	user, err := s.store.User().GetByAnyID(c, authingId)
	if err != nil {
		log.L(c).Error(err.Error())

		return nil, errors.Wrap(err, "Error when GetByAnyID from db")
	}
	params["luid"] = user.LarkUserID

	pageResults, err := s.store.Approval().GetsByPage(c, params)
	if err != nil {
		log.L(c).Error(errors.Wrap(err, "Error when GetsByPage from db").Error())

		return nil, err
	}

	return pageResults, nil
}

// GetApprovalByCode 获取单个审批详情.
func (s *approvalService) GetApprovalFromLark(c context.Context, approvalCode string) (*modelv1.ApprovalInLark, error) {
	commonService := &commonService{store: s.store}
	approvalJson, err := commonService.GetDataByReqLarkAPI(c, "GET", LarkOpenAPIBase+"/approval/v4/instances/"+approvalCode, nil, nil)
	if err != nil {
		return nil, errors.Wrap(err, "feishu get approval instance failed")
	}
	approvalInLark := &modelv1.ApprovalInLark{}

	if err = json.Unmarshal(approvalJson, approvalInLark); err != nil {
		log.L(c).Error(err.Error())

		return nil, errors.Wrap(err, "Error when unmarshal lark approval instance data")
	}

	var formList []modelv1.FormItem
	if err := json.Unmarshal([]byte(approvalInLark.Form), &formList); err != nil {
		log.L(c).Error(err.Error())

		return nil, errors.Wrap(err, "Error when unmarshal lark approval instance form data")
	}

	formVO := make(map[string]interface{})
	for _, form := range formList {
		if form.Type == "fieldList" {
			arr := form.Value.([]interface{})
			var tmp []map[string]interface{}
			for i := 0; i < len(arr); i++ {
				value := arr[i].([]interface{})
				itemMap := make(map[string]interface{}, 2)
				for j := 0; j < len(value); j++ {
					formIN := (value[j]).(map[string]interface{})
					itemMap[formIN["name"].(string)] = formIN["value"]
				}
				tmp = append(tmp, itemMap)
			}
			formVO[form.Name] = tmp
		} else if form.Type == "department" {
			departMap := form.Value.([]interface{})[0].(map[string]interface{})
			formVO[form.Name] = departMap["name"]
		} else {
			formVO[form.Name] = form.Value
		}
	}

	user, err := s.store.User().GetByAnyID(c, approvalInLark.UserId)
	if err != nil {
		log.L(c).Error(err.Error())

		return nil, errors.Wrap(err, "Error when GetByAnyID from db")
	}

	for _, each := range approvalInLark.TaskList {
		userEach, err := s.store.User().GetByAnyID(c, each.UserId)
		if err != nil {
			log.L(c).Error(err.Error())

			continue
		}
		each.UserName = userEach.Name
	}
	approvalInLark.FormVO = formVO
	approvalInLark.Form = ""
	approvalInLark.UserName = user.Name

	return approvalInLark, nil
}

func (s *approvalService) GetApprovalByInstanceCode(c context.Context, instanceCode string) (*modelv1.Approval, error) {
	approval, err := s.store.Approval().Get(c, &modelv1.Approval{InstanceCode: instanceCode})
	if err != nil {
		log.L(c).Error(err.Error())

		return nil, errors.Wrap(err, "Error when GetByInstanceCode from db")
	}

	return approval, nil
}

// Subscribe approval so that it can receive approval event from lark.
func (s *approvalService) Subscribe(c context.Context) {
	commonService := &commonService{store: s.store}
	for _, approvalDefCode := range modelv1.ApprovalCodeMap {
		_, err := commonService.GetDataByReqLarkAPI(c, "POST", LarkOpenAPIBase+"/approval/v4/approvals/"+approvalDefCode+"/subscribe", nil, nil)
		if err != nil {
			log.L(c).Error("fail to subscribe lark approval event, approval definition code: " + approvalDefCode + ", err: " + err.Error())

			continue
		}

		log.L(c).Info("success to subscribe lark approval event, approval definition code: " + approvalDefCode)
	}
}
