package tidbgroup

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/samber/lo"
	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/cluster-service-ng/pkg/storage/etcd"
	"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"
)

func (r *tidbGroupRepoImp) cvtTidbGroupToModel(tg *entity.TiDBGroup) (*schema.TidbGroup, *schema.TidbGroupEndpoint, error) {
	if tg == nil {
		return nil, nil, errors.New("TiDBGroup cannot be nil")
	}

	tgModel := &schema.TidbGroup{
		ID:              tg.GetId(),
		SharedPoolID:    tg.GetSharedPoolId(),
		ClusterID:       tg.GetClusterId(),
		TenantID:        tg.GetTenantId(),
		DataplaneName:   tg.GetDataplaneName(),
		Type:            tg.GetType().String(),
		TemplateVersion: tg.TemplateVersion,
	}

	endPointModel := &schema.TidbGroupEndpoint{
		TidbGroupID:              tg.GetId(),
		DNSIdentity:              tg.GetSqlEndpoint().GetDnsIdentity(),
		DisablePublicDNSIdentity: tg.GetSqlEndpoint().GetDisablePublicDnsIdentity(),
		PublicEnabled:            lo.ToPtr(tg.GetSqlEndpoint().GetPublicLb().GetEnabled()),
		PrivateEnabled:           lo.ToPtr(tg.GetSqlEndpoint().GetPrivateLb().GetEnabled()),
	}

	return tgModel, endPointModel, nil
}

