package msp

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"unicode/utf8"

	orgDao "github.com/tidbcloud/account/internel/dao/org"

	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/account/commons/config"

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

	"github.com/tidbcloud/tiinf/errs"

	"go.uber.org/zap"

	"github.com/tidbcloud/tiinf/monitor/tilog"

	"gorm.io/gorm"

	"github.com/tidbcloud/account/internel/dao/entity"
)

func (s *MspSvc) GetMspConfigByOrgID(ctx context.Context, orgID entity.ID) (*entity.MspConfig, error) {
	tilog.Info(ctx, "MspSvc GetMspConfigByOrgID", zap.Any("orgID", orgID))
	return s.hub.Msp.GetMspConfigByOrgID(ctx, s.db.WithContext(ctx), orgID)
}

func (s *MspSvc) CreateMsp(ctx context.Context, msp forms.MspOrg) (mspOrg *entity.Tenant, err error) {
	tilog.Info(ctx, "MspSvc CreateMspConfig ", zap.Any("msp", msp))
	if err := validateCreateMsp(ctx, s, msp); err != nil {
		return nil, err
	}

	//var newOrg entity.Tenant
	err = s.db.Transaction(func(tx *gorm.DB) error {

		org := entity.Tenant{
			Name:     msp.Name,
			Alias:    msp.Name,
			Email:    msp.Email,
			Country:  msp.Country,
			TimeZone: msp.TimeZone,
			Status:   entity.TenantStatusActive,

			Kind:                "external",
			AcceptService:       true,
			OrgType:             entity.OrgTypePersonal,
			ThirdPartyFreeTrial: false,
			OrgTag:              string(entity.OrgTagMsp)}

		mspOrg, err = s.hub.Org.CreateOrg(ctx, tx.WithContext(ctx), org)
		if err != nil {
			tilog.Error(ctx, "failed to create msp org  in db", zap.Any("org", org), zap.Error(err))
			return errs.NewInternalDB("failed to create msp org  in db", err)
		}
		advertisingJsonStr := ""
		if len(msp.AdvertisingArr) > 0 {
			advertisingJsonStr, err = s.AdvertisingArrToJsonStr(ctx, msp.AdvertisingArr)
		}
		if err != nil {
			return err
		}
		mspConfig := &entity.MspConfig{
			OrgID:       mspOrg.ID,
			Advertising: advertisingJsonStr,
			Website:     msp.Website,
			LogoName:    msp.LogoName,
			CreatedBy:   msp.Email,
			UpdatedBy:   msp.Email,
			Status:      entity.Active,
		}

		err = s.hub.Msp.CreateMspConfig(ctx, tx.WithContext(ctx), mspConfig)
		if err != nil {
			tilog.Error(ctx, "failed to create mspConfig  in db", zap.String("org", "org"), zap.Error(err))
			return errs.NewInternalDB("failed to create mspConfig org  in db", err)
		}
		return nil
	})

	return mspOrg, err

}

func (s *MspSvc) AdvertisingArrToJsonStr(ctx context.Context, advertisingArr []forms.Advertising) (string, error) {
	if len(advertisingArr) == 0 {
		return "", nil
	}
	jsonStr, err := json.Marshal(advertisingArr)
	if err != nil {
		tilog.Error(ctx, "json.Marshal(advertisingArr) err", zap.Any("advertisingArr", advertisingArr), zap.Error(err))
		return "", err
	}
	str := string(jsonStr)
	return str, nil
}

func (s *MspSvc) JsonStrToAdvertisingArr(ctx context.Context, advertisingJsonStr string) ([]forms.Advertising, error) {
	//tilog.Info(ctx, "MspSvc JsonStrToAdvertisingArr ", zap.Any("GetAdvertisingArr", GetAdvertisingArr))

	var obj []forms.Advertising

	err := json.Unmarshal([]byte(advertisingJsonStr), &obj)
	if err != nil {
		tilog.Error(ctx, "JsonStrToAdvertisingArr json.Unmarshal err", zap.Any("advertisingJsonStr", advertisingJsonStr), zap.Error(err))

	}

	return obj, err
}

func (s *MspSvc) UpdateMspConfig(ctx context.Context, mspConfig entity.MspConfig) error {
	tilog.Info(ctx, "MspSvc UpdateMspConfig ", zap.Any("mspConfig", mspConfig))
	return s.hub.Msp.UpdateMspConfig(ctx, s.db.WithContext(ctx), mspConfig)
}

