package vercel

import (
	"context"
	serrors "errors"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type ClusterConnectionDAO interface {
	AddVercelConnection(ctx context.Context, connection ClusterConnection) (*ClusterConnection, error)
	GetVercelConnection(ctx context.Context, tiDBClusterID uint64, vercelProjectID string) (*ClusterConnection, error)
	ListVercelConnectionsByClusterID(ctx context.Context, tiDBClusterID uint64) ([]ClusterConnection, error)
	ListVercelConnectionsByTiDBProjectID(ctx context.Context, tiDBProjectID uint64) ([]ClusterConnection, error)
	ListVercelConnectionsByOrgID(ctx context.Context, orgID uint64) ([]ClusterConnection, error)
	ListVercelConnectionsByConfigurationID(ctx context.Context, configurationID string) ([]ClusterConnection, error)
	ListVercelConnectionsByProjectIDsAndConfigurationID(ctx context.Context, projectIDs []uint64, configurationID string) ([]ClusterConnection, error)
	GetVercelConnectionByVercelProjectID(ctx context.Context, vercelProjectID string) (*ClusterConnection, error)
	ListAllTiDBClusterIDs(ctx context.Context) ([]uint64, error)
	ListVercelConnectionsByClusterIDs(ctx context.Context, tiDBClusterIDs []uint64) ([]ClusterConnection, error)
	ListVercelConnections(ctx context.Context) ([]ClusterConnection, error)
}

type clusterConnectionDAOImpl struct {
	db *gorm.DB
}

func NewClusterConnectionDAO(db *gorm.DB) ClusterConnectionDAO {
	return &clusterConnectionDAOImpl{
		db: db,
	}
}

func (d *clusterConnectionDAOImpl) AddVercelConnection(ctx context.Context, connection ClusterConnection) (_ *ClusterConnection, err error) {
	ctx, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.AddVercelConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	err = d.db.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "vercel_project_id"}, {Name: "tidb_cluster_id"}},
		DoUpdates: clause.AssignmentColumns([]string{"tidb_org_id", "tidb_project_id", "updated_at"}),
	}).Create(&connection).Error

	if err != nil {
		return &connection, errors.Trace(err)
	}

	tilog.Info(ctx, "Succeed to create connection")
	return &connection, nil
}

func (d *clusterConnectionDAOImpl) GetVercelConnection(ctx context.Context, tiDBClusterID uint64, vercelProjectID string) (_ *ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.GetVercelConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var connection ClusterConnection
	if err = d.db.Model(&ClusterConnection{}).Preload("EnvironmentIdentifier").Preload("BranchEnvironmentIdentifiers").Preload("VercelIntegrationConfiguration").Where("tidb_cluster_id = ? AND vercel_project_id = ?", tiDBClusterID, vercelProjectID).First(&connection).Error; err != nil {
		if serrors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		} else {
			return nil, errors.Trace(err)
		}
	}

	return &connection, nil
}

func (d *clusterConnectionDAOImpl) GetVercelConnectionByVercelProjectID(ctx context.Context, vercelProjectID string) (_ *ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.GetVercelConnectionByVercelProjectID")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var connection ClusterConnection
	if err = d.db.Model(&ClusterConnection{}).Preload("EnvironmentIdentifier").Preload("VercelIntegrationConfiguration").Where("vercel_project_id = ?", vercelProjectID).First(&connection).Error; err != nil {
		if serrors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		} else {
			return nil, errors.Trace(err)
		}
	}

	return &connection, nil
}

func (d *clusterConnectionDAOImpl) ListVercelConnectionsByClusterID(ctx context.Context, tiDBClusterID uint64) (_ []ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.ListVercelConnectionsByClusterID")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var records []ClusterConnection
	if err = d.db.Model(&ClusterConnection{}).Preload("BranchEnvironmentIdentifiers").Preload("EnvironmentIdentifier").Preload("VercelIntegrationConfiguration").Where("tidb_cluster_id = ?", tiDBClusterID).Find(&records).Error; err != nil {
		return nil, errors.Trace(err)
	}
	return records, nil
}

func (d *clusterConnectionDAOImpl) ListVercelConnectionsByClusterIDs(ctx context.Context, tiDBClusterIDs []uint64) (_ []ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.ListVercelConnectionsByClusterIDs")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var records []ClusterConnection
	if err = d.db.Model(&ClusterConnection{}).Preload("BranchEnvironmentIdentifiers").Preload("EnvironmentIdentifier").Preload("VercelIntegrationConfiguration").Where("tidb_cluster_id in ?", tiDBClusterIDs).Find(&records).Error; err != nil {
		return nil, errors.Trace(err)
	}
	return records, nil
}

func (d *clusterConnectionDAOImpl) ListVercelConnections(ctx context.Context) (_ []ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.ListVercelConnections")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var records []ClusterConnection
	if err = d.db.Model(&ClusterConnection{}).Find(&records).Error; err != nil {
		return nil, errors.Trace(err)
	}
	return records, nil
}

func (d *clusterConnectionDAOImpl) ListVercelConnectionsByTiDBProjectID(ctx context.Context, tiDBProjectID uint64) (_ []ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.ListVercelConnectionsByTiDBProjectID")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var records []ClusterConnection
	if err = d.db.Where("tidb_project_id = ?", tiDBProjectID).Find(&records).Error; err != nil {
		return nil, errors.Trace(err)
	}
	return records, nil
}

func (d *clusterConnectionDAOImpl) ListVercelConnectionsByOrgID(ctx context.Context, orgID uint64) (_ []ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.ListVercelConnectionsByOrgID")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var records []ClusterConnection
	if err = d.db.Where("tidb_org_id = ?", orgID).Find(&records).Error; err != nil {
		return nil, errors.Trace(err)
	}
	return records, nil
}

func (d *clusterConnectionDAOImpl) ListVercelConnectionsByConfigurationID(ctx context.Context, configurationID string) (_ []ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.ListVercelConnectionsByConfigurationID")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var records []ClusterConnection
	if err = d.db.Where("vercel_integration_configuration_id = ?", configurationID).Find(&records).Error; err != nil {
		return nil, errors.Trace(err)
	}
	return records, nil
}

func (d *clusterConnectionDAOImpl) ListVercelConnectionsByProjectIDsAndConfigurationID(ctx context.Context, projectIDs []uint64, configurationID string) (_ []ClusterConnection, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.ListVercelConnectionsByProjectIDsAndConfigurationID")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var records []ClusterConnection
	if err = d.db.Where("vercel_integration_configuration_id = ? and tidb_project_id IN ?", configurationID, projectIDs).Find(&records).Error; err != nil {
		return nil, errors.Trace(err)
	}
	return records, nil
}

func (d *clusterConnectionDAOImpl) ListAllTiDBClusterIDs(ctx context.Context) (_ []uint64, err error) {
	_, span := Tracer.Start(ctx, "clusterConnectionDAOImpl.ListAllTiDBClusterIDs")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var records []uint64
	if err = d.db.Model(&ClusterConnection{}).Pluck("TiDBClusterID", &records).Error; err != nil {
		return nil, errors.Trace(err)
	}
	return records, nil
}
