package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"regexp"
	"strconv"
	"time"
)

const (
	CSAppId            = "cli_a18d2ef3aff8d00d"
	CSAppSecret        = "6lVHGPaUe4gp9yGjikN5dhlzO76Lstr2"
	CSTableToken       = "bascnDbfN0a1FBKWCcvPb4BqIHb"
	CSTableId          = "tblOw4djdIlx45cM"
	CSFullViewId       = "vewVao2XgG"
	CSPrimaryColName   = "Customer Project/App - 客户业务"
	CSStageColName     = "Success Stage - 客户成功阶段"
	CSLevelColName     = "Relationship Level - 客户关系等级"
	CSRecordTableToken = "bascnWSAc3upf2dLG9xf1uOsz3d"
	CSRecordTableId    = "tblOvAcvU9R5Lk4e"
	CSRecordViewId     = "vewPWBiSQE"
)

type BiTableRecordsItem struct {
	RecordId string                 `json:"record_id,omitempty"`
	Fields   map[string]interface{} `json:"fields"`
}
type BiTableRecordsData struct {
	HasMore   bool                 `json:"has_more"`
	PageToken string               `json:"page_token"`
	Total     int                  `json:"total"`
	Items     []BiTableRecordsItem `json:"items"`
}
type BiTableRecordsLarkApiRes struct {
	Code int64              `json:"code"`
	Msg  string             `json:"msg"`
	Data BiTableRecordsData `json:"data"`
}
type InsertBiTableRecordsData struct {
	Records []BiTableRecordsItem `json:"records"`
}
type InsertBiTableRecordsLarkApiRes struct {
	Code int64                    `json:"code"`
	Msg  string                   `json:"msg"`
	Data InsertBiTableRecordsData `json:"data"`
}
type GeneralRecordBody struct {
	Records []BiTableRecordsItem `json:"records"`
}
type DiffStageLevelRes struct {
	Op     string             `json:"op"`
	Record BiTableRecordsItem `json:"record"`
}

var CSStageLevelRecords = map[string]BiTableRecordsItem{}

func fetchBiTableAllRecords(tat, pageSize, pageToken, tableToken, tableId, viewId string) ([]BiTableRecordsItem, error) {
	var records []BiTableRecordsItem
	resp, err := fetchBiTableRecords(tat, tableToken, tableId, &LarkFetchBiTableRecordsParams{
		ViewId:    viewId,
		PageSize:  pageSize,
		PageToken: pageToken,
	})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	var jsonB BiTableRecordsLarkApiRes
	if err := json.Unmarshal(body, &jsonB); err != nil {
		return nil, err
	}
	if jsonB.Code != 0 {
		return nil, errors.New(strconv.FormatInt(jsonB.Code, 10) + ": " + jsonB.Msg)
	}

	records = append(records, jsonB.Data.Items...)

	if jsonB.Data.HasMore && jsonB.Data.PageToken != "" {
		nextCustomers, err := fetchBiTableAllRecords(tat, "", jsonB.Data.PageToken, tableToken, tableId, viewId)
		if err == nil {
			records = append(records, nextCustomers...)
		}
	}
	return records, nil
}

func initCSStageLevelRecords(tat string) {
	allRecords, err := fetchBiTableAllRecords(tat, "", "", CSRecordTableToken, CSRecordTableId, CSRecordViewId)
	if err != nil {
		fmt.Printf("%s\n", "Fetch BiTable All Stage&Level Records Error: "+err.Error())
		return
	}
	for _, record := range allRecords {
		if record.Fields[CSPrimaryColName] != nil {
			customerName, ok := (record.Fields[CSPrimaryColName]).(string)
			if !ok {
				fmt.Printf("%s\n", "Stage&Level Table Customer Col Changed")
				return
			}
			CSStageLevelRecords[customerName] = record
		}
	}
}

