package crm

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/pingcap/crm-bot/pkg/util"
)

type AccessTokenResp struct {
	CorpAccessToken string `json:"corpAccessToken"`
	CorpID          string `json:"corpId"`
	ExpiresIn       int    `json:"expiresIn"`
	ErrorCode       int    `json:"errorCode"`
	ErrorMessage    string `json:"errorMessage"`
	TraceID         string `json:"traceId"`
}

func GetAccessToken() string {
	body := make(map[string]string)
	body["appId"] = "FSAID_131a00b"
	body["appSecret"] = os.Getenv("appSecret")
	body["permanentCode"] = os.Getenv("permanentCode")

	header := map[string]string{
		"Content-Type": "application/json",
	}

	req, err := util.HttpRequest(http.MethodPost, "https://open.fxiaoke.com/cgi/corpAccessToken/get/V2", header, nil, body)
	if err != nil {
		return ""
	}

	ac := AccessTokenResp{}
	err = util.Do(req, &ac)
	if err != nil {
		return ""
	}

	if ac.ErrorCode != 0 {
		return ""
	}
	return ac.CorpAccessToken
}

type UserResp struct {
	EmpList []struct {
		OpenUserID string `json:"openUserId"`
	} `json:"empList"`
	ErrorCode        int    `json:"errorCode"`
	ErrorMessage     string `json:"errorMessage"`
	ErrorDescription string `json:"errorDescription"`
	TraceID          string `json:"traceId"`
}

func GetOpenUserID(mobile, acToken string) string {
	body := make(map[string]string)
	body["corpAccessToken"] = acToken
	body["corpId"] = "FSCID_BBBDA0FD0A83028CD08938922C5D16DA"
	body["mobile"] = mobile

	header := map[string]string{
		"Content-Type": "application/json",
	}
	req, err := util.HttpRequest(http.MethodPost, "https://open.fxiaoke.com/cgi/user/getByMobile", header, nil, body)
	if err != nil {
		return ""
	}

	user := UserResp{}
	err = util.Do(req, &user)
	if err != nil {
		return ""
	}

	if user.ErrorCode != 0 {
		return ""
	}
	return user.EmpList[0].OpenUserID
}

type CrmRequest struct {
	CorpAccessToken   string `json:"corpAccessToken"`
	CorpID            string `json:"corpId"`
	CurrentOpenUserID string `json:"currentOpenUserId"`
	Data              struct {
		SearchQueryInfo struct {
			Offset  int           `json:"offset"`
			Limit   int           `json:"limit"`
			Filters []interface{} `json:"filters"`
		} `json:"search_query_info,omitempty"`
		DataObjectAPIName string `json:"dataObjectApiName,omitempty"`
		App               string `json:"app,omitempty"`
		BizType           int    `json:"bizType,omitempty"`
		PageSize          int    `json:"pageSize,omitempty"`
		ObjectDataId      string `json:"objectDataId,omitempty"`
	} `json:"data"`
}

type FieldDescription struct {
	DescribeAPIName string `json:"describe_api_name"`
	IsIndex         bool   `json:"is_index"`
	IsActive        bool   `json:"is_active"`
	CreateTime      int64  `json:"create_time"`
	IsEncrypted     bool   `json:"is_encrypted"`
	Description     string `json:"description"`
	IsUnique        bool   `json:"is_unique"`
	DefaultValue    string `json:"default_value"`
	Label           string `json:"label"`
	Type            string `json:"type"`
	IsNeedConvert   bool   `json:"is_need_convert"`
	IsRequired      bool   `json:"is_required"`
	APIName         string `json:"api_name"`
	TargetApiName   string `json:"target_api_name"`
	Options         []struct {
		FontColor string `json:"font_color"`
		FeKey     string `json:"fe_key"`
		Label     string `json:"label"`
		FeSaved   int    `json:"fe_saved"`
		Value     string `json:"value"`
		Config    struct {
			Edit   int `json:"edit"`
			Enable int `json:"enable"`
			Remove int `json:"remove"`
		} `json:"config"`
	} `json:"options"`
	DefineType   string `json:"define_type"`
	IsSingle     bool   `json:"is_single"`
	IsIndexField bool   `json:"is_index_field"`
	IndexName    string `json:"index_name"`
	Config       struct {
		Add     int `json:"add"`
		Edit    int `json:"edit"`
		Enable  int `json:"enable"`
		Display int `json:"display"`
		Attrs   struct {
			Options    int `json:"options"`
			Label      int `json:"label"`
			IsRequired int `json:"is_required"`
			HelpText   int `json:"help_text"`
		} `json:"attrs"`
	} `json:"config"`
	HelpText string `json:"help_text"`
}

