package v1

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/pingcap-inc/pchub/pkg/rstorage"
	"github.com/spf13/viper"

	"github.com/pingcap-inc/security/internal/alert-server/store"

	"github.com/pingcap-inc/security/model/v1"

	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/log"
)

const (
	LarkOpenAPIBase = "https://open.feishu.cn/open-apis"
	QfeiOpenAPIBase = "https://open.qfei.cn/open-apis"
	securityKey     = "security-"
	pchubKey        = "pchub-"
	dataSafeKey     = "data-safe-"
	qfeiKey         = "qfei-"

	pchub    = 0
	dataSafe = 1
	qfei     = 2
)

type commonService struct {
	store store.Factory
}

type ComSrv interface {
	GetLarkToken(ctx context.Context, keyType int) (string, error)
	GetDataByReqLarkAPI(ctx context.Context, m, url string, body io.Reader, params map[string]string, ttoken string) (json.RawMessage, error)
}

var _ ComSrv = (*commonService)(nil)

func newCommon(srv *service) *commonService {
	return &commonService{store: srv.store}
}

var cmSrv *commonService

func newCommonSrv() *commonService {
	if cmSrv == nil {
		cmSrv = &commonService{}
	}

	return cmSrv
}

func (cs *commonService) GetLarkToken(ctx context.Context, keyType int) (string, error) {
	log.L(ctx).Info("GetLarkToken Called.")

	rds := &rstorage.RedisCluster{
		KeyPrefix: securityKey,
	}

	var tok, rdsKey string
	var err error

	switch keyType {
	case dataSafe:
		tok, err = rds.GetKey(dataSafeKey)
		rdsKey = dataSafeKey
	case pchub:
		tok, err = rds.GetKey(pchubKey)
		rdsKey = pchubKey
	case qfei:
		tok, err = rds.GetKey(qfeiKey)
		rdsKey = qfeiKey
	default:
		return "", fmt.Errorf("invalid key type: %d", keyType)
	}

	if err != nil {
		if !errors.As(err, &rstorage.ErrKeyNotFound) {
			log.L(ctx).Error(errors.Wrap(err, "error when retrieve key in redis").Error())
		}
	}

	if tok == "" {
		var appIDInLark, appSecInLark, bUrl string
		var tatBody []byte
		switch keyType {
		case dataSafe:
			appIDInLark = viper.GetString("larkdata.appId")
			appSecInLark = viper.GetString("larkdata.appSecret")
			bUrl = LarkOpenAPIBase
			tatBody, _ = json.Marshal(map[string]string{
				"app_id":     appIDInLark,
				"app_secret": appSecInLark,
			})
		case pchub:
			appIDInLark = viper.GetString("lark.appId")
			appSecInLark = viper.GetString("lark.appSecret")
			bUrl = LarkOpenAPIBase
			tatBody, _ = json.Marshal(map[string]string{
				"app_id":     appIDInLark,
				"app_secret": appSecInLark,
			})
		case qfei:
			appIDInLark = viper.GetString("qfei.appId")
			appSecInLark = viper.GetString("qfei.appSecret")
			bUrl = QfeiOpenAPIBase
			tatBody, _ = json.Marshal(map[string]string{ //nolint:errchkjson // must Marshal
				"appId":     appIDInLark,
				"appSecret": appSecInLark,
			})
		default:
			return "", fmt.Errorf("invalid key type: %d", keyType)
		}

		if appIDInLark == "" || appSecInLark == "" {
			err := errors.New("lark-app-id or lark-app-secret is empty.")
			log.L(ctx).Error(err.Error())

			return "", err
		}

		tatReqBody := bytes.NewBuffer(tatBody)
		tatReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, bUrl+"/auth/v3/tenant_access_token/internal", tatReqBody)
		tatReq.Header.Set("Content-Type", "application/json; charset=utf-8")
		tatResp, err := (&http.Client{}).Do(tatReq)
		if err != nil {
			err := errors.Wrap(err, "error when request lark tenant access token")
			log.L(ctx).Error(err.Error())

			return "", err
		}
		defer func() {
			_ = tatResp.Body.Close()
		}()
		tatRespBody, _ := io.ReadAll(tatResp.Body)
		var tatRes v1.LarkTATokenRes
		if err := json.Unmarshal(tatRespBody, &tatRes); err != nil {
			log.L(ctx).Errorf("Error when unmarshal lark res: %s", err.Error())

			return "", errors.Wrap(err, "error when unmarshal lark tenant_access_token res")
		}
		tok = tatRes.TAT

		if err := rds.SetKey(rdsKey, tok, 30*time.Minute); err != nil {
			log.L(ctx).Errorf("Error when store multi-sa token into redis: %s", err.Error())
		}
	}

	return tok, nil
}

