package lark

import (
	"context"
	"errors"
	"log"

	"github.com/larksuite/oapi-sdk-go/api"
	"github.com/larksuite/oapi-sdk-go/api/core/request"
	"github.com/larksuite/oapi-sdk-go/api/core/response"
	"github.com/larksuite/oapi-sdk-go/core"
	larkConf "github.com/larksuite/oapi-sdk-go/core/config"
	"github.com/larksuite/oapi-sdk-go/core/tools"
	v3 "github.com/larksuite/oapi-sdk-go/service/contact/v3"
	v1 "github.com/larksuite/oapi-sdk-go/service/im/v1"
	v2 "github.com/larksuite/oapi-sdk-go/service/sheets/v2"

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

type Lark struct {
	LarkConf           *larkConf.Config
	LarkContactService *v3.Service
	LarkMessageService *v1.Service
	LarkSheetService   *v2.Service
}

func NewLark() *Lark {
	appSettings := core.NewInternalAppSettings(
		core.SetAppCredentials(config.GetString("appid"), config.GetString("appsecret")),
		core.SetAppEventKey(config.GetString("verificationtoken"), config.GetString("encryptkey")))
	var confiOptions []core.ConfigOpt
	if config.GetString("mode") == "debug" {
		confiOptions = append(confiOptions, core.SetLoggerLevel(core.LoggerLevelDebug))
	}

	lark := Lark{}
	lark.LarkConf = core.NewConfig(core.DomainFeiShu, appSettings, confiOptions...)
	lark.LarkContactService = v3.NewService(lark.LarkConf)
	lark.LarkSheetService = v2.NewService(lark.LarkConf)
	lark.LarkMessageService = v1.NewService(lark.LarkConf)

	return &lark
}

func (lark *Lark) GetDepartments() []*v3.Department {
	pageToken := ""
	allDepartments := []*v3.Department{}
	for {
		req := lark.LarkContactService.Departments.List(core.WrapContext(context.Background()))
		req.SetFetchChild(true)
		req.SetPageSize(50)
		if pageToken != "" {
			req.SetPageToken(pageToken)
		}

		departmentsList, err := req.Do()
		if err != nil {
			break
		}

		allDepartments = append(allDepartments, departmentsList.Items...)
		if departmentsList.HasMore {
			pageToken = departmentsList.PageToken
		} else {
			break
		}
	}

	return allDepartments
}

func (lark *Lark) GetOpenIdByEmail(email string) string {
	coreCtx := core.WrapContext(context.Background())
	params := map[string]interface{}{
		"emails": email,
	}
	ret := make(map[string]interface{})
	req := request.NewRequestWithNative("/open-apis/user/v1/batch_get_id", "GET",
		request.AccessTokenTypeTenant, nil, &ret, request.SetQueryParams(params))
	err := api.Send(coreCtx, lark.LarkConf, req)
	if err != nil {
		log.Println("Lark Native Api get err ", err, email)
		return ""
	}

	if users, ok := ret["email_users"]; ok {
		if userdata, ok := users.(map[string]interface{})[email]; ok {
			if ids, ok := userdata.([]interface{})[0].(map[string]interface{}); ok {
				return ids["open_id"].(string)
			}
		}
	}

	log.Println("batch_get_id resp", ret)
	return ""
}

func (lark *Lark) GetUser(user_id string) *v3.User {
	req := lark.LarkContactService.Users.Get(core.WrapContext(context.Background()))
	req.SetUserId(user_id)
	result, err := req.Do()
	if err != nil {
		log.Println("GetUserInfo err:", err)
		return nil
	}

	return result.User
}

type LarkApprovalListReq struct {
	ApprovalCode string `json:"approval_code"`
	StartTime    int64  `json:"start_time"`
	EndTime      int64  `json:"end_time"`
	Offset       int    `json:"offset"`
	Limit        int    `json:"limit"`
}

type ApprovalDetailItem struct {
	ID    string      `json:"id"`
	Name  string      `json:"name"`
	Type  string      `json:"type"`
	Ext   interface{} `json:"ext"`
	Value interface{} `json:"value"`
}

type LarkApprovalListResp struct {
	InstanceCodeList []string `json:"instance_code_list"`
}

type LarkApprovalInfo struct {
	ID           string
	ApprovalCode string        `json:"approval_code"`
	ApprovalName string        `json:"approval_name"`
	CommentList  []interface{} `json:"comment_list"`
	DepartmentID string        `json:"department_id"`
	EndTime      int64         `json:"end_time"`
	Form         string        `json:"form"`
	OpenID       string        `json:"open_id"`
	SerialNumber string        `json:"serial_number"`
	StartTime    int64         `json:"start_time"`
	Status       string        `json:"status"`
	TaskList     []struct {
		EndTime   int64  `json:"end_time"`
		ID        string `json:"id"`
		NodeID    string `json:"node_id"`
		NodeName  string `json:"node_name"`
		OpenID    string `json:"open_id"`
		StartTime int64  `json:"start_time"`
		Status    string `json:"status"`
		Type      string `json:"type"`
		UserID    string `json:"user_id"`
	} `json:"task_list"`
	Timeline []struct {
		CreateTime int64  `json:"create_time"`
		Ext        string `json:"ext"`
		OpenID     string `json:"open_id"`
		Type       string `json:"type"`
		UserID     string `json:"user_id"`
		TaskID     string `json:"task_id,omitempty"`
	} `json:"timeline"`
	UserID string `json:"user_id"`
	UUID   string `json:"uuid"`
}

func (lark *Lark) LarkApprovalList(body LarkApprovalListReq) []LarkApprovalInfo {
	coreCtx := core.WrapContext(context.Background())
	ret := LarkApprovalListResp{}
	req := request.NewRequestWithNative("approval/v2/instance/list", "POST",
		request.AccessTokenTypeTenant, &body, &ret,
	)
	err := api.Send(coreCtx, lark.LarkConf, req)
	if err != nil {
		log.Println(tools.Prettify(err))
		e := &response.Error{}
		if errors.As(err, e) {
			log.Println(e.Code)
			log.Println(e.Msg)
		}
		return nil
	}

	infos := []LarkApprovalInfo{}
	for _, approvalId := range ret.InstanceCodeList {
		approval := LarkApprovalInfo{}

		req = request.NewRequestWithNative("approval/v2/instance/get", "POST",
			request.AccessTokenTypeTenant, &map[string]interface{}{
				"instance_code": approvalId,
			}, &approval,
		)
		err := api.Send(coreCtx, lark.LarkConf, req)
		if err != nil {
			log.Println(tools.Prettify(err))
			e := &response.Error{}
			if errors.As(err, e) {
				log.Println(e.Code)
				log.Println(e.Msg)
			}
			continue
		}

		approval.ID = approvalId
		infos = append(infos, approval)
	}

	return infos
}

func (lark *Lark) GetSheetId(sheetToken string, idx int) (string, error) {
	coreCtx := core.WrapContext(context.Background())
	metaInfoReq := lark.LarkSheetService.Spreadsheetss.Metainfo(coreCtx)
	metaInfoReq.SetSpreadsheetToken(sheetToken)
	resp, err := metaInfoReq.Do()
	if err != nil {
		return "", err
	}

	return resp.Sheets[idx-1].SheetId, nil
}

func (lark *Lark) GetSheetData(sheetToken string, dataRange string) ([]interface{}, error) {
	coreCtx := core.WrapContext(context.Background())
	dataReq := lark.LarkSheetService.Spreadsheetss.ValuesGet(coreCtx)
	dataReq.SetSpreadsheetToken(sheetToken)
	dataReq.SetRange(dataRange)
	resp, err := dataReq.Do()
	if err != nil {
		return nil, err
	}

	return resp.ValueRange.Values, nil
}
