package auditlog

import (
	"context"
	"strconv"

	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	pkgclusterproxy "github.com/tidbcloud/pkgv2/clusterproxy"
	"github.com/tidbcloud/pkgv2/db"
	"go.opentelemetry.io/otel"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/account"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/clusterproxy"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/remoteencryptor"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	auditlogrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/auditlog"
	clusterrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	regionrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

var (
	V2MinVersion = "v7.1.0"
	tracer       = otel.Tracer("domain.auditlog")
)

type Service interface {
	GetAuditLogConfig(ctx context.Context, clusterID uint64) (*globaldedicatedv1.AuditLogConfig, bizerr.BizError)
	UpdateAuditLogConfig(
		ctx context.Context,
		config *globaldedicatedv1.AuditLogConfig,
		validateOnly bool,
	) (*globaldedicatedv1.AuditLogConfig, bizerr.BizError)

	ListAuditLogFilterRules(
		ctx context.Context,
		clusterID uint64,
	) ([]*globaldedicatedv1.AuditLogFilterRule, bizerr.BizError)
	CreateAuditLogFilterRule(
		ctx context.Context,
		rule *globaldedicatedv1.AuditLogFilterRule,
	) (*globaldedicatedv1.AuditLogFilterRule, bizerr.BizError)
	DeleteAuditLogFilterRule(
		ctx context.Context,
		request *globaldedicatedv1.DeleteAuditLogFilterRuleRequest,
	) bizerr.BizError
	ReplaceAuditLogFilterRule(
		ctx context.Context,
		rule *globaldedicatedv1.AuditLogFilterRule,
	) (*globaldedicatedv1.AuditLogFilterRule, bizerr.BizError)

	GetAccount(ctx context.Context, projectId uint64) (string, bizerr.BizError)

	QueryAuditLogFiles(
		ctx context.Context,
		req *globaldedicatedv1.QueryAuditLogFilesRequest,
	) (*globaldedicatedv1.QueryAuditLogFilesResponse, bizerr.BizError)
	GenerateAuditLogFileDownloadAddress(
		ctx context.Context,
		req *globaldedicatedv1.GenerateAuditLogFileDownloadAddressRequest,
	) (*globaldedicatedv1.GenerateAuditLogFileDownloadAddressResponse, bizerr.BizError)

	GetCloudAdminClusterProxy(
		ctx context.Context,
		cluster *entity.Cluster,
		regionEn *entity.Region,
	) (clusterproxy.ClusterProxy, bizerr.BizError)
}

type service struct {
	clusterRepo            clusterrepo.Repo
	regionRepo             regionrepo.Repo
	auditLogConfigRepo     auditlogrepo.ConfigRepo
	auditLogFilterRuleRepo auditlogrepo.FilterRuleRepo
	regionServerCli        regionserver.Client
	accountCli             account.Client
	clusterProxyProvider   clusterproxy.Provider
	encryptor              remoteencryptor.Client
	wf                     workflow.Workflow
	transaction            db.Transactioner
}

func NewService(
	clusterRepo clusterrepo.Repo,
	regionRepo regionrepo.Repo,
	auditLogConfigRepo auditlogrepo.ConfigRepo,
	auditLogFilterRuleRepo auditlogrepo.FilterRuleRepo,
	regionServerCli regionserver.Client,
	accountCli account.Client,
	clusterProxyProvider clusterproxy.Provider,
	encryptor remoteencryptor.Client,
	wf workflow.Workflow,
	transaction db.Transactioner,
) Service {
	return &service{
		clusterRepo:            clusterRepo,
		regionRepo:             regionRepo,
		auditLogConfigRepo:     auditLogConfigRepo,
		auditLogFilterRuleRepo: auditLogFilterRuleRepo,
		regionServerCli:        regionServerCli,
		accountCli:             accountCli,
		clusterProxyProvider:   clusterProxyProvider,
		encryptor:              encryptor,
		wf:                     wf,
		transaction:            transaction,
	}
}

func (svc *service) GetCloudAdminClusterProxy(
	ctx context.Context,
	cluster *entity.Cluster,
	regionEn *entity.Region,
) (clusterproxy.ClusterProxy, bizerr.BizError) {
	// Get audit log status from cluster via cluster proxy.
	cloudAdminPwd, err := svc.encryptor.DecryptBase64(ctx, cluster.EncryptedCloudAdminPassword)
	if err != nil {
		return nil, err
	}

	regionCluster, err := svc.regionServerCli.GetCluster(
		ctx,
		regionEn,
		&regionaldedicatedv1.GetClusterRequest{
			ClusterId:      strconv.FormatUint(cluster.ClusterId, 10),
			ShowStatusOnly: false,
		},
	)
	if err != nil {
		return nil, err
	}

	cluster.ProxyBaseUrl = regionCluster.ProxyBaseUrl
	proxy, err := svc.clusterProxyProvider.GetClusterProxy(
		ctx,
		cluster.ClusterId,
		cluster.ProxyBaseUrl,
		cluster.Version,
		&pkgclusterproxy.ClusterOperator{
			Username:   clusterproxy.CloudAdminUser,
			AuthMethod: pkgclusterproxy.AuthMethodPassword,
			Credential: cloudAdminPwd,
		},
	)
	if err != nil {
		return nil, err
	}

	return proxy, nil
}

func (svc *service) GetAccount(ctx context.Context, clusterId uint64) (string, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "GetAccount")
	defer span.End()

	// Get the cluster and region.
	cluster, err := svc.clusterRepo.Get(ctx, clusterId, false)
	if err != nil {
		return "", err
	}
	region, err := svc.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.OrganizationId)
	if err != nil {
		return "", err
	}

	a, err := svc.regionServerCli.GetAccount(ctx, region, &regionaldedicatedv1.GetAccountRequest{
		ProjectId: strconv.FormatUint(cluster.ProjectId, 10),
	})
	if err != nil {
		return "", err
	}

	return a.GetAccountId(), nil
}