func (s *MspSvc) ListMspConfigs(ctx context.Context, req forms.ListMspReq) (*forms.PageData, error) {
	filter := msp.MspConfigFilter{OrgIDs: []entity.ID{entity.ID(req.OrgId)},
		Status: []entity.EntityStatus{utils.GetEntityStatus(req.Status)}}

	objs, err := s.hub.Msp.ListMspConfigs(ctx, s.db.WithContext(ctx), filter)
	if err != nil {
		tilog.Error(ctx, "ListMspConfigs fail", zap.Any("filter", filter), zap.Error(err))
		return nil, err
	}
	items := utils.Pagination(req.Page, req.PageSize, objs)
	resp := &forms.PageData{
		Total: uint32(len(objs)),
		//Items: utils.Pagination(req.Page, req.PageSize, objs).([]entity.MspConfig),
		Items: items,
	}
	tilog.Info(ctx, "ListMspConfigs success", zap.Any("filter", filter), zap.Any("ListMspConfigs size", len(objs)))
	return resp, nil
}

func validateCreateMsp(ctx context.Context, s *MspSvc, mspOrg forms.MspOrg) (err error) {

	err = CheckAdvertising(ctx, mspOrg.AdvertisingArr)
	if err != nil {
		return err
	}

	return validatePartnerDuplicated(ctx, s, mspOrg.Name, mspOrg.Email)
}

func validatePartnerDuplicated(ctx context.Context, s *MspSvc, orgName, orgEmail string) (err error) {
	msg := ""
	// check [name ,org_tag] is unique
	filter := orgDao.ListOrgsFilter{Status: []string{string(entity.Active)}, OrgTag: PartnerOrgTags}
	tenants, err := s.hub.Org.ListOrgsByFilter(ctx, s.db, filter)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		tilog.Error(ctx, "ListOrgsFilter fail ", zap.Any("filter", filter), zap.Error(err))
		return errs.NewInternalDB("failed to create msp org  in db", err)
	}
	for _, org := range tenants {
		if org.Name == orgName {
			msg = "partner name already in use, please use a different name."
		}
		if org.Email == orgEmail {
			msg = "partner email already in use, please use a different email."
		}
		if msg != "" {
			return errs.NewBadRequest(msg, nil)
		}
	}
	return nil
}

func CheckAdvertising(ctx context.Context, advertisingArr []forms.Advertising) (err error) {
	msg := ""
	if len(advertisingArr) > 4 {
		msg = "the number of advertising must be less than 3."
		tilog.Info(ctx, msg, zap.Any(fmt.Sprintf("len(mspOrg.AdvertisingArr) =%d", len(advertisingArr)), advertisingArr))
		tilog.Info(ctx, msg+fmt.Sprintf("%s len(advertising.AdvertisingArr) =%d", msg, len(advertisingArr)))
		if msg != "" {
			return errs.NewBadRequest(msg, nil)
		}
	}
	for _, advertising := range advertisingArr {
		tilog.Info(ctx, "RuneCountInString", zap.Any("advertising.Heading", utf8.RuneCountInString(advertising.Heading)), zap.Any("advertising.Body", utf8.RuneCountInString(advertising.Body)))

		if utf8.RuneCountInString(advertising.Heading) > 40 {
			msg = "the length of advertising heading must be less than 40."
			tilog.Info(ctx, msg+fmt.Sprintf("%s len(advertising.Heading) =%d", msg, len(advertising.Heading)), zap.Any("advertising.Heading", advertising.Heading))
		}

		//if len(advertising.Body) > 200 {
		if utf8.RuneCountInString(advertising.Body) > 200 {

			msg = "the length of advertising body must be less than 200."
			tilog.Info(ctx, msg+fmt.Sprintf("%s len(advertising.Body) =%d", msg, len(advertising.Body)), zap.Any("advertising.Body", advertising.Body))
		}

		if msg != "" {
			return errs.NewBadRequest(msg, nil)
		}
	}

	return err

}

func (s *MspSvc) GetMspLogUrl(ctx context.Context, logoName string) string {

	if logoName != "" {
		domain := config.Global().MspLogoConfig.MspCloudfrontDomain

		logoUrl := fmt.Sprintf("%s/msp/%s", domain, logoName)
		tilog.Info(ctx, fmt.Sprintf("config.Global().MspLogoConfig.MspCloudfrontDomain=%s", config.Global().MspLogoConfig.MspCloudfrontDomain))
		return logoUrl
	}
	return ""
}
