package tidbcluster

import (
	"context"
	"fmt"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
)

func (r *clusterRepoImp) cvtClusterToModel(ctx context.Context, cluster *entity.TiDBCluster) (*schema.TidbCluster, error) {
	if cluster == nil {
		return nil, fmt.Errorf("cluster model cannot be nil")
	}

	auditlogStr, err := protojson.Marshal(cluster.GetAuditLogSetting())
	if err != nil {
		tilog.Error(ctx, "failed to marshal audit log setting", zap.String("clusterId", cluster.GetId()), zap.Error(err))
		return nil, status.Errorf(codes.Internal, "failed to marshal audit log setting: %s", err)
	}

	capacityMode := cluster.GetCapacityMode().String()
	if capacityMode == apiv1.CapacityMode_CAPACITY_MODE_UNSPECIFIED.String() {
		capacityMode = apiv1.CapacityMode_RCU_DRIVEN.String()
	}

	clusterModel := &schema.TidbCluster{
		ID:              cluster.GetId(),
		TenantID:        cluster.GetTenantId(),
		SharedPoolID:    cluster.GetSharedPoolId(),
		RUMin:           int64(cluster.GetRequestUnit().GetMin()),
		RUMax:           int64(cluster.GetRequestUnit().GetMax()),
		GatewayIdentity: cluster.GetGatewayIdentity(),
		AuditLogConfig:  string(auditlogStr),
		RuVersion:       cluster.GetRuVersion(),
		CapacityMode:    capacityMode,
	}

	return clusterModel, nil
}

func (r *clusterRepoImp) cvtModelToCluster(ctx context.Context, clusterModel *schema.TidbCluster) (*entity.TiDBCluster, error) {
	if clusterModel == nil {
		return nil, fmt.Errorf("cluster model cannot be nil")
	}

	clusterEntity := &entity.TiDBCluster{
		Id:              clusterModel.ID,
		TenantId:        clusterModel.TenantID,
		SharedPoolId:    clusterModel.SharedPoolID,
		GatewayIdentity: clusterModel.GatewayIdentity,
		RequestUnit: &entity.RequestUnit{
			Min: uint32(clusterModel.RUMin),
			Max: uint32(clusterModel.RUMax),
		},
		RuVersion:    clusterModel.RuVersion,
		CapacityMode: apiv1.CapacityMode(apiv1.CapacityMode_value[clusterModel.CapacityMode]),
	}

	if clusterModel.AuditLogConfig != "" {
		auditLogSetting := &entity.AuditLogSetting{}
		err := protojson.Unmarshal([]byte(clusterModel.AuditLogConfig), auditLogSetting)
		if err != nil {
			tilog.Error(ctx, "failed to unmarshal audit log setting", zap.String("clusterId", clusterEntity.GetId()), zap.Error(err))
			return nil, status.Errorf(codes.Internal, "failed to unmarshal audit log setting: %s", err)
		}
		clusterEntity.AuditLogSetting = auditLogSetting
	}

	if clusterModel.DeletedAt.Valid {
		return clusterEntity, nil
	}

	version, err := r.clusterConfigClient.GetTiDBClusterVersion(ctx, clusterEntity)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			tilog.Warn(ctx, "tidb cluster version not found", zap.String("clusterId", clusterEntity.GetId()))
			clusterEntity.Phase = apiv1.Cluster_CREATING
			return clusterEntity, nil
		}
		tilog.Error(ctx, "failed to get tidb cluster version", zap.String("clusterId", clusterEntity.GetId()), zap.Error(err))
		clusterEntity.Phase = apiv1.Cluster_UNAVAILABLE
		return clusterEntity, nil
	}
	clusterEntity.Version = version
	clusterEntity.Phase = apiv1.Cluster_AVAILABLE

	// Check TiProxy component status from K8s
	dataplaneName, err := r.getSharedPoolDataplaneName(ctx, clusterModel.SharedPoolID)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil, status.Errorf(codes.NotFound, "failed to get shared pool dataplane name for %s: %v", clusterModel.SharedPoolID, err)
		}
		return nil, status.Errorf(codes.Internal, "failed to get shared pool dataplane name for %s: %v", clusterModel.SharedPoolID, err)
	}

	remoteCli, err := r.dataplaneManager.GetClient(dataplaneName)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil, status.Errorf(codes.NotFound, "failed to get remote client for dataplane %s: %v", dataplaneName, err)
		}
		return nil, status.Errorf(codes.Internal, "failed to get remote client for dataplane %s: %v", dataplaneName, err)
	}

	tiproxyID := clusterModel.ID
	tiproxyComp, err := tioperator.GetTiProxyGroup(ctx, remoteCli, clusterModel.SharedPoolID, tiproxyID)
	if status.Code(err) == codes.NotFound {
		tiproxyID = utils.FnvHash(clusterModel.SharedPoolID)
		tiproxyComp, err = tioperator.GetTiProxyGroup(ctx, remoteCli, clusterModel.SharedPoolID, tiproxyID)
	}
	if err != nil {
		tilog.Warn(ctx, "failed to get tiproxy group", zap.String("tiproxyId", tiproxyID), zap.Error(err))
		switch status.Code(err) {
		case codes.NotFound:
			// TiProxyGroup not found, likely still being created.
			clusterEntity.Phase = apiv1.Cluster_CREATING
		default:
			// Other errors indicate TiProxy is not reliably available.
			clusterEntity.Phase = apiv1.Cluster_UNAVAILABLE
		}
		return clusterEntity, nil
	}

	switch tiproxyComp.GetPhase() {
	case apiv1.Component_UNAVAILABLE:
		clusterEntity.Phase = apiv1.Cluster_UNAVAILABLE
	case apiv1.Component_MODIFYING:
		clusterEntity.Phase = apiv1.Cluster_MODIFYING
	case apiv1.Component_CREATING:
		clusterEntity.Phase = apiv1.Cluster_CREATING
	default:
		// If TiProxy is available, we consider the cluster as available and let the caller check the TiDBGroup status for more accurate cluster status.
		clusterEntity.Phase = apiv1.Cluster_AVAILABLE
	}

	return clusterEntity, nil
}
