package org

import (
	"context"
	"strconv"

	"github.com/tidbcloud/account/internel/controllers/acl/access/transformer"
	"github.com/tidbcloud/account/internel/controllers/dto"
	"github.com/tidbcloud/account/internel/svc/msp"
	orgSvc "github.com/tidbcloud/account/internel/svc/org"
	orgSsoSvc "github.com/tidbcloud/account/internel/svc/orgsso"
	projectsvc "github.com/tidbcloud/account/internel/svc/project"
	"github.com/tidbcloud/account/internel/svc/rbac"
	"github.com/tidbcloud/account/internel/svc/user"
	"google.golang.org/protobuf/types/known/timestamppb"

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

	"github.com/jinzhu/copier"
	"github.com/tidbcloud/account/internel/dao/org"

	"github.com/tidbcloud/account/idl/pbgen/proto/acl"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/dao/role"
	"github.com/tidbcloud/tiinf/errs"
	"google.golang.org/protobuf/types/known/emptypb"

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

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

var _ central.TenantServiceServer = (*OrgController)(nil)

type OrgController struct {
	central.UnimplementedTenantServiceServer
}

var GlobalOrgController = NewOrgController()

func NewOrgController() *OrgController {
	return &OrgController{}
}

func (s *OrgController) GetUserProfile(ctx context.Context, req *central.OrgIdReq) (*central.UserProfile, error) {
	//if err := checkOrgID(ctx, req.OrgId); err != nil {
	//	return nil, err
	//}

	// WARN: DO NOT use user struct from compatibility layer directly
	tenantUser, err := user.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	userSvc, _ := user.GetUserSvc()
	user, err := userSvc.GetUserByID(ctx, tenantUser.ID.Uint64())
	if err != nil {
		return nil, err
	}

	projectSvc, _ := projectsvc.GetProjectSvc()
	var projects []entity.Project
	if tenantUser.Role == acl.OrgRole_ORG_ROLE_OWNER || tenantUser.IsImpersonated {
		//listAllProjects
		if projects, err = projectSvc.ListActiveProjectsByOrgID(ctx, req.OrgId); err != nil {
			return nil, err
		}
	} else {

		if projects, err = projectSvc.ListUserVisibleProjects(ctx, tenantUser.ID.Uint64(), []uint64{req.OrgId}); err != nil {
			return nil, err
		}
	}

	org, err := userSvc.GetUserOrg(ctx)
	if err != nil {
		return nil, err
	}

	// HACK(iven): Use role from GetUserAccess, so we don't need to query it from role_bindings table.
	user.Role = transformer.OrgRoleReverseMap[tenantUser.Role]

	userProfile := dto.ConvertUserProfile(user, projects, org)

	var rbacRole string
	if tenantUser.IsImpersonated {
		rbacRole = entity.RoleOrgOwner
	} else {
		rbacRole, err = s.getUserRbacRole(ctx, user.ID.Uint64(), entity.RoleBindingScopeOrg, org.ID.Uint64())
		if err != nil {
			return nil, err
		}
	}

	userProfile.RbacRole = rbacRole

	// convert user entity to central.UserProfile

	// is new user
	isNewUser := false
	userId := user.ID.Uint64()
	isNewUser, err = userSvc.IsNewUser(ctx, &userId)
	tilog.Info(ctx, "get is new user", zap.Any("isNewUser", isNewUser), zap.Any("email", user.Email), zap.Any("userId", userId))
	if err != nil {
		return nil, err
	}
	userProfile.IsNewUser = isNewUser
	if isNewUser {
		obj, err := userSvc.GetNewUserDefaultCluster(ctx, &userId, nil)
		if err != nil {
			tilog.Error(ctx, "GetNewUserDefaultCluster fail", zap.Error(err))
		}
		if obj != nil {
			userProfile.DefaultClusterId = obj.TargetID.Uint64()
			tilog.Info(ctx, "GetNewUserDefaultCluster  result ", zap.Any("userId", userId), zap.Any("DefaultClusterId", obj.TargetID.Uint64()))

		}
	}

	userProfile.IsImpersonatedUser = tenantUser.IsImpersonated

	return userProfile, nil
}

func (s *OrgController) getUserRbacRole(ctx context.Context, userID uint64, scopeType entity.ScopeType, scopeID uint64) (string, error) {
	svc, err := rbac.GetRbacRoleBindingSvc()
	if err != nil {
		return "", err
	}
	bindings, err := svc.ListRoleBindings(ctx, role.RoleBindingFilter{
		ScopeTypes: []entity.ScopeType{scopeType},
		ScopeIDs:   []entity.ID{entity.ID(scopeID)},
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		TargetIDs:  []entity.ID{entity.ID(userID)},
	})
	if err != nil {
		return "", err
	}
	if len(bindings) == 0 {
		return "", errs.NewInternalDB("not found role_binding", nil)
	}
	binding := bindings[0]
	return binding.Role, nil
}

func (s *OrgController) OpenOrgSso(ctx context.Context, req *central.OpenOrgSsoReq) (*emptypb.Empty, error) {
	bm := utils.Begin(ctx, "OpenOrgSso", zap.Any("req", req))
	defer bm.OnExit(ctx)
	var err error
	orgSsoSvc, _ := orgSsoSvc.GetOrgSsoSvc()
	if err = orgSsoSvc.OpenOrgSso(ctx, req); err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, err
}

func (s *OrgController) GetMspConfig(ctx context.Context, req *central.GetMspConfigReq) (*central.GetMspConfigResp, error) {
	bm := utils.Begin(ctx, "GetMspConfig", zap.Any("req", req))
	defer bm.OnExit(ctx)
	resp := &central.GetMspConfigResp{}
	var err error

	orgFilter := org.ListOrgsFilter{Name: []string{req.MspName}, OrgTag: []string{string(entity.OrgTagMsp)}, Status: []string{string(entity.Active)}}
	orgSvc, _ := orgSvc.GetOrgSvc()
	orgs, err := orgSvc.ListOrgsByFilter(ctx, orgFilter)
	if err != nil {
		return nil, err
	}
	if len(orgs) == 1 {
		mspSvc, _ := msp.GetMspSvc()
		mspOrg := orgs[0]
		mspConfig, err := mspSvc.GetMspConfigByOrgID(ctx, mspOrg.ID)
		if err != nil {
			return nil, err
		}
		if mspConfig != nil {
			resp.Disclaimers = config.MspDisclaimers

			//if mspConfig.LogoName != "" {
			//	domain := config.Global().MspLogoConfig.MspCloudfrontDomain
			//	if config.IsDevEnv() {
			//		domain = fmt.Sprintf("%s%s", "https://", domain)
			//	}
			//	logoUrl := fmt.Sprintf("%s/msp/%s", domain, mspConfig.LogoName)
			//	tilog.Info(ctx, fmt.Sprintf("config.Global().MspLogoConfig.MspCloudfrontDomain=%s", config.Global().MspLogoConfig.MspCloudfrontDomain))
			//}
			resp.LogoUrl = mspSvc.GetMspLogUrl(ctx, mspConfig.LogoName)

			if mspConfig.Advertising != "" {

				arr, err := mspSvc.JsonStrToAdvertisingArr(ctx, mspConfig.Advertising)
				if err != nil {
					return nil, err
				}
				var advertisingArr []*central.Advertising
				for _, advertising := range arr {
					bean := &central.Advertising{}
					_ = copier.Copy(bean, advertising)
					advertisingArr = append(advertisingArr, bean)
				}

				//slice.SortByField(advertisingArr, "Index", "asc")
				resp.Advertisings = advertisingArr
			}

		}
	}
	tilog.Info(ctx, "GetMspCustomer result", zap.Any("resp", resp))
	return resp, err
}

func (s *OrgController) GetMspCustomer(ctx context.Context, req *central.GetMspCustomerReq) (*central.GetMspCustomerResp, error) {
	bm := utils.Begin(ctx, "GetMspCustomer", zap.Any("req", req))
	defer bm.OnExit(ctx)
	resp := &central.GetMspCustomerResp{}

	var err error
	// 1、 find org name
	orgSvc, _ := orgSvc.GetOrgSvc()
	customerOrg, err := orgSvc.GetOrgByID(ctx, req.OrgId)
	if err != nil {
		return nil, err
	}

	if customerOrg == nil || customerOrg.OrgTag != string(entity.OrgTagMspCustomer) {
		return nil, utils.ConvertErr2GrpcErr(errs.NewBadRequest("org_id is invalid", nil))
	}
	mspOrgIdStr := customerOrg.ThirdPartyAccountCode
	if mspOrgIdStr != nil {

		mspOrgId, _ := strconv.ParseUint(*mspOrgIdStr, 10, 64)
		mspOrg, err := orgSvc.GetOrgByID(ctx, mspOrgId)
		if err != nil {
			return nil, err
		}
		resp.Name = mspOrg.Name
	}

	// 2、 find customer EffectedAt
	mspSvc, _ := msp.GetMspSvc()
	customer, err := mspSvc.GetCustomerByCustomerID(ctx, customerOrg.ID)
	if err != nil {
		return nil, err
	}
	if customer.EffectedAt != nil {
		resp.EffectedAt = timestamppb.New(*(customer.EffectedAt))
	}

	// 3、 find msp logo
	mspConfig, err := mspSvc.GetMspConfigByOrgID(ctx, customer.PartnerOrgID)
	if err != nil {
		return nil, err
	}
	if mspConfig != nil {

		resp.Website = mspConfig.Website
		resp.LogoUrl = mspSvc.GetMspLogUrl(ctx, mspConfig.LogoName)
	}

	tilog.Info(ctx, "GetMspCustomer result", zap.Any("resp", resp))
	return resp, err
}

func (s *OrgController) GetPartnerByCustomerId(ctx context.Context, req *central.GetPartnerByCustomerIdReq) (*central.GetPartnerByCustomerIdResp, error) {
	bm := utils.Begin(ctx, "GetPartnerByCustomerId", zap.Any("req", req))
	defer bm.OnExit(ctx)
	resp := &central.GetPartnerByCustomerIdResp{}

	var err error

	orgSvc, _ := orgSvc.GetOrgSvc()
	customerOrg, err := orgSvc.GetOrgByID(ctx, req.OrgId)
	if err != nil {
		return nil, err
	}

	//if customerOrg == nil || (customerOrg.OrgTag != string(entity.OrgTagMspCustomer) && customerOrg.OrgTag != string(entity.OrgTagAwsCppoCustomer)) {
	//
	//	return nil, utils.ConvertErr2GrpcErr(errs.NewBadRequest("org_id is invalid", nil))
	//}

	mspSvc, _ := msp.GetMspSvc()
	customer, err := mspSvc.GetCustomerByCustomerID(ctx, customerOrg.ID)
	if err != nil {
		return nil, err
	}
	// When org is not a customer org, t will cause a null pointer panic. Add customer nil check
	if customer == nil {
		return nil, utils.ConvertErr2GrpcErr(errs.NewBadRequest("request org is not a customer org", nil))
	}
	// 1、 find partner org name
	partnerOrg, err := orgSvc.GetOrgByID(ctx, customer.PartnerOrgID.Uint64())
	if err != nil {
		return nil, err
	}
	resp.Name = partnerOrg.Name

	// 2、 find customer EffectedAt
	if customer.EffectedAt != nil {
		resp.EffectedAt = timestamppb.New(*(customer.EffectedAt))
	}

	// 3、 if msp ,find msp logo
	if customer.PartnerType == string(entity.PartnerTypeMsp) {
		mspConfig, err := mspSvc.GetMspConfigByOrgID(ctx, customer.PartnerOrgID)
		if err != nil {
			return nil, err
		}
		if mspConfig != nil {

			resp.Website = mspConfig.Website
			resp.LogoUrl = mspSvc.GetMspLogUrl(ctx, mspConfig.LogoName)
		}
	}

	tilog.Info(ctx, "GetPartnerByCustomerId result", zap.Any("resp", resp))
	return resp, err
}
