package server

import (
	"context"
	"time"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const cassandraProxyPLSReadThroughTimeout = 2 * time.Second

// GetCassandraProxy returns the current observed state of the
// cassandra-proxy component. DPM read-through is best-effort so a degraded DPM
// does not fail the core component status lookup.
func (s *serverImpl) GetCassandraProxy(ctx context.Context, req *apiv1.GetCassandraProxyRequest) (*apiv1.GetCassandraProxyResponse, error) {
	if req.GetClusterId() == "" {
		return nil, status.Errorf(codes.InvalidArgument, "cluster_id is required")
	}

	_, err := s.tidbClusterRepo.Load(ctx, req.GetClusterId(), true)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil, status.Errorf(codes.NotFound, "tidb cluster %s not found", req.GetClusterId())
		}
		return nil, status.Errorf(codes.Internal, "failed to load tidb cluster: %s", err)
	}

	state, err := s.cassandraProxyStateRepo.LoadByClusterID(ctx, req.GetClusterId(), false)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil, status.Errorf(codes.NotFound, "cassandra-proxy component for cluster %s not found", req.GetClusterId())
		}
		return nil, status.Errorf(codes.Internal, "failed to load cassandra-proxy state: %s", err)
	}
	image, err := s.resolveCassandraProxyImageFromState(state)
	if err != nil {
		return nil, err
	}

	privatelinkDNSName := state.PrivatelinkDNSName
	privateLinkService := (*apiv1.CassandraProxyPrivateLinkServiceInfo)(nil)
	componentPhase := entity.CassandraProxyPhaseToComponentPhase(state.Phase)
	if shouldLookupCassandraProxyPLS(state, componentPhase) {
		pls := s.lookupCassandraProxyPLS(ctx, state.SharedPoolID, state.ClusterID)
		if pls != nil {
			if privatelinkDNSName == "" {
				privatelinkDNSName = pls.GetServiceDnsName()
			}
			privateLinkService = buildCassandraProxyPLSInfo(pls)
		}
	}

	return &apiv1.GetCassandraProxyResponse{
		HostId:             state.HostID,
		InternalEndpoint:   buildCassandraProxyInternalEndpoint(state.SharedPoolID, state.ClusterID),
		PrivatelinkDnsName: privatelinkDNSName,
		Phase:              componentPhase,
		PrivateLinkService: privateLinkService,
		NodeQuota:          state.NodeQuotaEntity,
		Image:              image,
	}, nil
}

func shouldLookupCassandraProxyPLS(state *entity.CassandraProxyState, componentPhase apiv1.Component_Phase) bool {
	return state.PrivatelinkDNSName == "" || componentPhase == apiv1.Component_AVAILABLE
}

// lookupCassandraProxyPLSDns is a best-effort read-through that returns the
// customer-facing PrivateLink DNS for the given component. Returns "" on any
// error so a degraded DPM does not make Get fail.
func (s *serverImpl) lookupCassandraProxyPLSDns(ctx context.Context, sharedPoolID, componentID string) string {
	pls := s.lookupCassandraProxyPLS(ctx, sharedPoolID, componentID)
	if pls == nil {
		return ""
	}
	return pls.GetServiceDnsName()
}

// lookupCassandraProxyPLS is best-effort; returns nil on any error so a
// degraded DPM does not make GetCassandraProxy fail.
func (s *serverImpl) lookupCassandraProxyPLS(ctx context.Context, sharedPoolID, componentID string) *dplanemgrv1.PrivateLinkService {
	readThroughCtx, cancel := context.WithTimeout(ctx, cassandraProxyPLSReadThroughTimeout)
	defer cancel()

	sharedPool, err := s.sharedPoolRepo.Load(readThroughCtx, sharedPoolID, false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			tilog.Warn(ctx, "GetCassandraProxy: sharedPool read-through failed",
				zap.String("sharedPoolId", sharedPoolID), zap.Error(err))
		}
		return nil
	}

	pls, err := s.dataplaneClient.GetPrivateLinkService(readThroughCtx, &dplanemgrv1.GetPrivateLinkServiceRequest{
		DataPlaneId: sharedPool.GetDataplaneName(),
		Id:          entity.GetCassandraProxyLbName(componentID),
	})
	if err != nil {
		if status.Code(err) != codes.NotFound {
			tilog.Warn(ctx, "GetCassandraProxy: PLS read-through failed",
				zap.String("componentId", componentID), zap.Error(err))
		}
		return nil
	}
	return pls
}

func buildCassandraProxyPLSInfo(pls *dplanemgrv1.PrivateLinkService) *apiv1.CassandraProxyPrivateLinkServiceInfo {
	phase, ok := cassandraProxyPLSPhaseToAPI(pls.GetStatus())
	if !ok {
		return nil
	}

	info := &apiv1.CassandraProxyPrivateLinkServiceInfo{
		Phase: phase,
	}
	if pls.GetStatus() == dplanemgrv1.PrivateLinkService_ACTIVE {
		info.ServiceName = pls.GetAws().GetServiceName()
		info.AvailabilityZoneIds = pls.GetAws().GetAzIds()
	}
	return info
}

func cassandraProxyPLSPhaseToAPI(plsStatus dplanemgrv1.PrivateLinkService_Status) (apiv1.PrivateLinkService_Status, bool) {
	switch plsStatus {
	case dplanemgrv1.PrivateLinkService_CREATING:
		return apiv1.PrivateLinkService_CREATING, true
	case dplanemgrv1.PrivateLinkService_ACTIVE:
		return apiv1.PrivateLinkService_ACTIVE, true
	case dplanemgrv1.PrivateLinkService_DELETING:
		return apiv1.PrivateLinkService_DELETING, true
	default:
		return apiv1.PrivateLinkService_STATE_UNSPECIFIED, false
	}
}
