package privatelink

import (
	"context"
	"errors"
	"strconv"

	"github.com/tidbcloud/pkgv2/db"
	"go.opentelemetry.io/otel"
	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	privatelinkrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/privatelink"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

var tracer = otel.Tracer("infra.privatelink")

func NewPrivateLinkRepo(provider db.Provider) privatelinkrepo.Repo {
	return &repoImpl{
		provider: provider,
	}
}

type repoImpl struct {
	provider db.Provider
}

func (r *repoImpl) CreatePrivateLinkService(ctx context.Context, service *entity.PrivateLinkService) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "privatelink.CreatePrivateLinkService")
	defer span.End()

	schemaService := privatelinkServiceEnToModel(service)
	wdb := r.provider.WriteDB(ctx)

	var existingService schema.PrivateLinkService

	// at present, we only support one service per tidb group
	if rawErr := wdb.Model(&schema.PrivateLinkService{}).
		Where("`cluster_id` = ?", service.ClusterId).
		Where("`tidb_group_id` = ?", service.TidbGroupId).
		First(&existingService).Error; rawErr != nil {
		err := bizerr.WrapGormError(rawErr, "load private link service")
		if err.ErrCode() != bizerr.ErrCodeRecordNotFound {
			return err
		}

		if rawErr := wdb.Create(schemaService).Error; rawErr != nil {
			return bizerr.WrapGormError(rawErr, "create private link service")
		}

		return nil
	}

	return nil
}

func (r *repoImpl) UpdatePrivateLinkService(ctx context.Context, service *entity.PrivateLinkService) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "privatelink.UpdatePrivateLinkService")
	defer span.End()

	schemaService := privatelinkServiceEnToModel(service)
	wdb := r.provider.WriteDB(ctx)

	existingService := &schema.PrivateLinkService{}
	if rawErr := wdb.Model(&schema.PrivateLinkService{}).
		Where("`tidb_group_id` = ?", service.TidbGroupId).
		First(existingService).
		Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "get private link service")
	}

	schemaService.ID = existingService.ID
	if rawErr := wdb.Omit("created_at").Save(schemaService).Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "update private link service")
	}

	return nil
}

func (r *repoImpl) ListPrivateLinkServices(
	ctx context.Context,
	filter *privatelinkrepo.Filter,
) ([]*entity.PrivateLinkService, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "privatelink.ListPrivateLinkServices")
	defer span.End()

	if filter.ClusterID == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster_id is required")
	}

	rdb := r.provider.ReadDB(ctx)
	cluster := &schema.Cluster{}
	if rawErr := rdb.Model(&schema.Cluster{}).
		Preload("Provider").
		Preload("Region").
		Where("`id` = ?", filter.ClusterID).
		First(cluster).
		Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "get cluster failed for list private link services")
	}

	rdb = rdb.Model(&schema.PrivateLinkService{})
	var services []*schema.PrivateLinkService
	if filter.ClusterID != 0 {
		rdb = rdb.Where("`cluster_id` = ?", filter.ClusterID)
	}
	if filter.TidbGroupID != 0 {
		rdb = rdb.Where("`tidb_group_id` = ?", filter.TidbGroupID)
	}

	if rawErr := rdb.Find(&services).Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "list private link services")
	}

	if len(services) == 0 {
		return nil, nil
	}

	entities := make([]*entity.PrivateLinkService, 0, len(services))
	for _, service := range services {
		en, err := privatelinkServiceModelToEn(service, cluster.Region, cluster.Provider)
		if err != nil {
			return nil, err
		}
		if en == nil {
			// This shouldn't happen, but let's log it for debugging
			return nil, bizerr.New(bizerr.ErrCodeDB, "converted service is nil")
		}
		entities = append(entities, en)
	}

	return entities, nil
}

func (r *repoImpl) GetPrivateLinkService(
	ctx context.Context,
	serviceID uint64,
) (*entity.PrivateLinkService, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "privatelink.GetPrivateLinkService")
	defer span.End()

	rdb := r.provider.ReadDB(ctx)
	service := &schema.PrivateLinkService{}
	if rawErr := rdb.Model(&schema.PrivateLinkService{}).Where("`id` = ?", serviceID).First(service).Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "get private link service")
	}
	cluster := &schema.Cluster{}
	if rawErr := rdb.Model(&schema.Cluster{}).
		Preload("Provider").
		Preload("Region").
		Where("`id` = ?", service.ClusterId).
		First(cluster).
		Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "get private link service")
	}

	en, err := privatelinkServiceModelToEn(service, cluster.Region, cluster.Provider)
	if err != nil {
		return nil, err
	}
	return en, nil
}

func (r *repoImpl) DeletePrivateLinkService(ctx context.Context, serviceID uint64) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "privatelink.DeletePrivateLinkService")
	defer span.End()

	wdb := r.provider.WriteDB(ctx)
	if rawErr := wdb.Model(&schema.PrivateLinkService{}).Where("`id` = ?", serviceID).Delete(&schema.PrivateLinkService{}).Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "delete private link service")
	}

	return nil
}