func (cs *commonService) GetDataByReqLarkAPI(ctx context.Context, m, url string, body io.Reader, params map[string]string, ttoken string) (json.RawMessage, error) {
	log.L(ctx).Info("GetDataByReqLarkAPI Called.")

	tat := ttoken
	if tat == "" {
		rtat, err := cs.GetLarkToken(ctx, pchub)
		if err != nil {
			log.L(ctx).Error(err.Error())

			return nil, errors.Wrap(err, "Error when GetLarkToken")
		}
		tat = rtat
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	req, _ := http.NewRequestWithContext(ctx, m, url, body)

	query := req.URL.Query()
	if params != nil || len(params) != 0 {
		for k, v := range params {
			query.Set(k, v)
		}
	}
	req.URL.RawQuery = query.Encode()
	req.Header.Set("Authorization", "Bearer "+tat)
	req.Header.Set("Content-Type", "application/json")
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.L(ctx).Error(err.Error())
		err = fmt.Errorf("error when request lark api: %s %s, err: %w", m, url, err)

		return nil, err
	}
	defer func() {
		_ = resp.Body.Close()
	}()

	b, _ := io.ReadAll(resp.Body)
	var res v1.LarkCommonRes

	if err := json.Unmarshal(b, &res); err != nil {
		log.L(ctx).Error(err.Error())
		err = fmt.Errorf("error when unmarshal lark api res: %s %s, err: %w", m, url, err)

		return nil, err
	}
	if res.Code != 0 {
		log.L(ctx).Errorf("Lark API error code: %d, msg: %s", res.Code, res.Msg)
		err = fmt.Errorf("lark Error Code: %d", res.Code)

		return nil, err
	}

	return res.Data, nil
}

type EventLog struct {
	CommonDrawers CommonDrawers `json:"common_drawers"`
	Objects       []EventObject `json:"objects"`
	OperatorValue string        `json:"operator_value"`
	EventTime     int64         `json:"event_time"`
	UniqueID      string        `json:"unique_id"`
}

type DrawerInfoList struct {
	InfoKey string `json:"info_key"`
	InfoVal string `json:"info_val"`
}

type CommonDrawers struct {
	CommonDrawInfoList []DrawerInfoList `json:"common_draw_info_list"`
}

type EventObject struct {
	ObjectName string `json:"object_name"`
}

type DrawerInfo struct {
	Key     string `json:"key"`
	KeyName string `json:"keyName"`
	Value   string `json:"value"`
}

type user struct {
	Name string `json:"name"`
}

