package orgsso

import (
	"context"
	"fmt"
	"strings"

	"github.com/spf13/viper"

	orgsvc "github.com/tidbcloud/account/internel/svc/org"
	orgsso2 "github.com/tidbcloud/account/internel/svc/orgsso"

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

	"github.com/duke-git/lancet/v2/slice"

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

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

	"github.com/tidbcloud/tiinf/errs"

	"github.com/tidbcloud/account/internel/dao/sso"

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

	"google.golang.org/protobuf/types/known/emptypb"

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

type OrgAuthMethodService struct {
	central.UnimplementedOrgAuthMethodServiceServer
}

func NewOrgAuthMethodService() central.OrgAuthMethodServiceServer {
	return &OrgAuthMethodService{}
}

const (
	auth0DomainDev     = "auth.dev.tidbcloud.com"
	auth0DomainStaging = "auth.staging.tidbcloud.com"
	auth0DomainProd    = "auth.tidbcloud.com"

	oidcCallbackUrlFormat = "https://%s/login/callback"
	// https://{yourDomain}/login/callback?connection={yourConnectionName}
	samlPostBackUrlFormat = "https://%s/login/callback?connection=%s"
	// urn:auth0:{yourTenant}:{yourConnectionName}
	samlEntityIdFormat = "urn:auth0:%s:%s"

	openConnectionWhitelistKey = "sso.openConnectionWhitelist"
)

var (
	needCreateConnectionType = []string{
		string(entity.AuthMethodTypeOIDC),
		string(entity.AuthMethodTypeSAML),
	}
)

var (
	scimBaseUrlFormat           = "https://%s/orgs/%d/scim/v2"
	scimUserUniqueField         = "userName"
	scimSupportProvisionActions = []string{"Push New Users", "Push Profile Updates", "Push Groups"}
)

func (s *OrgAuthMethodService) UpdateAuthMethod(ctx context.Context, req *central.UpdateAuthMethodReq) (*emptypb.Empty, error) {
	if len(req.Name) > 50 {
		return &emptypb.Empty{}, errs.NewBadRequest("name length exceeded", nil)
	}
	if !config.IsProdEnv() {
		orgWhitelist := viper.GetStringSlice(openConnectionWhitelistKey)
		if slice.Contain(needCreateConnectionType, req.Type) && !slice.Contain(orgWhitelist, fmt.Sprintf("%d", req.OrgId)) {
			return nil, errs.NewBadRequest("please apply to create OIDC/SAML connection", nil)
		}
	}
	svc, _ := orgsso2.GetOrgSsoSvc()

	err := svc.UpdateAuthMethod(ctx, req)
	return &emptypb.Empty{}, err
}

func (s *OrgAuthMethodService) SetAuthMethodStatus(ctx context.Context, req *central.SetAuthMethodStatusReq) (*emptypb.Empty, error) {
	svc, _ := orgsso2.GetOrgSsoSvc()

	err := svc.SetAuthMethodStatus(ctx, req)
	return &emptypb.Empty{}, err
}
func (s *OrgAuthMethodService) ListAuthMethods(ctx context.Context, req *central.ListAuthMethodsFilter) (resp *central.ListAuthMethodsRsp, err error) {
	svc, _ := orgsso2.GetOrgSsoSvc()

	if req.OrgId == 0 {
		err = errs.NewBadRequest("org_id must not null", nil)
		return nil, err
	}

	filter := sso.OrgAuthMethodFilter{OrgIds: []uint64{req.OrgId}}
	if req.Name != "" {
		filter.Names = []string{req.Name}
	}
	if req.Status != "" {
		entityStatus := utils.GetEntityStatus(req.Status)
		if entityStatus == "" {
			err = errs.NewBadRequest("status invalid", nil)

			return nil, err
		}
		filter.Status = []entity.EntityStatus{entityStatus}
	}

	authMethods, err := svc.ListAuthMethods(ctx, filter)
	if err != nil {
		return nil, err
	}
	respList := make([]*central.OrgAuthMethod, 0, len(authMethods))
	for _, m := range authMethods {
		authMethod := central.OrgAuthMethod{}
		_ = copier.Copy(&authMethod, &m)
		if len(m.AllowEmailDomain) > 0 {
			authMethod.AllowEmailDomain = strings.Split(m.AllowEmailDomain, entity.CommaSeparator)
		}
		if m.Type == entity.AuthMethodTypeOIDC && m.Content != "" {
			oidcConfig, err := svc.GetOidcConfigFromContent(ctx, m.Content)
			if err != nil {
				return nil, err
			}
			authMethod.OidcConfig = oidcConfig
		}
		if m.Type == entity.AuthMethodTypeSAML && m.Content != "" {
			samlConfig, err := svc.GetSamlConfigFromContent(ctx, m.Content)
			if err != nil {
				return nil, err
			}
			authMethod.SamlConfig = samlConfig
		}
		respList = append(respList, &authMethod)
	}

	return &central.ListAuthMethodsRsp{AuthMethods: respList}, err
}
func (s *OrgAuthMethodService) ShowAuthMethods(ctx context.Context, req *central.ShowAuthMethodsReq) (resp *central.ListAuthMethodsRsp, err error) {
	svc, _ := orgsso2.GetOrgSsoSvc()
	callbackUrl := getOidcCallBackUrl()
	if req.OrgId == 0 {
		err = errs.NewBadRequest("org_id must not null", nil)
		return nil, err
	}

	ssoConfig := config.Global().SSOConfig
	samlConnectionName := orgsso2.GetSamlConnectionName(req.OrgId)
	samlPostBackUrl := getSamlCallBackUrl(samlConnectionName)
	samlEntityId := fmt.Sprintf(samlEntityIdFormat, ssoConfig.Auth0Tenant, samlConnectionName)
	tilog.Info(ctx, "get saml entity id", zap.Any("samlEntityId", samlEntityId))

	scimBaseUrl := fmt.Sprintf(scimBaseUrlFormat, ssoConfig.IamOpenApiDomain, req.OrgId)

	filter := sso.OrgAuthMethodFilter{}
	filter.OrgIds = []uint64{req.OrgId}

	orgSvc, _ := orgsvc.GetOrgSvc()
	respList := make([]*central.OrgAuthMethod, 0)
	org, err := orgSvc.GetOrgByID(ctx, req.OrgId)
	if err != nil {
		return nil, err
	}
	for _, name := range entity.AuthMethodArr {
		authMethodBean := central.OrgAuthMethod{
			Name:        name,
			Type:        string(utils.GetAuthMethodTypeByName(name)),
			Status:      string(entity.Disable),
			DisplayName: utils.GetAuthMethodDisplayName(name),
		}
		//if authMethodBean.Type == string(entity.AuthMethodTypeOIDC) {
		//	authMethodBean.OidcConfig = &central.OidcConfig{CallbackUrl: getOidcCallBackUrl()}
		//}
		respList = append(respList, &authMethodBean)
	}
	// if OrgTypePersonal return default authmethod
	if org.OrgType == entity.OrgTypePersonal {
		return &central.ListAuthMethodsRsp{
			AuthMethods:                      respList,
			CallbackUrl:                      callbackUrl,
			SamlPostBackUrl:                  samlPostBackUrl,
			SamlEntityId:                     samlEntityId,
			ScimConnectorBaseUrl:             scimBaseUrl,
			ScimUniqueIdentifierField:        scimUserUniqueField,
			ScimSupportedProvisioningActions: scimSupportProvisionActions,
		}, nil
	}
	authMethods, err := svc.ListAuthMethods(ctx, filter)
	if err != nil {
		return nil, err
	}
	for _, dbAuthMethod := range authMethods {
		isEven := func(i int, b *central.OrgAuthMethod) bool {
			if dbAuthMethod.Type != entity.AuthMethodTypeOIDC && dbAuthMethod.Type != entity.AuthMethodTypeSAML {
				return b.Name == dbAuthMethod.Name
			}
			return b.Type == string(dbAuthMethod.Type)
		}
		// find the auth
		beans := slice.Filter(respList, isEven)
		if len(beans) == 0 {
			err = errs.NewInternalDB("fail to get auth method ", nil)
			return nil, err
		}
		authMethodBean := beans[0]
		if authMethodBean == nil {
			tilog.Error(ctx, "this  name in auth_method is error  record ", zap.Any("authMethod", dbAuthMethod))
			err := errs.NewInternalDB("this  name in auth_method is error  record", nil)
			return &central.ListAuthMethodsRsp{}, err
		}

		_ = copier.Copy(&authMethodBean, &dbAuthMethod)
		if len(dbAuthMethod.AllowEmailDomain) > 0 {
			authMethodBean.AllowEmailDomain = strings.Split(dbAuthMethod.AllowEmailDomain, entity.CommaSeparator)
		}
		if dbAuthMethod.Type == entity.AuthMethodTypeOIDC {
			var oidcConfig *central.OidcConfig
			if dbAuthMethod.Content == "" {
				oidcConfig = &central.OidcConfig{}
			} else {
				oidcConfig, err = svc.GetOidcConfigFromContent(ctx, dbAuthMethod.Content)
				if err != nil {
					return nil, err
				}
			}

			//oidcConfig.CallbackUrl = getOidcCallBackUrl()
			authMethodBean.OidcConfig = oidcConfig
		}
		if dbAuthMethod.Type == entity.AuthMethodTypeSAML {
			var samlConfig *central.SamlConfig
			if dbAuthMethod.Content == "" {
				samlConfig = &central.SamlConfig{}
			} else {
				samlConfig, err = svc.GetSamlConfigFromContent(ctx, dbAuthMethod.Content)
				if err != nil {
					return nil, err
				}
			}
			authMethodBean.SamlConfig = samlConfig
		}

	}

	return &central.ListAuthMethodsRsp{
		AuthMethods:                      respList,
		CallbackUrl:                      callbackUrl,
		SamlPostBackUrl:                  samlPostBackUrl,
		SamlEntityId:                     samlEntityId,
		ScimConnectorBaseUrl:             scimBaseUrl,
		ScimUniqueIdentifierField:        scimUserUniqueField,
		ScimSupportedProvisioningActions: scimSupportProvisionActions,
	}, err
}

func getOidcCallBackUrl() string {
	//return fmt.Sprintf(oidcCallbackUrlFormat, config.Global().SSOConfig.Auth0CallbackDomain)
	var callBackUrl = ""
	if config.IsDevEnv() || config.IsLocalEnv() {
		callBackUrl = fmt.Sprintf(oidcCallbackUrlFormat, auth0DomainDev)
	} else if config.IsStagingEnv() {
		callBackUrl = fmt.Sprintf(oidcCallbackUrlFormat, auth0DomainStaging)
	} else if config.IsProdEnv() {
		callBackUrl = fmt.Sprintf(oidcCallbackUrlFormat, auth0DomainProd)
	}
	return callBackUrl
}

func getSamlCallBackUrl(connectionName string) string {
	//return fmt.Sprintf(samlPostBackUrlFormat, config.Global().SSOConfig.Auth0CallbackDomain, connectionName)
	var callBackUrl = ""
	if config.IsDevEnv() || config.IsLocalEnv() {
		callBackUrl = fmt.Sprintf(samlPostBackUrlFormat, auth0DomainDev, connectionName)
	} else if config.IsStagingEnv() {
		callBackUrl = fmt.Sprintf(samlPostBackUrlFormat, auth0DomainStaging, connectionName)
	} else if config.IsProdEnv() {
		callBackUrl = fmt.Sprintf(samlPostBackUrlFormat, auth0DomainProd, connectionName)
	}
	return callBackUrl
}

func (s *OrgAuthMethodService) GetEmailDomains(ctx context.Context, req *central.GetEmailDomainsReq) (*central.GetEmailDomainsRsp, error) {
	svc, _ := orgsso2.GetOrgSsoSvc()

	if req.OrgId == 0 {
		return nil, errs.NewBadRequest("org_id must not null", nil)
	}

	filter := sso.OrgAuthMethodFilter{
		OrgIds: []uint64{req.OrgId},
		Status: []entity.EntityStatus{entity.Enable},
	}

	authMethods, err := svc.ListAuthMethods(ctx, filter)
	if err != nil {
		return nil, err
	}

	emailDomains := make([]string, 0, len(authMethods))
	for _, m := range authMethods {
		arr := strings.Split(m.AllowEmailDomain, ",")
		emailDomains = slice.Concat(emailDomains, arr)
	}
	tilog.Info(ctx, fmt.Sprintf("%d, GetEmailDomains result", req.OrgId), zap.Any("emailDomains", emailDomains))
	resp := &central.GetEmailDomainsRsp{EmailDomains: emailDomains}

	return resp, err
}

func (s *OrgAuthMethodService) CheckAuthMethod(ctx context.Context, req *central.CheckAuthMethodReq) (*central.CheckAuthMethodRsp, error) {
	if req.Name == "" {
		return nil, utils.ConvertErr2GrpcErr(errs.NewBadRequest("name is empty", nil))
	}
	svc, _ := orgsso2.GetOrgSsoSvc()
	valid, err := svc.CheckAuthMethod(ctx, req)
	if err != nil {
		return nil, err
	}
	return &central.CheckAuthMethodRsp{
		Valid: valid,
	}, nil

}

func (s *OrgAuthMethodService) CreateBearerToken(ctx context.Context, req *central.CreateBearerTokenReq) (*central.BearerToken, error) {
	if req.Description == "" {
		return nil, errs.NewBadRequest("description is blank", nil)
	}
	if req.OrgId == 0 {
		return nil, errs.NewBadRequest("org_id is invalid", nil)
	}
	svc, _ := orgsso2.GetOrgSsoSvc()
	rsp, err := svc.CreateBearerToken(ctx, req)
	return rsp, err
}

func (s *OrgAuthMethodService) ListBearerTokens(ctx context.Context, req *central.ListBearerTokensReq) (*central.BearerTokens, error) {
	if req.OrgId == 0 {
		return nil, errs.NewBadRequest("org_id is invalid", nil)
	}
	svc, _ := orgsso2.GetOrgSsoSvc()
	rsp, err := svc.ListBearerTokens(ctx, req)
	return rsp, err
}
