package orgsso

import (
	"context"
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	usersvc "github.com/tidbcloud/account/internel/svc/user"

	"github.com/jinzhu/copier"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

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

	"github.com/tidbcloud/tiinf/errs"

	"github.com/tidbcloud/account/idl/pbgen/proto/central"

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

var domainRegex = "\\w+(\\.\\w+)+"

func pushContentToAuth0(ctx context.Context, oidcInfo OidcInfo, oidcConnectionName string) (*Auth0ConnectionResp, error) {
	oktaDomain := oidcInfo.IssuerUrl
	createParam := GenOidcConnectionParamWithDiscoveryUrl(ctx, oidcConnectionName, oktaDomain, oidcInfo.ClientId, oidcInfo.ClientSecret)
	if oidcInfo.Id != "" {
		tilog.Info(ctx, "pushContentToAuth0 oidc connection id exist, UpdateAuth0Connection")
		err := UpdateAuth0Connection(ctx, oidcInfo.Id, createParam)
		if err != nil {
			return nil, err
		}
		return &Auth0ConnectionResp{
			Id:   oidcInfo.Id,
			Name: createParam.ConnectionName,
		}, nil
	}
	/*if oidcInfo.Id != "" {
		if err := DeleteAuth0Connection(ctx, oidcInfo.Id); err != nil {
			return nil, err
		}
	}*/
	resp, err := CreateAuth0Connection(ctx, createParam)
	if err != nil {
		tilog.Error(ctx, "CreateAuth0Connection fail", zap.Error(err))
		return nil, errs.NewBadRequest(fmt.Sprintf("%s %s", oidcConnectionName, err.Error()), nil)
	}
	tilog.Info(ctx, "pushContentToAuth0 success,resp: ", zap.Any("resp", resp))
	return resp, nil
}

func pushSamlContentToAuth0(ctx context.Context, samlInfo SamlInfo, samlConnectionName string) (*Auth0ConnectionResp, error) {
	var createConnectionParam *CreateConnectionParam
	if samlInfo.MetadataUrl != "" {
		createConnectionParam = GenSamlConnectionParamWithMetadataUrl(ctx, samlConnectionName, samlInfo.MetadataUrl)
	} else {
		createConnectionParam = GenSamlConnectionParamWithSignOnUrl(ctx, samlConnectionName, samlInfo.SignOnUrl, samlInfo.SigningCert)
	}
	if samlInfo.Id != "" {
		tilog.Info(ctx, "pushSamlContentToAuth0 saml connection id exist, UpdateAuth0Connection")
		err := UpdateAuth0Connection(ctx, samlInfo.Id, createConnectionParam)
		if err != nil {
			return nil, err
		}
		return &Auth0ConnectionResp{
			Id:   samlInfo.Id,
			Name: createConnectionParam.ConnectionName,
		}, nil
	}
	/*if samlInfo.Id != "" {
		tilog.Info(ctx, "pushSamlContentToAuth0 samlInfo.Id is not empty, need to delete origin connection", zap.Any("connectionId", samlInfo.Id))
		if err := DeleteAuth0Connection(ctx, samlInfo.Id); err != nil {
			return nil, err
		}
	}*/
	resp, err := CreateAuth0Connection(ctx, createConnectionParam)
	if err != nil {
		tilog.Error(ctx, "CreateAuth0Connection error", zap.Any("param", createConnectionParam), zap.Error(err))
		return nil, errs.NewBadRequest(fmt.Sprintf("%s %s", samlConnectionName, err.Error()), nil)
	}
	tilog.Info(ctx, "pushSamlContentToAuth0 success,resp: ", zap.Any("resp", resp))
	return resp, nil
}

func GetAuthMethodEntity(ctx context.Context, req *central.UpdateAuthMethodReq) (*entity.OrgAuthMethod, error) {
	var content string
	var err error

	if req.Name == "" {
		return nil, errs.NewBadRequest("name must not null", nil)
	}
	if utils.GetAuthMethodType(req.Type) == "" {
		return nil, errs.NewBadRequest("type is invalid", nil)
	}
	for _, domain := range req.AllowEmailDomain {
		match, _ := regexp.MatchString(domainRegex, domain)
		if !match {
			return nil, errs.NewBadRequest("allow_email_domain is invalid:"+domain, nil)
		}

	}
	emailDomains := strings.Join(req.AllowEmailDomain, entity.CommaSeparator)
	scimProvision := false

	if req.Type == string(entity.AuthMethodTypeOIDC) {
		if req.OidcConfig == nil {
			return nil, errs.NewBadRequest("OidcConfig must not null", nil)
		}
		if req.OidcConfig.IssuerUrl == "" {
			return nil, errs.NewBadRequest("issuer_url is necessary", nil)
		}
		if req.OidcConfig.ClientId == "" {
			return nil, errs.NewBadRequest("client_id is necessary", nil)
		}
		if req.OidcConfig.ClientSecret == "" {
			return nil, errs.NewBadRequest("client_secret is necessary", nil)
		}
		oidcInfo := &OidcInfo{}
		_ = copier.Copy(oidcInfo, req.OidcConfig)
		content = getContentFromOidcInfo(ctx, *oidcInfo)

	} else if req.Type == string(entity.AuthMethodTypeSAML) {
		if req.SamlConfig == nil {
			return nil, errs.NewBadRequest("SamlConfig must not null", nil)
		}
		if req.SamlConfig.MetadataUrl == "" && (req.SamlConfig.SignOnUrl == "" || req.SamlConfig.SigningCert == "") {
			return nil, errs.NewBadRequest("metadata_url or sign_on_url/signing_cert is necessary", nil)
		}
		samlInfo := &SamlInfo{}
		_ = copier.Copy(samlInfo, req.SamlConfig)
		content = getContentFromSamlInfo(ctx, *samlInfo)

		scimProvision = req.ScimProvision
	}
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}
	orgAuthMethod := entity.OrgAuthMethod{
		OrgID:            entity.ID(req.OrgId),
		Type:             utils.GetAuthMethodType(req.Type),
		Name:             req.Name,
		Content:          content,
		AutoProvision:    req.AutoProvision,
		AllowEmailDomain: emailDomains,
		UpdatedBy:        user.Email,
		ScimProvision:    scimProvision,
	}
	return &orgAuthMethod, nil
}

func getContentFromOidcInfo(ctx context.Context, oidcInfo OidcInfo) string {

	content, err := json.Marshal(oidcInfo)
	if err != nil {
		tilog.Error(ctx, "req.OidcConfig convert to json fail"+err.Error())
		return ""
	}

	return string(content)
}
func getOidcInfoFromContent(ctx context.Context, methodContent string) *OidcInfo {
	bean := &OidcInfo{}
	if methodContent == "" {
		return bean
	}
	err := json.Unmarshal([]byte(methodContent), bean)
	if err != nil {
		tilog.Warn(ctx, "method content to OidcContent bean err", zap.Any("err", err))

	}
	return bean
}

func getContentFromSamlInfo(ctx context.Context, samlInfo SamlInfo) string {

	content, err := json.Marshal(samlInfo)
	if err != nil {
		tilog.Error(ctx, "req.SamlConfig convert to json fail"+err.Error())
		return ""
	}

	return string(content)
}

func getSamlInfoFromContent(ctx context.Context, methodContent string) *SamlInfo {
	bean := &SamlInfo{}
	if methodContent == "" {
		return bean
	}
	err := json.Unmarshal([]byte(methodContent), bean)
	if err != nil {
		tilog.Warn(ctx, "method content to SamlContent bean err", zap.Any("err", err))

	}
	return bean
}
