package network

import (
	"context"
	"fmt"

	"github.com/samber/lo"
	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	endpointrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/endpoint"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

type privateLinkEndpointRepoImpl struct {
	provider db.Provider
}

func NewPrivateLinkEndpointRepo(provider db.Provider) endpointrepo.PrivateLinkEndpointRepo {
	return &privateLinkEndpointRepoImpl{
		provider: provider,
	}
}

func (p *privateLinkEndpointRepoImpl) Create(
	ctx context.Context,
	endpoint *entity.PrivateLinkEndpoint,
) bizerr.BizError {
	wdb := p.provider.WriteDB(ctx)

	endpointModel := privateLinkEndpointEnToModel(endpoint)

	if err := wdb.Create(endpointModel).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to create private link endpoint")
	}
	endpoint.ID = uint64(endpointModel.ID)

	return nil
}

func (p *privateLinkEndpointRepoImpl) DeleteByTidbGroupID(
	ctx context.Context,
	clusterId, workPoolId uint64,
) (affectedLines int64, _ bizerr.BizError) {
	wdb := p.provider.WriteDB(ctx)

	result := wdb.Delete(&schema.PrivateLinkEndpoint{}, "cluster_id = ? AND work_pool_id = ?", clusterId, workPoolId)
	if err := result.Error; err != nil {
		return 0, bizerr.WrapGormError(err, "delete private link endpoint of work pool %d", workPoolId)
	}

	return result.RowsAffected, nil
}

func privateLinkEndpointEnToModel(endpoint *entity.PrivateLinkEndpoint) *schema.PrivateLinkEndpoint {
	return &schema.PrivateLinkEndpoint{
		Model: gorm.Model{
			ID: uint(endpoint.ID),
		},
		TenantID:         endpoint.TenantID,
		ProjectID:        endpoint.ProjectID,
		ClusterID:        endpoint.ClusterID,
		EndpointID:       endpoint.EndpointID,
		EndpointType:     PrivateLinkEndpointTypePbToModel(endpoint.Type),
		CloudProvider:    endpoint.CloudProvider,
		WorkPoolID:       endpoint.TidbGroupID,
		PrivateIpAddress: lo.EmptyableToPtr(endpoint.PrivateIpAddress),
	}
}

func PrivateLinkEndpointTypePbToModel(tp globaldedicatedv1.PrivateLinkEndpoint_Type) string {
	switch tp {
	case globaldedicatedv1.PrivateLinkEndpoint_TYPE_CUSTOMER:
		return "customer"
	case globaldedicatedv1.PrivateLinkEndpoint_TYPE_DBAAS:
		return "dbaas"
	default:
		panic(fmt.Sprintf("unknown endpoint type: %s", tp))
	}
}

func PrivateLinkEndpointTypeModel2Pb(tp string) globaldedicatedv1.PrivateLinkEndpoint_Type {
	switch tp {
	case "customer":
		return globaldedicatedv1.PrivateLinkEndpoint_TYPE_CUSTOMER
	case "dbaas":
		return globaldedicatedv1.PrivateLinkEndpoint_TYPE_DBAAS
	default:
		panic(fmt.Sprintf("unknown endpoint type: %s", tp))
	}
}

func (p *privateLinkEndpointRepoImpl) DeleteByIDOrEndpointID(
	ctx context.Context,
	clusterID, id, tidbGroupId uint64,
	endpointID string,
) bizerr.BizError {
	if err := metadata.Validate(ctx, &metadata.ExpectRequest[uint64]{
		ClusterId: clusterID,
		// ResourceId: strconv.FormatUint(tidbGroupId, 10),
	}); err != nil {
		return bizerr.WrapError(err, "validate failed")
	}

	tx := p.provider.WriteDB(ctx)
	if id > 0 {
		tx = tx.Where("cluster_id=? AND id=?", clusterID, id)
	} else {
		tx = tx.Where("cluster_id=? AND endpoint_id=?", clusterID, endpointID)
	}

	if err := tx.Delete(&schema.PrivateLinkEndpoint{}).Error; err != nil {
		return bizerr.WrapGormError(err, "delete private link endpoint")
	}
	return nil
}

func (p *privateLinkEndpointRepoImpl) DeleteByClusterID(ctx context.Context, clusterID uint64) bizerr.BizError {
	wdb := p.provider.WriteDB(ctx)
	if err := wdb.Where("cluster_id=?", clusterID).Delete(&schema.PrivateLinkEndpoint{}).Error; err != nil {
		return bizerr.WrapGormError(err, "delete private link endpoint")
	}
	return nil
}

