package openapi

import (
	"context"
	"fmt"
	"strings"

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

	orgdao "github.com/tidbcloud/account/internel/dao/org"
	"github.com/tidbcloud/account/internel/svc/rbac"

	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/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auth"
	"go.uber.org/zap"
)

type OrgService struct {
	openapiv1veta1.UnimplementedOrgServiceServer
}

func (s *OrgService) GetOrg(ctx context.Context, req *openapiv1veta1.GetOrgRequest) (*openapiv1veta1.Org, error) {
	tilog.Info(ctx, "GetOrg request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "GetOrg getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID
	tilog.Info(ctx, "GetOrg get authOrgId", zap.Any("authOrgId", authOrgId))

	orgSvc, _ := orgsvc.GetOrgSvc()
	org, err := orgSvc.GetOrgByID(ctx, authOrgId)
	if err != nil {
		tilog.Error(ctx, "GetOrg GetOrgByID error", zap.Any("orgId", authOrgId), zap.Any("err", err))
		return nil, errs.NewInternalUnknown(err.Error(), err)
	}
	if org.Status != entity.TenantStatusActive {
		tilog.Info(ctx, "GetOrg org is not active", zap.Any("orgId", authOrgId))
		return nil, errs.NewBadRequest("org is not active", err)
	}

	rsp := convEntity2OpenApiOrg(org)
	tilog.Info(ctx, "GetOrg response", zap.Any("rsp", rsp))
	return rsp, nil
}

func (s *OrgService) ListPartnerOrgs(ctx context.Context, req *openapiv1veta1.ListPartnerOrgsRequest) (*openapiv1veta1.ListPartnerOrgsResponse, error) {
	tilog.Info(ctx, "ListOrgs request", zap.Any("req", req))
	identInfo, err := auth.GetIdentityInfoFromGrpcContext(ctx)
	if err != nil {
		tilog.Error(ctx, "ListOrgs GetIdentityInfoFromGrpcContext error", zap.Any("err", err))
		return nil, errs.NewInternalUnknown(err.Error(), err)
	}
	canAccessOrgIds, err := listIdentityCanAccessOrgIds(ctx, identInfo)
	if err != nil {
		tilog.Error(ctx, "ListOrgs listIdentityCanAccessOrgIds error", zap.Any("err", err))
		return nil, err
	}
	tilog.Info(ctx, "ListOrgs get identity can access orgIds", zap.Any("orgIds", canAccessOrgIds))
	if len(canAccessOrgIds) == 0 {
		return nil, errs.NewForbidden("identity have no org to access", nil)
	}
	svc, _ := orgsvc.GetOrgSvc()
	orgs, err := svc.ListOrgsByFilter(ctx, orgdao.ListOrgsFilter{
		ID:     canAccessOrgIds,
		OrgTag: []string{string(entity.OrgTagMsp), string(entity.OrgTagAwsCppo), string(entity.OrgTagGcpMCPOReseller), string(entity.OrgTagResalePartner), string(entity.OrgTagAlicloud)},
	})
	if err != nil {
		tilog.Error(ctx, "ListOrgs ListOrgsByFilter error", zap.Any("orgIds", canAccessOrgIds), zap.Any("err", err))
		return nil, errs.NewInternalUnknown(err.Error(), err)
	}

	var returnOrgs []*openapiv1veta1.Org
	for _, org := range orgs {
		if org.Status != entity.TenantStatusActive {
			continue
		}
		convOrg := convEntity2OpenApiOrg(&org)
		returnOrgs = append(returnOrgs, convOrg)
	}

	rsp := &openapiv1veta1.ListPartnerOrgsResponse{
		Orgs:      returnOrgs,
		TotalSize: int32(len(orgs)),
	}
	tilog.Info(ctx, "ListOrgs response", zap.Any("rsp", rsp))
	return rsp, nil
}

func getOrgCreatorEmailDomain(creatorEmail string) string {
	index := strings.Index(creatorEmail, "@")
	if index == -1 {
		return ""
	}
	return creatorEmail[index+1:]
}

func listIdentityCanAccessOrgIds(ctx context.Context, ident *auth.AuthnIdentityInfo) ([]uint64, error) {
	if ident.IdentityType == auth.AuthnIdentityTypeAccessToken {
		return []uint64{ident.AccessTokenRsp.AccessTokenIdentity.OrgId}, nil
	}
	if ident.IdentityType == auth.AuthnIdentityTypeApiKeyAK {
		var orgIds []uint64
		for _, item := range ident.ApiKeyRsp.ApiKey.ResourceInfos {
			if item.GetScopeType() == entity.RoleBindingScopeOrg.String() {
				orgIds = append(orgIds, item.ResourceId)
			}
		}
		return orgIds, nil
	}
	if ident.IdentityType == auth.AuthnIdentityTypeUserToken {
		userId := ident.UserTokenRsp.User.Id
		svc, _ := rbac.GetRbacRoleBindingSvc()
		return svc.GetUserOrgIds(ctx, userId)
	}
	return nil, errs.NewForbidden("unknown authn identity type", nil)
}

func convEntity2OpenApiOrg(org *entity.Tenant) *openapiv1veta1.Org {
	return &openapiv1veta1.Org{
		OrgId:             fmt.Sprintf("%d", org.ID.Uint64()),
		OrgName:           org.Name,
		Email:             org.Email,
		OrgTag:            org.OrgTag,
		CreateTime:        formatTime(org.CreatedAt.Local()),
		EmailDomain:       getOrgCreatorEmailDomain(org.Email),
		AllowEmailDomains: getPartnerAllowEmailDomains(org.ID.Uint64(), org.Email),
	}
}

func getPartnerAllowEmailDomains(orgId uint64, orgEmail string) []string {
	creatorEmailDomain := getOrgCreatorEmailDomain(orgEmail)

	allowDomainsMap := viper.GetStringMapStringSlice("partner.allowEmailDomains")
	tilog.Info(context.Background(), "oneconfig get partner allow email domains", zap.Any("email_domains", allowDomainsMap))
	allowDomains := allowDomainsMap[fmt.Sprintf("%d", orgId)]
	if !slice.Contain(allowDomains, creatorEmailDomain) {
		allowDomains = append(allowDomains, creatorEmailDomain)
	}

	return allowDomains
}