type FieldsInfo struct {
	TraceID string `json:"traceId"`
	Data    struct {
		Describe struct {
			CreatedBy string                      `json:"created_by"`
			IconIndex int                         `json:"icon_index"`
			APIName   string                      `json:"api_name"`
			IconPath  string                      `json:"icon_path"`
			Fields    map[string]FieldDescription `json:"fields"`
		} `json:"describe"`
	} `json:"data"`
}

type TodoView struct {
	TodoID      string   `json:"todoId"`
	TenantID    int      `json:"tenantId"`
	BizType     string   `json:"bizType"`
	TodoType    string   `json:"todoType"`
	SenderID    string   `json:"senderId"`
	ActionRange int      `json:"actionRange"`
	EmployeeIds []string `json:"employeeIds"`
	Title       string   `json:"title"`
	TodoData    struct {
		Elements []struct {
			Key   string `json:"key"`
			Value string `json:"value"`
		} `json:"elements"`
	} `json:"todoData"`
	ObjectAPIName   string `json:"objectApiName"`
	ObjectID        string `json:"objectId"`
	URL             string `json:"url"`
	CreateTimeStamp int64  `json:"createTimeStamp"`
	UnRead          bool   `json:"unRead"`
	Expired         bool   `json:"expired"`
	DealTimeStamp   int64  `json:"dealTimeStamp"`
	URLParameters   struct {
		ActivityID         string `json:"activityId"`
		ActivityInstanceID string `json:"activityInstanceId"`
		WorkflowInstanceID string `json:"workflowInstanceId"`
		TaskID             string `json:"taskId"`
	} `json:"urlParameters,omitempty"`
}

type CRMTodoListResp struct {
	TraceID string `json:"traceId"`
	Data    struct {
		TodoViewList     []TodoView `json:"todoViewList,omitempty"`
		TodoDealViewList []TodoView `json:"todoDealViewList,omitempty"`
		Count            int        `json:"count"`
	} `json:"data"`
	ErrorDescription string `json:"errorDescription"`
	ErrorMessage     string `json:"errorMessage"`
	ErrorCode        int    `json:"errorCode"`
}
type CRMGetResp struct {
	TraceID          string                 `json:"traceId"`
	Data             map[string]interface{} `json:"data"`
	ErrorDescription string                 `json:"errorDescription"`
	ErrorMessage     string                 `json:"errorMessage"`
	ErrorCode        int                    `json:"errorCode"`
}

type CRMResp struct {
	TraceID string `json:"traceId"`
	Data    struct {
		DataList           []map[string]interface{} `json:"dataList"`
		Offset             int                      `json:"offset"`
		Limit              int                      `json:"limit"`
		Total              int                      `json:"total"`
		AllPageSummaryData struct {
		} `json:"allPageSummaryData"`
	} `json:"data"`
	ErrorDescription string `json:"errorDescription"`
	ErrorMessage     string `json:"errorMessage"`
	ErrorCode        int    `json:"errorCode"`
}

const (
	Todo_URL        = "https://open.fxiaoke.com/cgi/crm/v2/special/getTodoViewList"
	Finish_Todo_URL = "https://open.fxiaoke.com/cgi/crm/v2/special/getTodoDealViewList"
)

// "https://open.fxiaoke.com/cgi/crm/v2/special/getTodoViewList"
func QueryTodoList(request *CrmRequest, url string) (*CRMTodoListResp, error) {
	header := map[string]string{
		"Content-Type": "application/json",
	}
	req, err := util.HttpRequest(http.MethodPost, url, header, nil, request)
	if err != nil {
		return nil, err
	}

	crmData := CRMTodoListResp{}
	err = util.Do(req, &crmData)
	if err != nil {
		return nil, err
	}

	if crmData.ErrorCode != 0 {
		return nil, errors.New(crmData.ErrorMessage)
	}
	return &crmData, err
}

func GetCrmData(request *CrmRequest) (*CRMGetResp, error) {
	header := map[string]string{
		"Content-Type": "application/json",
	}
	req, err := util.HttpRequest(http.MethodPost, "https://open.fxiaoke.com/cgi/crm/custom/v2/data/get", header, nil, request)
	if err != nil {
		return nil, err
	}

	crmData := CRMGetResp{}
	err = util.Do(req, &crmData, util.SetHttpTimeout(5*time.Second))
	if err != nil {
		return nil, err
	}

	if crmData.ErrorCode != 0 {
		err = errors.New(crmData.ErrorMessage)
	}
	return &crmData, err
}