func diffCustomerStageLevl(customerName, level, stage string) *DiffStageLevelRes {
	if level == "" || stage == "" {
		return nil
	}
	sr, _ := regexp.Compile(`^(S\d):`)
	lr, _ := regexp.Compile(`^(L\d):`)
	smatch := sr.FindStringSubmatch(stage)
	lmatch := lr.FindStringSubmatch(level)
	if smatch == nil || lmatch == nil {
		return nil
	}
	coord := smatch[1] + " - " + lmatch[1]
	customerSLRecord, exist := CSStageLevelRecords[customerName]
	isNewStage := false
	nowUnixSec := time.Now().Unix()
	if exist {
		fieldVal := customerSLRecord.Fields
		coordCell, exists := fieldVal[coord]
		coordStr, canStr := coordCell.(string)
		if exists && canStr && coordStr != "" {
			coordBytes := []byte(coordStr)
			var jsonCoord [][]int64
			if err := json.Unmarshal(coordBytes, &jsonCoord); err != nil {
				fmt.Printf("%s\n", "Unmarshal Coord Value Error: "+err.Error())
				return nil
			}
			last := jsonCoord[len(jsonCoord)-1]
			if !(last[0] > 0 && last[1] == 0) {
				jsonCoord = append(jsonCoord, []int64{nowUnixSec, 0})
				isNewStage = true
				ncoordVal, _ := json.Marshal(jsonCoord)
				fieldVal[coord] = string(ncoordVal)
			} else {
				return nil
			}
		} else {
			slVals := [][]int64{
				{nowUnixSec, 0},
			}
			coordVal, _ := json.Marshal(slVals)
			fieldVal[coord] = string(coordVal)
			isNewStage = true
		}

		if isNewStage {
			for fieldName, field := range fieldVal {
				r, _ := regexp.Compile(`^S\d\s-\sL\d$`)
				if r.FindString(fieldName) != "" && fieldName != coord {
					slStr, _ := field.(string)
					needCloseReg, _ := regexp.Compile(`\[\d+,0\]\]$`)
					if needCloseReg.FindString(slStr) == "" {
						continue
					}
					slBytes := []byte(slStr)
					var jsonSL [][]int64
					if err := json.Unmarshal(slBytes, &jsonSL); err != nil {
						fmt.Printf("%s\n", "Unmarshal SL Cell Value Error: "+err.Error())
						return nil
					}
					last := jsonSL[len(jsonSL)-1]
					last[1] = nowUnixSec - 1
					jsonSL[len(jsonSL)-1] = last
					nSL, _ := json.Marshal(jsonSL)
					fieldVal[fieldName] = string(nSL)
				}
			}
		}

		return &DiffStageLevelRes{
			Op: "UPDATE",
			Record: BiTableRecordsItem{
				RecordId: customerSLRecord.RecordId,
				Fields:   fieldVal,
			},
		}
	} else {
		fieldVal := make(map[string]interface{})
		fieldVal[CSPrimaryColName] = customerName
		slVals := [][]int64{
			{nowUnixSec, 0},
		}
		coordVal, _ := json.Marshal(slVals)
		fieldVal[coord] = string(coordVal)
		return &DiffStageLevelRes{
			Op: "INSERT",
			Record: BiTableRecordsItem{
				Fields: fieldVal,
			},
		}
	}
}