func (r *tidbGroupRepoImp) cvtModelToTiDBGroup(ctx context.Context, tgModel *schema.TidbGroup) (*entity.TiDBGroup, error) {
	if tgModel == nil {
		return nil, errors.New("TiDBGroup model cannot be nil")
	}
	if tgModel.DataplaneName == "" {
		return nil, errors.New("DataplaneName cannot be nil")
	}

	tgEntity := &entity.TiDBGroup{
		TiDBGroup: apiv1.TiDBGroup{
			Id:            tgModel.ID,
			SharedPoolId:  tgModel.SharedPoolID,
			ClusterId:     tgModel.ClusterID,
			TenantId:      tgModel.TenantID,
			DataplaneName: tgModel.DataplaneName,
			SqlEndpoint: &entity.SQLEndpoint{
				DnsIdentity:              tgModel.Endpoint.DNSIdentity,
				DisablePublicDnsIdentity: tgModel.Endpoint.DisablePublicDNSIdentity,
				PublicLb: &entity.LoadBalancer{
					Enabled: *tgModel.Endpoint.PublicEnabled,
				},
				PrivateLb: &entity.LoadBalancer{
					Enabled: *tgModel.Endpoint.PrivateEnabled,
				},
			},
		},
		TemplateVersion: tgModel.TemplateVersion,
	}

	if tgModel.Type == "" {
		tgEntity.Type = apiv1.TiDBGroupType_DEFAULT
	} else {
		if _, ok := apiv1.TiDBGroupType_value[strings.ToUpper(tgModel.Type)]; ok {
			tgEntity.Type = apiv1.TiDBGroupType(apiv1.TiDBGroupType_value[strings.ToUpper(tgModel.Type)])
		} else {
			tilog.Warn(ctx, "unknown tidb group type in model, default to DEFAULT", zap.String("type", tgModel.Type))
			tgEntity.Type = apiv1.TiDBGroupType_DEFAULT
		}
	}

	if tgModel.DeletedAt.Valid {
		tgEntity.IsDeleted = true
		return tgEntity, nil
	}

	dataplaneInfo, err := r.dataplaneClient.GetDataPlane(ctx, &dplanemgrv1.GetDataPlaneRequest{
		DataPlaneId: tgModel.DataplaneName,
	})
	if err != nil {
		if status.Code(err) == codes.NotFound {
			tilog.Warn(ctx, "dataplane not found, maybe not ready", zap.Any("err", err))
			tgEntity.Phase = apiv1.TiDBGroup_CREATING
			return tgEntity, nil
		}
		return nil, fmt.Errorf("failed to get dataplane: %w", err)
	}

	if dataplaneInfo.GetStatus() != dplanemgrv1.DataPlane_Active {
		tilog.Warn(ctx, "dataplane is not ready, not full tidb group",
			zap.String("dataplaneName", tgModel.DataplaneName),
			zap.String("status", dataplaneInfo.GetStatus().String()))
		tgEntity.Phase = apiv1.TiDBGroup_CREATING
		return tgEntity, nil
	}

	tgEntity.GatewayBaseUrl = etcd.GetTiDBGroupGatewayUrl(dataplaneInfo.GetApiGateway(), tgEntity.GetSharedPoolId(), tgEntity.GetId())

	// full sql endpoint
	if tgEntity.GetSqlEndpoint().GetPublicLb().GetEnabled() {

		lb, err := r.dataplaneClient.GetLoadBalancer(ctx, &dplanemgrv1.GetLoadBalancerRequest{
			DataPlaneId: tgEntity.GetDataplaneName(),
			Id:          entity.GetPublicSvcName(tgEntity.GetId()),
		})
		if err != nil {
			if status.Code(err) == codes.NotFound {
				tilog.Warn(ctx, "public load balancer not found, maybe not ready", zap.Any("err", err))
			} else {
				return nil, fmt.Errorf("failed to get public load balancer: %w", err)
			}
		} else {
			tgEntity.SqlEndpoint.PublicLb.Host = lb.GetDns()
			tgEntity.SqlEndpoint.PublicLb.Port = lb.GetPort()
			tgEntity.SqlEndpoint.PublicLb.IpAllowList = make([]*apiv1.TiDBGroup_SqlEndpoint_LoadBalancer_IpAddress, 0, len(lb.GetIpWhiteList()))
			for _, ip := range lb.GetIpWhiteList() {
				tgEntity.SqlEndpoint.PublicLb.IpAllowList = append(
					tgEntity.SqlEndpoint.PublicLb.IpAllowList,
					&apiv1.TiDBGroup_SqlEndpoint_LoadBalancer_IpAddress{
						CidrBlock: ip,
					},
				)
			}
		}
	}

	if tgEntity.GetSqlEndpoint().GetPrivateLb().GetEnabled() {
		lb, err := r.dataplaneClient.GetLoadBalancer(ctx, &dplanemgrv1.GetLoadBalancerRequest{
			DataPlaneId: tgEntity.GetDataplaneName(),
			Id:          entity.GetPrivateSvcName(tgEntity.GetId()),
		})
		if err != nil {
			if status.Code(err) == codes.NotFound {
				tilog.Warn(ctx, "private load balancer not found, maybe not ready", zap.Any("err", err))
			} else {
				return nil, fmt.Errorf("failed to get private load balancer: %w", err)
			}
		} else {
			tgEntity.SqlEndpoint.PrivateLb.Host = lb.GetDns()
			tgEntity.SqlEndpoint.PrivateLb.Port = lb.GetPort()
		}
	}

	remoteCli, err := r.dataplaneManager.GetClient(tgModel.DataplaneName)
	if err != nil {
		tilog.Error(ctx, "failed to get remote client for tidb group", zap.String("dataplaneName", tgModel.DataplaneName), zap.Error(err))
		return nil, fmt.Errorf("failed to get remote client for tidb group: %w", err)
	}

	// full tidb group with tidb component and worker
	tidbComp, err := tioperator.GetTiDBGroup(ctx, remoteCli, tgEntity.GetSharedPoolId(), tgEntity.GetId())
	if err != nil {
		if status.Code(err) == codes.NotFound {
			tilog.Warn(ctx, "tidb group not found, maybe not ready", zap.String("id", tgEntity.GetId()), zap.Any("err", err))
			tgEntity.Phase = apiv1.TiDBGroup_CREATING
			return tgEntity, nil
		}
		return nil, fmt.Errorf("failed to get tidb group: %w", err)
	}

	tiproxyID := tgEntity.GetClusterId()
	tiproxyComp, err := tioperator.GetTiProxyGroup(ctx, remoteCli, tgEntity.GetSharedPoolId(), tiproxyID)
	if status.Code(err) == codes.NotFound {
		tiproxyID = utils.FnvHash(tgEntity.GetSharedPoolId())
		tiproxyComp, err = tioperator.GetTiProxyGroup(ctx, remoteCli, tgEntity.GetSharedPoolId(), tiproxyID)
	}
	if err != nil {
		if status.Code(err) == codes.NotFound {
			tilog.Warn(ctx, "tiproxy group not found, maybe not ready", zap.String("id", tiproxyID), zap.Any("err", err))
			tgEntity.Phase = apiv1.TiDBGroup_UNAVAILABLE
		} else {
			return nil, fmt.Errorf("failed to get tiproxy group: %w", err)
		}
	}

	// auditLogConfig, err := tioperator.GetTiDBAuditLogConfig(ctx, remoteCli, tgEntity.GetSharedPoolId(), tgEntity.GetId())
	// if err != nil {
	// 	tilog.Warn(ctx, "failed to get tidb audit log config", zap.String("sharedPoolId", tgEntity.GetSharedPoolId()), zap.String("tidbGroupId", tgEntity.GetId()), zap.Error(err))
	// 	// return nil, fmt.Errorf("failed to get tidb audit log config: %w", err)
	// } else {
	// 	tgEntity.TiDBGroup.AuditLogSetting = cvtAuditLogSetting(auditLogConfig)
	// }

	etcd.FullTidbGroup(ctx, tgEntity, tidbComp, tiproxyComp)

	return tgEntity, nil
}