func QueryCrmData(request *CrmRequest, url string) (*CRMResp, error) {
	if url == "" {
		url = "https://open.fxiaoke.com/cgi/crm/v2/data/query"
	}

	header := map[string]string{
		"Content-Type": "application/json",
	}

	req, err := util.HttpRequest(http.MethodPost, url, header, nil, request)
	if err != nil {
		return nil, err
	}

	crmData := CRMResp{}
	err = util.Do(req, &crmData)
	if err != nil {
		return nil, err
	}

	if crmData.ErrorCode != 0 {
		return nil, errors.New(crmData.ErrorMessage)
	}
	return &crmData, err
}

type CrmWashedData map[string]interface{}
type OutputData struct {
	Data []map[string]interface{} `json:"data"`
}

type FieldDesc struct {
	FieldId   string
	FieldName string
	Option    string
	Type      string
	Key       string
}

var associateMap = map[string]interface{}{}

func WashCrmData(fieldDescFileName, needFieldFileName string,
	crmData []map[string]interface{}, request *CrmRequest) (OutputData, []FieldDesc) {
	fData, _ := os.ReadFile(fieldDescFileName)
	d := FieldsInfo{}
	json.Unmarshal(fData, &d)

	csvData, _ := os.ReadFile(needFieldFileName)
	csvReader := csv.NewReader(bytes.NewReader(csvData))
	csvReader.Read()
	fieldMap := make([]FieldDesc, 0, 10)
	for {
		record, err := csvReader.Read()
		if err != nil {
			break
		}
		fieldMap = append(fieldMap, FieldDesc{record[1], record[0], record[6], record[3], record[5]})
	}

	output := OutputData{}
	for _, data := range crmData {
		washedData := map[string]interface{}{}
		for _, fieldDesc := range fieldMap {
			if value, ok := data[fieldDesc.FieldId]; ok {
				if fieldDesc.Option == "Need" {
					options := d.Data.Describe.Fields[fieldDesc.FieldId].Options
					match := false

					if fieldDesc.Type == "Array" {
						value = value.([]interface{})[0].(string)

						for _, option := range options {
							if option.Value == value {
								match = true
								washedData[fieldDesc.FieldName] = option.Label
								break
							}
						}

						if !match {
							washedData[fieldDesc.FieldName] = value
						}
						continue
					} else if fieldDesc.Type == "ArrayMap" {
						valueArr, ok := value.([]interface{})
						if !ok || len(valueArr) == 0 {
							continue
						}

						value = valueArr[0].(map[string]interface{})[fieldDesc.Key]
						washedData[fieldDesc.FieldName] = value
						continue
					} else if fieldDesc.Type == "Map" {
						valueMap, ok := value.(map[string]interface{})
						if !ok {
							continue
						}

						value = valueMap[fieldDesc.Key]
						washedData[fieldDesc.FieldName] = value
						continue
					}

					for _, option := range options {
						if option.Value == value {
							match = true
							washedData[fieldDesc.FieldName] = option.Label
							break
						}
					}

					if !match {
						washedData[fieldDesc.FieldName] = value
					}
				} else {
					if fieldDesc.Type == "金额" ||
						fieldDesc.Type == "数字" ||
						fieldDesc.Type == "计算字段" ||
						fieldDesc.Type == "统计字段" ||
						fieldDesc.Type == "百分数" {
						switch value := value.(type) {
						case string:
							v, err := strconv.ParseFloat(value, 64)
							if err != nil {
								washedData[fieldDesc.FieldName] = value
							} else {
								washedData[fieldDesc.FieldName] = v
							}
						case float64:
							washedData[fieldDesc.FieldName] = value
						}
					} else if fieldDesc.Type == "日期时间todate" {
						dateTime := time.Unix(int64(value.(float64)/1000), int64(value.(float64))%1000)
						// 创建时间 -> 2021-07-02T08:59:34.000+0000
						washedData[fieldDesc.FieldName] = dateTime
					} else if fieldDesc.Type == "日期时间tostring" {
						dateTime := time.Unix(int64(value.(float64)/1000), int64(value.(float64))%1000)
						// 创建时间 -> 2021-07-02T08:59:34.000+0000
						washedData[fieldDesc.FieldName] = dateTime.Format("2006-01-02T15:04:05.000-0000")
					} else if strings.Contains(fieldDesc.Type, "查找关联") {
						request.Data.ObjectDataId = value.(string)
						request.Data.DataObjectAPIName = d.Data.Describe.Fields[fieldDesc.FieldId].TargetApiName
						if request.Data.DataObjectAPIName != "" &&
							strings.Contains(fieldDesc.Type, "|") {
							associateField := strings.Split(fieldDesc.Type, "|")[1]

							if v, ok := associateMap[request.Data.ObjectDataId]; ok {
								washedData[fieldDesc.FieldName] = v
							} else {
								// get detail
								resp, err := GetCrmData(request)
								if err != nil {
									log.Println("[error] GetCrmData", err, request)
									continue
								}

								if v, ok := resp.Data[associateField]; ok {
									washedData[fieldDesc.FieldName] = v
									associateMap[request.Data.ObjectDataId] = v
								}
							}
						} else {
							washedData[fieldDesc.FieldName] = value
						}
					} else {
						washedData[fieldDesc.FieldName] = value
					}
				}
			} else {
				washedData[fieldDesc.FieldName] = nil
			}
		}
		output.Data = append(output.Data, washedData)
	}

	return output, fieldMap
}

