package v1

import (
	"context"
	"encoding/json"
	"strconv"
	"strings"
	"time"
	"unicode"

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

	"github.com/pingcap-inc/security/pkg/rstorage"

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

type companySearchService struct {
	store store.Factory
}

type CompanySearchSrv interface {
	StartSearch(c context.Context, event v1.Event)
	SearchCompany(c context.Context, event v1.Event) error
	TestBitable(ctx context.Context)
}

var _ CompanySearchSrv = (*companySearchService)(nil)

func newCompanySearch(srv *service) *companySearchService {
	return &companySearchService{store: srv.store}
}

const (
	chatKeyPrefix   = "chat:"
	companyAppToken = "OlUFbCjfPadHCxsiI2XcyIkqnNf"
	comProblem      = "tblyuIeVOeclkQX4"
	sanctionToken   = "tblixRcxWBDYmSpa"
	sanctionRef     = "tblYNEI28it2dpLk"
	companyBasic    = "tblrXslaILoVRV4l"
	searchCompany   = "tblRr8GEO4njlfgb"
)

type TextMessage struct {
	Text string `json:"text"`
}

func (css *companySearchService) StartSearch(c context.Context, event v1.Event) {
	err := css.SearchCompany(c, event)
	if err != nil {
		sdMsg(c)

		log.L(c).Errorf("Error when search company %v", err)
	}
}

func (css *companySearchService) SearchCompany(c context.Context, event v1.Event) error {
	log.L(c).Info("SearchCompany Called.")

	visitUrl := "https://security.pingcap.net/#/company?company_name="

	if exist, err := IsDuplicateMessage(c, event); err != nil || exist {
		return err
	}

	go css.SaveUserLog(c, event)

	var text TextMessage
	if err := json.Unmarshal([]byte(event.EventData.Message.Content), &text); err != nil {
		log.L(c).Error("Error when unmarshal content.")

		return err
	}

	textStr := strings.ReplaceAll(text.Text, "：", ":")
	splits := strings.Split(strings.TrimSpace(textStr), ":")
	comName := strings.TrimSpace(splits[0])
	var country string
	if len(splits) > 1 {
		country = strings.ToUpper(strings.TrimSpace(splits[1]))
	}

	exist, err := css.checkCompanyExist(c, comName, country)
	if err != nil {
		return err
	}

	comSrv := newCommon()

	tok, err := comSrv.GetLarkToken(c, 0)
	if err != nil {
		log.L(c).Error("Error when get lark token.")

		return err
	}

	if !exist && country == "" {
		te := "Company not exist. Pls use company name:country code to search. eg: huawei:CN"
		waitSearchMsg(c, event, tok, te)

		return err
	}

	if !exist {
		te := "Company not exist. We will retrieve and create it for you."
		go waitSearchMsg(c, event, tok, te)
		err := css.store.Company().CreateCompany(c, []v1.Company{
			{
				CreatedAt: time.Now(),
				Name:      comName,
				Status:    0,
				From:      "机器人",
				Country:   country,
			},
		})
		if err != nil && !IsDuplicate(err) {
			log.L(c).Error("Error when create company.")

			return err
		}

		err = StartDuns(c)
		if err != nil {
			log.L(c).Error("Error when dun check.")

			return err
		}
	}

	var ct TextMessage
	// counts, err := css.ListCountInfo(c, comName, country)
	// if err != nil {
	// 	return err
	// }
	coms, err := css.store.Company().GetCompany(c, comName, country)
	if err != nil {
		log.L(c).Error("Error when get company by name.")

		return err
	}

	ct.Text = SearchRespStr(coms) + "[公司信息检索平台](" + visitUrl + comName + ")"
	Msg, _ := json.Marshal(ct)
	_, err = ReplyLarkMsg(c, event.EventData.Message.MessageID, MessageBody{
		MsgType: "text",
		Content: string(Msg),
	}, tok)
	if err != nil {
		return err
	}

	log.L(c).Info("SearchCompany Done.")

	return nil
}

func (css *companySearchService) checkCompanyExist(c context.Context, company, country string) (bool, error) {
	rds, err := css.store.Company().GetCompany(c, company, country)
	if err != nil {
		if strings.Contains(err.Error(), "not found") {
			log.L(c).Infof("Company %s not exist.", company)

			err := css.store.Company().CreateCompany(c, []v1.Company{
				{
					Name:   company,
					Status: 0,
				},
			})
			if err != nil {
				log.L(c).Error("Error when create company.")

				return false, err
			}

			return false, nil
		} else {
			log.L(c).Error("Error when get company by name.")

			return false, err
		}
	}
	if len(rds) == 0 {
		return false, nil
	}

	return true, nil
}

func (css *companySearchService) SaveUserLog(c context.Context, event v1.Event) {
	log.L(c).Info("SaveUserLog Called.")

	userLog := &v1.UserLog{
		CreatedAt: time.Now(),
		UserID:    event.EventData.Sender.SenderID.UserID,
		MessageID: event.EventData.Message.MessageID,
		Content:   event.EventData.Message.Content,
	}

	err := css.store.Company().SaveUserLog(c, userLog)
	if err != nil {
		log.L(c).Error("Error when save user log.")

		return
	}

	log.L(c).Info("SaveUserLog Done.")
}

func (css *companySearchService) OutToBitable(ctx context.Context, tok string) error {
	comInfos, err := css.store.Company().ListComProblem(ctx)
	if err != nil {
		log.L(ctx).Errorf("list company info error: %v", err)

		return err
	}

	addInfos := make([]v1.BitableItem, 0)
	for _, comInfo := range comInfos {
		RiskIcons := strings.Split(comInfo.RiskIcons, ",")
		addInfos = append(addInfos, v1.BitableItem{
			Fields: v1.ComProblemFields{
				SearchedName:    comInfo.CompanyName,
				PrimaryName:     comInfo.PrimaryName,
				CountryISOCode:  comInfo.CountryISOAlpha2Code,
				EntityType:      comInfo.EntityType,
				Title:           comInfo.Title,
				RiskIcons:       RiskIcons,
				MatchedName:     comInfo.MatchedName,
				MatchedNameType: comInfo.MatchedNameType,
			}})
	}

	err = FreshBiTable(ctx, companyAppToken, comProblem, addInfos, tok)
	if err != nil {
		return err
	}

	sanctions, err := css.store.Company().ListSanction(ctx)
	if err != nil {
		log.L(ctx).Errorf("list company sanction error: %v", err)

		return err
	}
	addSancs := make([]v1.BitableItem, 0)
	for _, sanction := range sanctions {
		addSancs = append(addSancs, v1.BitableItem{
			Fields: v1.SanctionsFields{
				SearchedName:           sanction.CompanyName,
				PrimaryName:            sanction.PrimaryName,
				OrganizationNames:      sanction.OrganizationNames,
				WatchListContentNotes:  sanction.WatchListContentNotes,
				WatchListContentStatus: sanction.WatchListContentStatus,
				DataProviders:          sanction.DataProviders,
			}})
	}

	err = FreshBiTable(ctx, companyAppToken, sanctionToken, addSancs, tok)
	if err != nil {
		return err
	}

	sancRefs, err := css.store.Company().ListSanctionReferences(ctx)
	if err != nil {
		log.L(ctx).Errorf("list company sanction references error: %v", err)

		return err
	}
	addSancRefs := make([]v1.BitableItem, 0)
	for _, sancRef := range sancRefs {
		addSancRefs = append(addSancRefs, v1.BitableItem{
			Fields: v1.SancRefFields{
				SearchedName: sancRef.CompanyName,
				PrimaryName:  sancRef.PrimaryName,
				Reference:    sancRef.Reference,
				ProviderName: sancRef.ProviderName,
				ProviderCode: sancRef.ProviderCode,
				Description:  sancRef.Description,
				Status:       sancRef.Status,
				StartDate:    sancRef.StartDate,
				EndDate:      sancRef.EndDate,
			}})
	}

	err = FreshBiTable(ctx, companyAppToken, sanctionRef, addSancRefs, tok)
	if err != nil {
		return err
	}

	// comOthers, err := css.store.Company().ListCompanyOthers(ctx)
	// if err != nil {
	// 	log.L(ctx).Errorf("list company others error: %v", err)
	//
	// 	return err
	// }

	// err = FreshBiTable(ctx, companyAppToken, companyBasic, addComOthers, tok)
	// if err != nil {
	// 	return err
	// }

	log.L(ctx).Info("OutToBitable Finished.")

	return nil
}

func (css *companySearchService) OutToBitableAddOnly(ctx context.Context, tok string, record []*v1.Company) error {
	log.L(ctx).Info("OutToBitableAddOnly Called.")
	recordIDs := make([]uint64, 0)
	for _, rec := range record {
		recordIDs = append(recordIDs, rec.ID)
	}

	ci, cs, csr, co, err := css.store.Company().ListAllByCompanyIDs(ctx, recordIDs)
	if err != nil {
		log.L(ctx).Errorf("list all by company ids error: %v", err)

		return err
	}

	addInfos := make([]v1.BitableItem, 0)
	for _, comInfo := range ci {
		RiskIcons := strings.Split(comInfo.RiskIcons, ",")
		addInfos = append(addInfos, v1.BitableItem{
			Fields: v1.ComProblemFields{
				SearchedName:    comInfo.CompanyName,
				PrimaryName:     comInfo.PrimaryName,
				CountryISOCode:  comInfo.CountryISOAlpha2Code,
				EntityType:      comInfo.EntityType,
				Title:           comInfo.Title,
				RiskIcons:       RiskIcons,
				MatchedName:     comInfo.MatchedName,
				MatchedNameType: comInfo.MatchedNameType,
				MatchScore:      comInfo.MatchScore,
			}})
	}

	if len(addInfos) > 0 {
		err = AddBiRecords(ctx, companyAppToken, comProblem, addInfos, tok)
		if err != nil {
			return err
		}
	}

	addSancs := make([]v1.BitableItem, 0)
	for _, sanction := range cs {
		addSancs = append(addSancs, v1.BitableItem{
			Fields: v1.SanctionsFields{
				SearchedName:           sanction.CompanyName,
				PrimaryName:            sanction.PrimaryName,
				OrganizationNames:      sanction.OrganizationNames,
				WatchListContentNotes:  sanction.WatchListContentNotes,
				WatchListContentStatus: sanction.WatchListContentStatus,
				DataProviders:          sanction.DataProviders,
			}})
	}

	if len(addSancs) > 0 {
		err = AddBiRecords(ctx, companyAppToken, sanctionToken, addSancs, tok)
		if err != nil {
			return err
		}
	}

	addSancRefs := make([]v1.BitableItem, 0)
	for _, sancRef := range csr {
		addSancRefs = append(addSancRefs, v1.BitableItem{
			Fields: v1.SancRefFields{
				SearchedName: sancRef.CompanyName,
				PrimaryName:  sancRef.PrimaryName,
				Reference:    sancRef.Reference,
				ProviderName: sancRef.ProviderName,
				ProviderCode: sancRef.ProviderCode,
				Description:  sancRef.Description,
				Status:       sancRef.Status,
				StartDate:    sancRef.StartDate,
				EndDate:      sancRef.EndDate,
			}})
	}

	if len(addSancRefs) > 0 {
		err = AddBiRecords(ctx, companyAppToken, sanctionRef, addSancRefs, tok)
		if err != nil {
			return err
		}
	}

	addComOthers := make([]v1.BitableItem, 0)
	for _, comOther := range co {
		addComOthers = append(addComOthers, v1.BitableItem{
			Fields: v1.ComBasicFields{
				SearchedName: comOther.CompanyName,
				PrimaryName:  comOther.PrimaryName,
				Region:       comOther.Country,
				Status:       comOther.Status,
				Address:      comOther.Address,
				RegNumber:    comOther.RegNumber,
			}})
	}

	if len(addComOthers) > 0 {
		err = AddBiRecords(ctx, companyAppToken, companyBasic, addComOthers, tok)
		if err != nil {
			return err
		}
	}

	addSearchCom := make([]v1.BitableItem, 0)
	for _, com := range record {
		icons := strings.Split(com.RiskIcons, ",")
		if com.RiskIcons == "" {
			icons = []string{"无"}
		}
		addSearchCom = append(addSearchCom, v1.BitableItem{
			Fields: v1.SearchedCompany{
				SearchedName:     com.Name,
				Country:          com.Country,
				From:             com.From,
				SearchResult:     mapStatus(com.Status),
				BannedCountry:    mapTrueFalse(com.BannedCountry),
				RiskIcons:        icons,
				SanctionQuantity: strconv.Itoa(com.SancCount),
			}})
	}

	if len(addSearchCom) > 0 {
		err = AddBiRecords(ctx, companyAppToken, searchCompany, addSearchCom, tok)
		if err != nil {
			return err
		}
	}

	log.L(ctx).Info("OutToBitable Finished.")

	return nil
}

func (css *companySearchService) TestBitable(ctx context.Context) {
	comSrv := newCommon()

	tok, err := comSrv.GetLarkToken(ctx, 0)
	if err != nil {
		log.L(ctx).Error("Error when get lark token.")

		return
	}

	records, err := css.store.Company().ListCompany(ctx)
	if err != nil {

	}
	rd := make([]*v1.Company, 0)
	for _, rec := range records {
		rd = append(rd, &rec)
	}
	err = css.OutToBitableAddOnly(ctx, tok, rd)
}

// func (css *companySearchService) ListCountInfo(ctx context.Context, name, country string) ([]int, error) {
// 	log.L(ctx).Infof("ListCountInfo Called. name: %s, country: %s", name, country)
//
// 	schComs, err := css.store.Company().GetCompany(ctx, name, country)
// 	if err != nil {
// 		log.L(ctx).Errorf("get company by name error: %v", err)
//
// 		return nil, err
// 	}
//
// 	res := make([]int, 0)
// 	res = append(res, len(schComs))
//
// 	resMap := make(map[string]int)
//
// 	ids := make([]uint64, 0)
// 	for _, schCom := range schComs {
// 		ids = append(ids, schCom.ID)
// 	}
//
// 	ci, cs, _, _, err := css.store.Company().ListAllByCompanyIDs(ctx, ids)
// 	if err != nil {
// 		log.L(ctx).Errorf("list all by company ids error: %v", err)
//
// 		return nil, err
// 	}
//
// 	for _, compro := range ci {
// 		icons := strings.Split(compro.RiskIcons, ",")
// 		for _, icon := range icons {
// 			if icon != "" {
// 				resMap[icon]++
// 			}
// 		}
// 	}
//
// 	log.L(ctx).Info("ListCountInfo Finished.")
//
// 	return res, nil
// }

func IsDuplicateMessage(c context.Context, event v1.Event) (bool, error) {
	rStore := &rstorage.RedisCluster{
		KeyPrefix: chatKeyPrefix,
	}

	// already exist
	exists, err := rStore.Exists(event.EventData.Message.MessageID)
	if err != nil {
		log.L(c).Error("Error when check if message already exist.")

		return false, err
	}
	if exists {
		log.L(c).Infof("Message %s already exist.", event.EventData.Message.MessageID)

		return true, nil
	}

	// set redis key
	if err := rStore.SetKey(event.EventData.Message.MessageID, "", time.Hour); err != nil {
		log.L(c).Error("Error when set key in redis.")

		return false, err
	}

	return false, nil
}

func ifTooShort(c context.Context, msgID, content string) (bool, error) {

	comSrv := newCommon()

	if isLessThanThreeChars(content) {
		log.L(c).Info("Content too short.")

		txt := TextMessage{
			Text: "Company name too short.",
		}
		Msg, _ := json.Marshal(txt)

		tx, err := comSrv.GetLarkToken(c, 0)
		if err != nil {
			return false, err
		}

		_, err = ReplyLarkMsg(c, msgID, MessageBody{
			MsgType: "text",
			Content: string(Msg),
		}, tx)
		if err != nil {
			log.L(c).Error("Error when reply message.")

			return false, err
		}

		return true, nil
	}

	return false, nil
}

func waitSearchMsg(c context.Context, event v1.Event, tok, msg string) error {
	log.L(c).Info("waitSearchMsg Called.")

	ct := TextMessage{
		Text: msg,
		// Text: "Company not exist. We will retrieve and create it for you.",
	}
	Msg, _ := json.Marshal(ct)
	_, err := ReplyLarkMsg(c, event.EventData.Message.MessageID, MessageBody{
		MsgType: "text",
		Content: string(Msg),
	}, tok)
	if err != nil {
		log.L(c).Error("Error when reply message.")

		return err
	}

	log.L(c).Info("waitSearchMsg Done.")

	return nil
}

func isLessThanThreeChars(s string) bool {
	count := 0
	for _, r := range s {
		if unicode.Is(unicode.Scripts["Latin"], r) || unicode.Is(unicode.Scripts["Han"], r) {
			count++
		}
	}
	return count <= 3
}

func SearchRespStr(record []*v1.Company) string {
	sancAll := 0
	riMaps := make(map[string]int)
	for _, rec := range record {
		sancAll += rec.SancCount
		split := strings.Split(rec.RiskIcons, ",")
		for _, sp := range split {
			riMaps[sp]++
		}
	}

	ris := ""
	for k, _ := range riMaps {
		ris += k + ","
	}
	ris = strings.TrimSuffix(ris, ",")

	str := ""
	if ris == "" {
		str = "The company has no risk icons\n"
	} else {
		str = "The company has risk icons: " + BoldItalics(ris) + "\n"
	}
	str += "The number of sanction information: " + BoldItalics(strconv.Itoa(sancAll)) + "\n"
	str += "You can check the details in "

	return str
}

func BoldItalics(str string) string {
	return "<b><i>" + str + "</i></b>"
}

func mapStatus(status int) string {
	switch status {
	case 1:
		return "已检索完成"
	default:
		return "未检索到结果"
	}
}

func mapTrueFalse(b int) string {
	if b == 1 {
		return "True"
	}

	return "False"
}
