package openapi

import (
	"context"
	"fmt"
	"sort"
	"time"

	"github.com/tidbcloud/account/admin/forms"
	"github.com/tidbcloud/account/internel/svc/msp"

	"github.com/tidbcloud/account/commons/config"
	openapiv1veta1 "github.com/tidbcloud/account/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/account/internel/dao/entity"
	orgsvc "github.com/tidbcloud/account/internel/svc/org"
	"github.com/tidbcloud/account/internel/svc/user"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

type PartnerService struct {
	openapiv1veta1.UnimplementedPartnerServiceServer
}

func (s *PartnerService) CreateSignupUrl(ctx context.Context, req *openapiv1veta1.CreateSignupUrlRequest) (*openapiv1veta1.CreateSignupUrlResponse, error) {
	tilog.Info(ctx, "CreateSignupUrl request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "CreateSignupUrl getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID
	tilog.Info(ctx, "CreateSignupUrl get auth orgId", zap.Any("orgId", authOrgId))

	orgSvc, _ := orgsvc.GetOrgSvc()
	mspOrg, err := orgSvc.GetOrgByID(ctx, authOrgId)
	if err != nil {
		tilog.Error(ctx, "CreateSignupUrl GetOrgByID error", zap.Any("err", err))
		return nil, errs.NewInternalUnknown("get org by id error", err)
	}
	if mspOrg == nil || (mspOrg.OrgTag != string(entity.OrgTagMsp) && mspOrg.OrgTag != string(entity.OrgTagResalePartner)) {
		tilog.Error(ctx, "CreateSignupUrl msp org is nil or org tag is not msp")
		return nil, errs.NewBadRequest("org id is invalid", err)
	}

	userSvc, _ := user.GetUserSvc()
	var method string
	var partnerKey string
	var provider string
	switch mspOrg.OrgTag {
	case string(entity.OrgTagMsp):
		method = config.MspCustomerRegister
		provider = entity.MspThirdPartyAccountProvider
		partnerKey = "msp"
	case string(entity.OrgTagResalePartner):
		method = config.ResaleCustomerRegister
		provider = entity.ResaleThirdPartyAccountProvider
		partnerKey = "reseller"
	}
	registerToken, err := userSvc.CreateInviteToken(ctx, mspOrg.ID.Uint64(), mspOrg.Email, method)
	if err != nil {
		tilog.Error(ctx, "CreateSignupUrl CreateInviteToken error", zap.Any("orgId", mspOrg.ID), zap.Any("email", mspOrg.Email), zap.Error(err))
		return nil, errs.NewInternalUnknown("create invite token error", err)
	}
	signupUrl := fmt.Sprintf("https://%s/signup?%s=%s&third_party_account_provider=%s&third_party_account_token=%s",
		config.Global().ServerConfig.FrontendDNS, partnerKey, mspOrg.Name, provider, registerToken.TokenStr)

	rsp := &openapiv1veta1.CreateSignupUrlResponse{
		CustomerSignupUrl: signupUrl,
		ExpiredTime:       formatTime(registerToken.InviteToken.ExpireAt),
	}
	tilog.Info(ctx, "CreateSignupUrl response", zap.Any("orgId", authOrgId), zap.Any("rsp", rsp))

	return rsp, nil
}
func (s *PartnerService) ListCustomers(ctx context.Context, req *openapiv1veta1.ListCustomersRequest) (*openapiv1veta1.ListCustomersResponse, error) {
	tilog.Info(ctx, "ListCustomers request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "ListCustomers getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID
	tilog.Info(ctx, "ListCustomers get auth orgId", zap.Any("orgId", authOrgId))

	svc, _ := msp.GetMspSvc()
	resMap, err := svc.GetCustomerByMspID(ctx, entity.ID(authOrgId))
	if err != nil {
		tilog.Error(ctx, "ListCustomers GetCustomerByMspID error", zap.Any("orgId", authOrgId), zap.Error(err))
		return nil, errs.NewInternalUnknown("list customers by msp_id error", err)
	}
	var customers []entity.PartnerCustomer
	for customer := range resMap {
		customers = append(customers, customer)
	}
	sort.Slice(customers, func(i, j int) bool {
		ic := customers[i]
		jc := customers[j]
		var it, jt *time.Time
		if ic.EffectedAt != nil && jc.EffectedAt != nil {
			it = ic.EffectedAt
			jt = jc.EffectedAt
		} else {
			it = &ic.CreatedAt
			jt = &jc.CreatedAt
		}
		if it.Equal(*jt) {
			return customers[i].ID > customers[j].ID
		}
		return it.Unix() > jt.Unix()
	})

	var returnCustomers []*openapiv1veta1.Customer
	for _, customer := range customers {
		customerOrg := resMap[customer]
		apiCustomer := convEntity2OpenApiCustomer(&customerOrg, customer)
		returnCustomers = append(returnCustomers, apiCustomer)
	}

	rsp := &openapiv1veta1.ListCustomersResponse{
		Customers: returnCustomers,
	}
	tilog.Info(ctx, "ListCustomers response", zap.Any("rsp", rsp))
	return rsp, nil
}

func (s *PartnerService) GetPartnerProfile(ctx context.Context, req *openapiv1veta1.GetPartnerProfileRequest) (*openapiv1veta1.PartnerProfile, error) {
	tilog.Info(ctx, "GetPartnerProfile request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "GetPartnerProfile getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID
	tilog.Info(ctx, "GetPartnerProfile get auth orgId", zap.Any("orgId", authOrgId))

	mspSvc, _ := msp.GetMspSvc()
	mspConfig, err := mspSvc.GetMspConfigByOrgID(ctx, entity.ID(authOrgId))
	if err != nil {
		tilog.Error(ctx, "GetPartnerProfile GetMspConfigByOrgID error", zap.Any("orgId", authOrgId), zap.Any("err", err))
	}
	if mspConfig == nil {
		tilog.Warn(ctx, "GetPartnerProfile msp config not exist", zap.Any("orgId", authOrgId))
		return nil, errs.NewBadRequest(fmt.Sprintf("org %d' msp config is not exist", authOrgId), err)
	}
	rsp := &openapiv1veta1.PartnerProfile{
		OrgId:     fmt.Sprintf("%d", authOrgId),
		LightLogo: mspSvc.GetMspLogUrl(ctx, mspConfig.LogoName),
		DarkLogo:  mspSvc.GetMspLogUrl(ctx, mspConfig.DarkLogoName),
	}
	if mspConfig.Advertising != "" {
		advertisingArr, _ := mspSvc.JsonStrToAdvertisingArr(ctx, mspConfig.Advertising)
		rsp.Advertising = convFormAdvertising2OpenApiAdvertising(advertisingArr)
	}
	tilog.Info(ctx, "GetPartnerProfile response", zap.Any("rsp", rsp))

	return rsp, nil
}
func (s *PartnerService) UpdatePartnerProfile(ctx context.Context, req *openapiv1veta1.UpdatePartnerProfileRequest) (*openapiv1veta1.PartnerProfile, error) {
	tilog.Info(ctx, "UpdatePartnerProfile request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "UpdatePartnerProfile getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID
	tilog.Info(ctx, "UpdatePartnerProfile get auth orgId", zap.Any("orgId", authOrgId))

	advertising := convOpenApiAdvertising2FormAdvertising(req.PartnerProfile.Advertising)
	err = msp.CheckAdvertising(ctx, advertising)
	if err != nil {
		tilog.Warn(ctx, "UpdatePartnerProfile CheckAdvertising error", zap.Any("advertising", advertising), zap.Any("err", err))
		return nil, err
	}
	mspSvc, _ := msp.GetMspSvc()
	str, err := mspSvc.AdvertisingArrToJsonStr(ctx, advertising)
	if err != nil {
		tilog.Error(ctx, "UpdatePartnerProfile AdvertisingArrToJsonStr error", zap.Any("advertising", advertising), zap.Any("err", err))
		return nil, errs.NewInternalUnknown("marshal advertising to json error", err)
	}
	mspConfig := entity.MspConfig{
		OrgID:        entity.ID(authOrgId),
		Advertising:  str,
		LogoName:     req.PartnerProfile.LightLogo,
		DarkLogoName: req.PartnerProfile.DarkLogo,
		Status:       entity.Active,
	}
	err = mspSvc.UpdateMspConfig(ctx, mspConfig)
	if err != nil {
		tilog.Error(ctx, "UpdatePartnerProfile UpdateMspOrg error", zap.Any("mspConfig", mspConfig), zap.Error(err))
		return nil, errs.NewInternalUnknown("update msp config error", err)
	}

	rsp := &openapiv1veta1.PartnerProfile{
		OrgId:       fmt.Sprintf("%d", authOrgId),
		LightLogo:   req.PartnerProfile.LightLogo,
		DarkLogo:    req.PartnerProfile.DarkLogo,
		Advertising: req.PartnerProfile.Advertising,
	}
	tilog.Info(ctx, "UpdatePartnerProfile response", zap.Any("rsp", rsp))
	return rsp, nil
}

func formatTime(t time.Time) string {
	return t.Local().Format("2006-01-02 15:04:05")
}

func convEntity2OpenApiCustomer(customerOrg *entity.Tenant, customer entity.PartnerCustomer) *openapiv1veta1.Customer {
	retCustomer := &openapiv1veta1.Customer{
		Email:   customerOrg.Email,
		OrgId:   fmt.Sprintf("%d", customerOrg.ID.Uint64()),
		OrgName: customerOrg.Name,
		State:   string(customer.Status),
	}
	if customer.ExpiredAt != nil && !customer.ExpiredAt.IsZero() {
		retCustomer.ExpiredTime = formatTime(*customer.ExpiredAt)
	}
	if customer.EffectedAt != nil && !customer.EffectedAt.IsZero() {
		retCustomer.EffectedTime = formatTime(*customer.EffectedAt)
	}
	return retCustomer
}

func convFormAdvertising2OpenApiAdvertising(advertisingArr []forms.Advertising) []*openapiv1veta1.Advertising {
	var openApiAdvertisingArr []*openapiv1veta1.Advertising
	for _, ad := range advertisingArr {
		openApiAd := &openapiv1veta1.Advertising{
			Index:   ad.Index,
			Heading: ad.Heading,
			Body:    ad.Body,
		}
		openApiAdvertisingArr = append(openApiAdvertisingArr, openApiAd)
	}
	return openApiAdvertisingArr
}

func convOpenApiAdvertising2FormAdvertising(advertisingArr []*openapiv1veta1.Advertising) []forms.Advertising {
	var formAdvertisingArr []forms.Advertising
	for _, ad := range advertisingArr {
		formAd := forms.Advertising{
			Index:   ad.Index,
			Heading: ad.Heading,
			Body:    ad.Body,
		}
		formAdvertisingArr = append(formAdvertisingArr, formAd)
	}
	return formAdvertisingArr
}