func updateCustomerSuccess() {
	// fetch tenant token
	tat, err := fTenantToken(CSAppId, CSAppSecret)
	if err != nil {
		fmt.Printf("%s\n", "Fetch Tenant Token Error: "+err.Error())
		return
	}
	// init memory CSStageLevelRecords
	if len(CSStageLevelRecords) == 0 {
		initCSStageLevelRecords(tat)
	}
	// fetch all customers
	allCustomers, err := fetchBiTableAllRecords(tat, "", "", CSTableToken, CSTableId, CSFullViewId)
	if err != nil {
		fmt.Printf("%s\n", "Fetch BiTable All Customers Records Error: "+err.Error())
		return
	}
	// loop customers, update customer stage&level records
	allDiffRes := map[string][]BiTableRecordsItem{
		"INSERT": {},
		"UPDATE": {},
	}
	for _, customerRecord := range allCustomers {
		primaryCol, ok := (customerRecord.Fields[CSPrimaryColName]).([]interface{})
		if !ok {
			fmt.Printf("%s\n", "CS Primary Col Changed")
			return
		}
		customerCol, ok := (primaryCol[0]).(map[string]interface{})
		customerName, toStrOK := (customerCol["text"]).(string)
		if !ok || !toStrOK {
			fmt.Printf("%s\n", "Customer Name Convert Fail")
			return
		}
		levelStr := ""
		if customerRecord.Fields[CSLevelColName] != nil {
			levelStr, ok = (customerRecord.Fields[CSLevelColName]).(string)
			if !ok {
				fmt.Printf("%s\n", "CS Level Col Changed")
				return
			}
		}
		stageStr := ""
		if customerRecord.Fields[CSStageColName] != nil {
			stageStr, ok = (customerRecord.Fields[CSStageColName]).(string)
			if !ok {
				fmt.Printf("%s\n", "CS Stage Col Changed")
				return
			}
		}
		diffRes := diffCustomerStageLevl(customerName, levelStr, stageStr)
		if diffRes != nil && diffRes.Op == "INSERT" {
			allDiffRes["INSERT"] = append(allDiffRes["INSERT"], diffRes.Record)
		} else if diffRes != nil && diffRes.Op == "UPDATE" {
			allDiffRes["UPDATE"] = append(allDiffRes["UPDATE"], diffRes.Record)
		}
	}

	if len(allDiffRes["INSERT"]) > 0 {
		body, _ := json.Marshal(&GeneralRecordBody{
			Records: allDiffRes["INSERT"],
		})
		reqBody := bytes.NewBuffer(body)
		insertRes, err := batchInsertBiTableRecord(tat, CSRecordTableToken, CSRecordTableId, reqBody)
		if err != nil {
			fmt.Printf("%s\n", "Insert BiTable Stage&Level Record Error: "+err.Error())
			return
		}
		defer insertRes.Body.Close()
		ib, _ := ioutil.ReadAll(insertRes.Body)
		var jsonB InsertBiTableRecordsLarkApiRes
		if err := json.Unmarshal(ib, &jsonB); err != nil {
			fmt.Printf("%s\n", "Unmarshal InsertBiTableRecordsLarkApiRes Error: "+err.Error())
			return
		}
		if jsonB.Code != 0 {
			fmt.Printf("%s\n", "Unmarshal InsertBiTableRecordsLarkApiRes Error: "+strconv.FormatInt(jsonB.Code, 10)+": "+jsonB.Msg)
			return
		}
		for _, v := range jsonB.Data.Records {
			if v.Fields[CSPrimaryColName] != nil {
				customerName, ok := (v.Fields[CSPrimaryColName]).(string)
				if !ok {
					fmt.Printf("%s\n", "Update Memory Cache Error: Stage&Level Table Customer Col Changed")
					return
				}
				CSStageLevelRecords[customerName] = v
			}
		}
	}

	if len(allDiffRes["UPDATE"]) > 0 {
		body, _ := json.Marshal(&GeneralRecordBody{
			Records: allDiffRes["UPDATE"],
		})
		reqBody := bytes.NewBuffer(body)
		upRes, err := batchUpdateBiTableRecord(tat, CSRecordTableToken, CSRecordTableId, reqBody)
		if err != nil {
			fmt.Printf("%s\n", "Update BiTable Stage&Level Record Error: "+err.Error())
			return
		}
		defer upRes.Body.Close()
		ub, _ := ioutil.ReadAll(upRes.Body)
		var jsonB InsertBiTableRecordsLarkApiRes
		if err := json.Unmarshal(ub, &jsonB); err != nil {
			fmt.Printf("%s\n", "Unmarshal InsertBiTableRecordsLarkApiRes(Update) Error: "+err.Error())
			return
		}
		if jsonB.Code != 0 {
			fmt.Printf("%s\n", "Unmarshal InsertBiTableRecordsLarkApiRes(Update) Error: "+strconv.FormatInt(jsonB.Code, 10)+": "+jsonB.Msg)
			return
		}
		for _, v := range jsonB.Data.Records {
			if v.Fields[CSPrimaryColName] != nil {
				customerName, ok := (v.Fields[CSPrimaryColName]).(string)
				if !ok {
					fmt.Printf("%s\n", "Update Memory Cache Error: Stage&Level Table Customer Col Changed")
					return
				}
				CSStageLevelRecords[customerName] = v
			}
		}
	}

	fmt.Printf("Done UpdateCustomerSuccess: %d inserted, %d updated\n", len(allDiffRes["INSERT"]), len(allDiffRes["UPDATE"]))
}