func (r *repoImpl) CreatePrivateLinkConnection(
	ctx context.Context,
	connection *entity.PrivateLinkConnection,
) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "privatelink.CreatePrivateLinkConnection")
	defer span.End()

	wdb := r.provider.WriteDB(ctx)

	existingConnection := &schema.PrivateLinkConnection{}
	if rawErr := wdb.Model(&schema.PrivateLinkConnection{}).
		Where("`endpoint_id` = ?", connection.EndpointId).
		First(existingConnection).
		Error; rawErr != nil {
		if !errors.Is(rawErr, gorm.ErrRecordNotFound) {
			return bizerr.WrapGormError(rawErr, "create private link connection")
		}
	}

	if existingConnection.ID != 0 {
		return bizerr.New(bizerr.ErrCodeRecordExist, "private link connection already exists")
	}

	service := &schema.PrivateLinkService{}
	if rawErr := wdb.Model(&schema.PrivateLinkService{}).
		Where("`service_name` = ?", connection.PrivateLinkService).
		First(service).
		Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "create private link connection")
	}

	if connection.PrivateLinkConnectionId == "" {
		return bizerr.New(bizerr.ErrCodeParams, "private link connection id is required")
	}
	id, err := strconv.ParseUint(connection.PrivateLinkConnectionId, 10, 64)
	if err != nil {
		return bizerr.WrapError(err, "parse private link connection id")
	}
	schemaConnection := privatelinkConnectionEnToModel(connection, service, id)
	if rawErr := wdb.Create(schemaConnection).Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "create private link connection")
	}
	return nil
}

func (r *repoImpl) GetPrivateLinkConnection(
	ctx context.Context,
	connectionID uint64,
) (*entity.PrivateLinkConnection, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "privatelink.GetPrivateLinkConnection")
	defer span.End()

	rdb := r.provider.ReadDB(ctx)
	schemaConnection := &schema.PrivateLinkConnection{}
	if rawErr := rdb.Model(&schema.PrivateLinkConnection{}).Where("`id` = ?", connectionID).First(schemaConnection).Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "get private link connection")
	}
	cluster := &schema.Cluster{}
	if rawErr := rdb.Model(&schema.Cluster{}).
		Preload("Provider").
		Preload("Region").
		Where("`id` = ?", schemaConnection.ClusterId).
		First(cluster).
		Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "get cluster failed for get private link connection")
	}
	en, err := privatelinkConnectionModelToEn(schemaConnection, cluster.Region, cluster.Provider)
	if err != nil {
		return nil, err
	}
	return en, nil
}

func (r *repoImpl) ListPrivateLinkConnections(
	ctx context.Context,
	filter *privatelinkrepo.Filter,
) ([]*entity.PrivateLinkConnection, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "privatelink.ListPrivateLinkConnections")
	defer span.End()

	if filter.ClusterID == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster_id is required")
	}

	rdb := r.provider.ReadDB(ctx)

	cluster := &schema.Cluster{}
	if rawErr := rdb.Model(&schema.Cluster{}).
		Preload("Provider").
		Preload("Region").
		Where("`id` = ?", filter.ClusterID).
		First(cluster).
		Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "list private link connections")
	}

	var connections []*schema.PrivateLinkConnection
	rdb = rdb.Model(&schema.PrivateLinkConnection{})
	if filter.ClusterID != 0 {
		rdb = rdb.Where("`cluster_id` = ?", filter.ClusterID)
	}
	if filter.TidbGroupID != 0 {
		rdb = rdb.Where("`tidb_group_id` = ?", filter.TidbGroupID)
	}
	if rawErr := rdb.Find(&connections).Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "list private link connections")
	}

	entities := make([]*entity.PrivateLinkConnection, 0, len(connections))
	for _, connection := range connections {
		en, err := privatelinkConnectionModelToEn(connection, cluster.Region, cluster.Provider)
		if err != nil {
			return nil, err
		}
		entities = append(entities, en)
	}

	return entities, nil
}

func (r *repoImpl) DeletePrivateLinkConnection(ctx context.Context, connectionID uint64) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "privatelink.DeletePrivateLinkConnection")
	defer span.End()

	wdb := r.provider.WriteDB(ctx)
	if rawErr := wdb.Model(&schema.PrivateLinkConnection{}).
		Where("`id` = ?", connectionID).
		Delete(&schema.PrivateLinkConnection{}).
		Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "delete private link connection")
	}

	return nil
}

func (r *repoImpl) DeletePrivateLinkConnectionByServiceID(ctx context.Context, serviceID uint64) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "privatelink.DeletePrivateLinkConnectionByServiceID")
	defer span.End()

	wdb := r.provider.WriteDB(ctx)
	if rawErr := wdb.Model(&schema.PrivateLinkConnection{}).
		Where("`private_link_service_id` = ?", serviceID).
		Delete(&schema.PrivateLinkConnection{}).
		Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "delete private link connection by service id")
	}
	return nil
}