func EventRequest(ctx context.Context, params map[string]string) ([]EventLog, error) {
	log.L(ctx).Info("FetchEvent Called.")

	comSrv := newCommonSrv()

	tok, _ := comSrv.GetLarkToken(ctx, pchub)
	now := time.Now()
	// 计算1小时之前的时间
	oneHourAgo := now.Add(-time.Hour)
	// 获取1小时之前的时间戳（以秒为单位）
	timestamp := oneHourAgo.Unix()
	fmt.Println(timestamp)

	var pageToken string

	eventLogs := make([]EventLog, 0)
	for {
		if pageToken != "" {
			params["page_token"] = pageToken
		}
		data, err := comSrv.GetDataByReqLarkAPI(ctx, "GET", LarkOpenAPIBase+"/admin/v1/audit_infos", nil, params, tok)
		if err != nil {
			log.L(ctx).Errorf("get data by req lark api failed:%s", err.Error())

			return nil, err
		}

		var eventLog []EventLog
		var dataMap map[string]interface{}

		err = json.Unmarshal(data, &dataMap)
		if err != nil {
			log.L(ctx).Errorf("unmarshal data failed:%s", err.Error())

			return nil, err
		}

		pageToken = dataMap["page_token"].(string)

		mar, _ := json.Marshal(dataMap["items"])
		err = json.Unmarshal(mar, &eventLog)
		if err != nil {
			log.L(ctx).Errorf("unmarshal data failed:%s", err.Error())

			return nil, err
		}

		eventLogs = append(eventLogs, eventLog...)

		hasMore := dataMap["has_more"].(bool)
		if !hasMore {
			break
		}
	}

	log.L(ctx).Infof("event logs:%v", eventLogs)

	return eventLogs, nil
}

type SecApprMsg struct {
	SafeType      string   `json:"safe_type"`
	Assets        string   `json:"assets"`
	PromDesc      string   `json:"prom_msg"`
	Suggestion    string   `json:"suggestion"`
	EmerLevel     string   `json:"emergency_level"`
	RespPerson    []string `json:"responsible_person"`
	Collaborators []string `json:"collaborators"`
}

func SendLarkAlert(ctx context.Context, apprMsg SecApprMsg) error {
	log.L(ctx).Info("SendLarkAlert Called.")

	comSrv := newCommonSrv()

	reqBody := LarkCreateApprovalBody{
		ApprovalCode: phishingApprovalCode,
	}

	reqBody.UserId = defaultUserLarkID
	reqBody.OpenId = defaultUserOpLarkID

	var resForm []FormItem
	resForm = append(resForm, FormItem{
		Id:    v1.SafeType,
		Type:  "radioV2",
		Value: apprMsg.SafeType,
	}, FormItem{
		Id:    v1.Assets,
		Type:  "radioV2",
		Value: apprMsg.Assets,
	}, FormItem{
		Id:    v1.ProblemDescription,
		Type:  "textarea",
		Value: apprMsg.PromDesc,
	}, FormItem{
		Id:    v1.ImprovementSuggestion,
		Type:  "textarea",
		Value: apprMsg.Suggestion,
	}, FormItem{
		Id:    v1.EmergencyLevel,
		Type:  "radioV2",
		Value: apprMsg.EmerLevel,
	}, FormItem{
		Id:    v1.ResponsiblePerson,
		Type:  "contact",
		Value: apprMsg.RespPerson,
	}, FormItem{
		Id:    v1.Collaborators,
		Type:  "contact",
		Value: apprMsg.Collaborators,
	}, FormItem{
		Id:    v1.RectificationTime,
		Type:  "date",
		Value: time.Now().Add(time.Hour * 24 * 15).Format(time.RFC3339),
	})

	jsonForm, _ := json.Marshal(resForm)
	reqBody.Form = string(jsonForm)

	marshalBody, _ := json.Marshal(reqBody)

	larkToken, err := comSrv.GetLarkToken(ctx, pchub)
	if err != nil {
		log.L(ctx).Errorf("GetLarkToken error: %v", err)

		return err
	}

	approvalR, err := comSrv.GetDataByReqLarkAPI(ctx, "POST", LarkOpenAPIBase+"/approval/v4/instances", bytes.NewReader(marshalBody), nil, larkToken)
	if err != nil {
		log.L(ctx).Errorf("error when submit lark approval, err: %s", err.Error())

		return err
	}

	var approvalRes ApprovalLarkResData
	err = json.Unmarshal(approvalR, &approvalRes)
	if err != nil {
		log.L(ctx).Errorf("error when unmarshal lark approval response, err: %s", err.Error())

		return err
	}

	log.L(ctx).Infof("submit lark approval success, approval id: %s", approvalRes.InstanceCode)

	return nil
}