func CrmAccount() {
	acToken := GetAccessToken()
	if acToken == "" {
		log.Fatal("get acToken failed")
	}
	openUserId := GetOpenUserID("18601011760", acToken)
	if openUserId == "" {
		log.Fatal("get openUserId failed")
	}
}

type CrmDataSource struct {
	ApiName           string
	FieldDescFileName string
	NeedFieldFileName string
	Topic             string
	Url               string
	Type              reflect.Type
	WideTableObjName  string
	Filter            interface{}
}

//
//func CrmSqs() {
//	ss := []CrmDataSource{
//		{
//			ApiName:           "SalesOrderObj",
//			FieldDescFileName: "./etc/order-fielddesc.json",
//			NeedFieldFileName: "./etc/order-crm.csv",
//			Topic:             "pingcap-crm-order",
//		},
//		{
//			ApiName:           "NewOpportunityObj",
//			FieldDescFileName: "./etc/business-opportunity-fielddesc.json",
//			NeedFieldFileName: "./etc/business-opportunity-crm.csv",
//			Topic:             "pingcap-crm-opportunity",
//		},
//		{
//			ApiName:           "PaymentObj",
//			FieldDescFileName: "./etc/payment-fielddesc.json",
//			NeedFieldFileName: "./etc/payment-crm.csv",
//			Topic:             "pingcap-crm-payment",
//		},
//	}
//
//	// send to sqs
//	sess := session.Must(session.NewSessionWithOptions(session.Options{
//		SharedConfigState: session.SharedConfigEnable,
//	}))
//
//	svc := sqs.New(sess)
//
//	queue := "ingress-mim.fifo"
//	result, err := svc.GetQueueUrl(&sqs.GetQueueUrlInput{
//		QueueName: &queue,
//	})
//
//	if err != nil {
//		log.Fatal(err)
//	}
//
//	for i := range ss {
//		source := &ss[i]
//		outputData := CrmData(source)
//
//		fmt.Println(len(outputData.Data))
//		for _, data := range outputData.Data {
//			dataBytes, _ := json.Marshal(data)
//			_, err = svc.SendMessage(&sqs.SendMessageInput{
//				MessageGroupId:         aws.String(source.Topic),
//				MessageDeduplicationId: aws.String(uuid.NewString()),
//				MessageBody:            aws.String(string(dataBytes)),
//				QueueUrl:               result.QueueUrl,
//			})
//
//			if err != nil {
//				fmt.Println(err)
//			}
//		}
//	}
//}

func CrmData(source *CrmDataSource, filter interface{}) OutputData {
	acToken := GetAccessToken()
	if acToken == "" {
		log.Println("[error] get acToken failed")
		return OutputData{}
	}
	openUserId := GetOpenUserID("18601011760", acToken)
	if openUserId == "" {
		log.Println("[error] get openUserId failed")
		return OutputData{}
	}

	log.Println("process ", source.Topic)
	requestData := CrmRequest{}
	requestData.CorpID = "FSCID_BBBDA0FD0A83028CD08938922C5D16DA"
	requestData.CorpAccessToken = acToken
	requestData.CurrentOpenUserID = openUserId
	requestData.Data.DataObjectAPIName = source.ApiName
	requestData.Data.SearchQueryInfo.Offset = 0
	requestData.Data.SearchQueryInfo.Limit = 500
	if filter != nil {
		requestData.Data.SearchQueryInfo.Filters = append(requestData.Data.SearchQueryInfo.Filters, filter)
	}

	inputData := make([]map[string]interface{}, 0, requestData.Data.SearchQueryInfo.Limit)
	for {
		resp, err := QueryCrmData(&requestData, source.Url)
		if err != nil {
			log.Println("[error] QueryCrmData", err)
			return OutputData{}
		}

		inputData = append(inputData, resp.Data.DataList...)
		if resp.Data.Total > requestData.Data.SearchQueryInfo.Offset+requestData.Data.SearchQueryInfo.Limit {
			requestData.Data.SearchQueryInfo.Offset += requestData.Data.SearchQueryInfo.Limit
		} else {
			break
		}
		time.Sleep(200 * time.Millisecond)
	}

	outputData, _ := WashCrmData(source.FieldDescFileName, source.NeedFieldFileName, inputData, &requestData)

	return outputData
}