func buildTxByPrivateLinkEndpointFilter(tx *gorm.DB, filter *endpointrepo.PrivateLinkEndpointFilter) *gorm.DB {
	if len(filter.TenantIDs) != 0 {
		tx = tx.Where("tenant_id IN (?)", filter.TenantIDs)
	}
	if len(filter.ProjectIDs) != 0 {
		tx = tx.Where("project_id IN (?)", filter.ProjectIDs)
	}
	if len(filter.ClusterIDs) != 0 {
		tx = tx.Where("cluster_id IN (?)", filter.ClusterIDs)
	}
	if len(filter.IDs) != 0 {
		tx = tx.Where("id IN (?)", filter.IDs)
	}
	if len(filter.EndpointIDs) != 0 {
		tx = tx.Where("endpoint_id IN (?)", filter.EndpointIDs)
	}
	if len(filter.Types) != 0 {
		tps := make([]string, 0, len(filter.Types))
		for _, each := range filter.Types {
			tps = append(tps, PrivateLinkEndpointTypePbToModel(each))
		}
		tx = tx.Where("endpoint_type IN (?)", tps)
	}
	if len(filter.TidbGroupIDs) != 0 {
		tx = tx.Where("work_pool_id IN (?)", filter.TidbGroupIDs)
	}
	return tx
}

func privateLinkEndpointModelToEn(endpoint *schema.PrivateLinkEndpoint) *entity.PrivateLinkEndpoint {
	out := &entity.PrivateLinkEndpoint{
		ID:               uint64(endpoint.ID),
		TenantID:         endpoint.TenantID,
		ProjectID:        endpoint.ProjectID,
		ClusterID:        endpoint.ClusterID,
		TidbGroupID:      endpoint.WorkPoolID,
		Type:             PrivateLinkEndpointTypeModel2Pb(endpoint.EndpointType),
		EndpointID:       endpoint.EndpointID,
		CloudProvider:    endpoint.CloudProvider,
		PrivateIpAddress: lo.FromPtr(endpoint.PrivateIpAddress),
	}

	return out
}

func (p *privateLinkEndpointRepoImpl) Query(
	ctx context.Context,
	filter *endpointrepo.PrivateLinkEndpointFilter,
) ([]*entity.PrivateLinkEndpoint, bizerr.BizError) {
	tx := p.provider.ReadDB(ctx)
	tx = buildTxByPrivateLinkEndpointFilter(tx.Model(&schema.PrivateLinkEndpoint{}), filter)

	var endpointList []*schema.PrivateLinkEndpoint
	if err := tx.Find(&endpointList).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "find aws private endpoint")
	}

	var out []*entity.PrivateLinkEndpoint
	for _, each := range endpointList {
		out = append(out, privateLinkEndpointModelToEn(each))
	}

	return out, nil
}

func (p *privateLinkEndpointRepoImpl) Load(
	ctx context.Context,
	id uint64,
) (*entity.PrivateLinkEndpoint, bizerr.BizError) {
	rdb := p.provider.ReadDB(ctx)
	var endpoint schema.PrivateLinkEndpoint
	if err := rdb.First(&endpoint, id).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load private link endpoint")
	}

	return privateLinkEndpointModelToEn(&endpoint), nil
}

// CutoverEndpointMeta will cutover the endpoint meta from source to target
func (p *privateLinkEndpointRepoImpl) CutoverEndpointMeta(
	ctx context.Context,
	sourceClusterId, sourceTidbGroupId, targetClusterId, targetTidbGroupId uint64,
) bizerr.BizError {
	sourceEndpoints, err := p.Query(ctx, &endpointrepo.PrivateLinkEndpointFilter{
		ClusterIDs:   []uint64{sourceClusterId},
		TidbGroupIDs: []uint64{sourceTidbGroupId},
	})
	if err != nil {
		return bizerr.WrapError(err, "query source endpoints")
	}
	wdb := p.provider.WriteDB(ctx)
	for _, each := range sourceEndpoints {
		each.ClusterID = targetClusterId
		each.TidbGroupID = targetTidbGroupId
		if err := wdb.Omit("created_at").Save(privateLinkEndpointEnToModel(each)).Error; err != nil {
			return bizerr.WrapGormError(err, "save private link endpoint")
		}
	}
	return nil
}
