package account

import (
	"context"

	"github.com/tidbcloud/dataflow-service/internal/bizutils"
	grpc_http "github.com/tidbcloud/dataflow-service/internal/grpc/http"
	"github.com/tidbcloud/dataflow-service/pkg/account/accountdb"
	"github.com/tidbcloud/dataflow-service/pkg/account/accounttypes"
	bizerr_wrapper "github.com/tidbcloud/dataflow-service/pkg/bizerr-wrapper"
	"github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli"
	"github.com/tidbcloud/dataflow-service/pkg/log"
	"github.com/tidbcloud/dataflow-service/pkg/parse"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.uber.org/zap"
	"google.golang.org/grpc"
)

func GetUserFromContext(ctx context.Context) *accountdb.User {
	user, err := bizutils.GetUserFromContext(ctx)
	if err != nil {
		panic("expected user claim")
	}
	return user
}

func CheckOrgIDAndProjectID(ctx context.Context, orgID, projectID uint64) bizerr.BizError {
	err := CheckOrgID(ctx, orgID)
	if err != nil {
		return err
	}
	return CheckProjectID(ctx, projectID)
}

func CheckOrgID(ctx context.Context, orgID uint64) bizerr.BizError {
	user := GetUserFromContext(ctx)
	if user.IsImpersonated {
		return nil
	}

	if orgID != user.TenantID {
		return bizerr_wrapper.NewForbiddenErr("org_id %d is inconsistent with the user's org ID", orgID)
	}
	return nil
}

func CheckProjectID(ctx context.Context, projectID uint64) bizerr.BizError {
	user := GetUserFromContext(ctx)
	if userInProject(user.Projects, projectID) {
		return nil
	}
	return bizerr_wrapper.NewForbiddenErr("project_id %d is inconsistent with the user's project ID", projectID)
}

func userInProject(userProjects []accounttypes.Project, projectID uint64) bool {
	for _, project := range userProjects {
		if project.ID == projectID {
			return true
		}
	}
	return false
}

func UserCheckerUnaryServerInterceptor(coreCli grpcli.CoreGrpcClient) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		logger := log.GetLogger(ctx)
		url := grpc_http.HttpURI(ctx)
		orgID, hasOrgID := parse.ParseOrgIDFromPath(url)
		if hasOrgID {
			if err := CheckOrgID(ctx, orgID); err != nil {
				logger.Warn("check org id failed", zap.Any("orgID", orgID), zap.Error(err))
				return ctx, err
			}
		}

		projectID, hasProjectID := parse.ParseProjectIDFromPath(url)
		if hasProjectID {
			if err := CheckProjectID(ctx, projectID); err != nil {
				logger.Warn("check project id failed", zap.Any("project", projectID), zap.Error(err))
				return ctx, err
			}
		}

		clusterID, hasClusterID := parse.ParseClusterIDFromPath(url)
		if !hasClusterID {
			return handler(ctx, req)
		}
		cluster, bizError := coreCli.GetAnyClusterByID(ctx, clusterID)
		if bizError != nil {
			logger.Error("get cluster by id failed", zap.Error(bizError))
			return nil, bizError
		}
		if err := CheckOrgID(ctx, cluster.TenantId); err != nil {
			logger.Warn("check org id failed", zap.Any("orgID", orgID), zap.Error(err))
			return ctx, err
		}
		if err := CheckProjectID(ctx, cluster.ProjectId); err != nil {
			logger.Warn("check org id failed", zap.Any("orgID", orgID), zap.Error(err))
			return ctx, err
		}
		return handler(ctx, req)
	}
}
