package cluster

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/samber/lo"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	pkgclusterproxy "github.com/tidbcloud/pkgv2/clusterproxy"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/clusterproxy"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

const (
	GetLogRedactionPolicySQL       = "show global variables like '%tidb_redact_log%'"
	setLogRedactionPolicySQL       = "set @@global.tidb_redact_log = %d"
	logRedactionPolicyVariableName = "tidb_redact_log"
)

func (s *serviceImpl) GetLogRedactionPolicy(
	ctx context.Context,
	clusterId uint64,
) (*globaldedicatedv1.LogRedactionPolicy, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.GetLogRedactionPolicy")
	defer span.End()

	logRedaction := &globaldedicatedv1.LogRedactionPolicy{
		ClusterId: clusterId,
		// default to true
		RedactLog: lo.ToPtr(true),
	}
	err := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		cluster, err := s.clusterRepo.Get(ctx, clusterId, false)
		if err != nil {
			return err
		}
		if cluster.State != globaldedicatedv1.Cluster_AVAILABLE &&
			cluster.State != globaldedicatedv1.Cluster_SCALING &&
			cluster.State != globaldedicatedv1.Cluster_MODIFYING {
			return bizerr.New(
				bizerr.ErrCodeParams,
				"log redaction policy can only be shown when the cluster is available or modifying",
			)
		}

		region, err := s.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.OrganizationId)
		if err != nil {
			return err
		}
		if region.State != globalcommonv1.Region_ACTIVE {
			return bizerr.New(bizerr.ErrCodePermission, "region is not active")
		}

		getReq := &regionaldedicatedv1.GetClusterRequest{
			ClusterId:      strconv.FormatUint(cluster.ClusterId, 10),
			ShowStatusOnly: false,
		}

		regionCluster, err := s.regionServerCli.GetCluster(ctx, region, getReq)
		if err != nil {
			return err
		}
		cluster = s.mergeCluster(ctx, cluster, nil, regionCluster, nil, nil, nil, nil)

		cloudAdminPwd, err := s.encryptor.DecryptBase64(ctx, cluster.EncryptedCloudAdminPassword)
		if err != nil {
			return err
		}

		// Set cloud admin password via cluster proxy.
		proxy, err := s.clusterProxyProvider.GetClusterProxy(
			ctx,
			cluster.ClusterId,
			cluster.ProxyBaseUrl,
			cluster.Version,
			&pkgclusterproxy.ClusterOperator{
				Username:   clusterproxy.CloudAdminUser,
				AuthMethod: pkgclusterproxy.AuthMethodPassword,
				Credential: cloudAdminPwd,
			},
		)
		if err != nil {
			return err
		}
		sqlResult, err := proxy.QuerySQL(ctx, GetLogRedactionPolicySQL)
		if err != nil {
			return err
		}

		type logRedactionResult struct {
			Value []string `json:"Value"`
		}

		var result logRedactionResult
		if err := json.Unmarshal(sqlResult, &result); err != nil {
			return bizerr.WrapErrorWithCode(err, bizerr.ErrCodeJsonMarshal, "failed to unmarshal log redaction policy")
		}
		if len(result.Value) != 1 {
			return bizerr.New(bizerr.ErrCodeParams, "failed to get log redaction policy")
		}

		decodeRet, nerr := base64.StdEncoding.DecodeString(result.Value[0])
		if nerr != nil {
			return bizerr.WrapError(nerr, "failed to decode log redaction policy")
		}
		logRedaction.RedactLog = lo.Ternary(
			string(decodeRet) == "OFF" || string(decodeRet) == "off",
			lo.ToPtr(false),
			lo.ToPtr(true),
		)

		return nil
	})
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to show log redaction policy")
	}
	return logRedaction, nil
}

func (s *serviceImpl) UpdateLogRedactionPolicy(
	ctx context.Context,
	req *globaldedicatedv1.UpdateLogRedactionPolicyRequest,
) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "Domain.UpdateLogRedactionPolicy")
	defer span.End()

	logRedaction := req.GetLogRedactionPolicy()
	if err := validateLogRedaction(logRedaction); err != nil {
		return err
	}
	err := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		cluster, err := s.clusterRepo.Get(ctx, logRedaction.GetClusterId(), false)
		if err != nil {
			return err
		}
		if cluster.State != globaldedicatedv1.Cluster_AVAILABLE &&
			cluster.State != globaldedicatedv1.Cluster_SCALING &&
			cluster.State != globaldedicatedv1.Cluster_MODIFYING {
			return bizerr.New(
				bizerr.ErrCodeParams,
				"log redaction policy can only be updated when the cluster is available or modifying",
			)
		}
		region, err := s.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.OrganizationId)
		if err != nil {
			return err
		}
		if region.State != globalcommonv1.Region_ACTIVE {
			return bizerr.New(bizerr.ErrCodePermission, "region is not active")
		}

		getReq := &regionaldedicatedv1.GetClusterRequest{
			ClusterId:      strconv.FormatUint(cluster.ClusterId, 10),
			ShowStatusOnly: false,
		}

		regionCluster, err := s.regionServerCli.GetCluster(ctx, region, getReq)
		if err != nil {
			return err
		}
		cluster = s.mergeCluster(ctx, cluster, nil, regionCluster, nil, nil, nil, nil)

		cloudAdminPwd, err := s.encryptor.DecryptBase64(ctx, cluster.EncryptedCloudAdminPassword)
		if err != nil {
			return err
		}

		proxy, err := s.clusterProxyProvider.GetClusterProxy(
			ctx,
			cluster.ClusterId,
			cluster.ProxyBaseUrl,
			cluster.Version,
			&pkgclusterproxy.ClusterOperator{
				Username:   clusterproxy.CloudAdminUser,
				AuthMethod: pkgclusterproxy.AuthMethodPassword,
				Credential: cloudAdminPwd,
			},
		)
		if err != nil {
			return err
		}
		setSql := fmt.Sprintf(setLogRedactionPolicySQL, lo.Ternary(logRedaction.GetRedactLog(), 1, 0))
		if err := proxy.ExecuteSQL(ctx, setSql); err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return bizerr.WrapError(err, "failed to update log redaction policy")
	}
	return nil
}
func validateLogRedaction(logRedaction *globaldedicatedv1.LogRedactionPolicy) bizerr.BizError {
	if logRedaction == nil {
		return bizerr.New(bizerr.ErrCodeParams, "log redaction policy is required")
	}

	if logRedaction.ClusterId == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "cluster id is required")
	}

	if logRedaction.RedactLog == nil {
		return bizerr.New(bizerr.ErrCodeParams, "log redaction policy is required")
	}

	return nil
}
