package vercel

import (
	"context"
	serrors "errors"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/ecosystem-service/config"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.opentelemetry.io/otel"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

var Tracer = otel.Tracer("vercel")

type ComposeEntityDAO interface {
	DeleteIntegrationConfiguration(ctx context.Context, ID string) error
	DeleteClusterConnection(ctx context.Context, tiDBClusterID uint64, vercelProjectID string) error
	DeleteVercelConnectionsByVercelProjectIDs(ctx context.Context, vercelProjectIDs []string) error
	DeleteClusterConnectionsByIDs(ctx context.Context, IDs []uint64) error
	DeleteDataAppConnection(ctx context.Context, dataAppID uint64, vercelProjectID string) error
	AddClusterConnection(ctx context.Context, connection *ClusterConnection, identifier *EnvironmentIdentifier) error
	AddDataAppConnection(ctx context.Context, connection *DataAppConnection, identifier *DataAppEnvironmentIdentifier) error
}

type composeEntityDAOImpl struct {
	db                              *gorm.DB
	dataAppEnvironmentIdentifierDAO DataAppEnvironmentIdentifierDAO
	dataAppConnectionDAO            DataAppConnectionDAO
	environmentIdentifierDAO        EnvironmentIdentifierDAO
	clusterConnectionDAO            ClusterConnectionDAO
}

func NewComposeEntityDAO(db *gorm.DB,
	dataAppEnvironmentIdentifierDAO DataAppEnvironmentIdentifierDAO,
	dataAppConnectionDAO DataAppConnectionDAO,
	environmentIdentifierDAO EnvironmentIdentifierDAO,
	clusterConnectionDAO ClusterConnectionDAO) ComposeEntityDAO {
	return &composeEntityDAOImpl{
		db:                              db,
		dataAppEnvironmentIdentifierDAO: dataAppEnvironmentIdentifierDAO,
		dataAppConnectionDAO:            dataAppConnectionDAO,
		environmentIdentifierDAO:        environmentIdentifierDAO,
		clusterConnectionDAO:            clusterConnectionDAO,
	}
}

func (d *composeEntityDAOImpl) DeleteIntegrationConfiguration(ctx context.Context, ID string) (err error) {
	ctx, span := Tracer.Start(ctx, "composeEntityDAOImpl.DeleteIntegrationConfiguration")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	ctxDB, cancel := context.WithTimeout(ctx, config.DefaultDBOperationTimeout)

	defer cancel()
	err = d.db.WithContext(ctxDB).Transaction(
		func(tx *gorm.DB) error {
			var connections []ClusterConnection
			if err := tx.Where("vercel_integration_configuration_id = ?", ID).Find(&connections).Error; err != nil {
				return errors.Trace(err)
			}
			if len(connections) != 0 {
				if err := tx.Select("EnvironmentIdentifier", "BranchEnvironmentIdentifiers").Delete(&connections).Error; err != nil {
					return errors.Trace(err)
				}
			}

			var dataAppConnections []DataAppConnection
			if err := tx.Where("vercel_integration_configuration_id = ?", ID).Find(&dataAppConnections).Error; err != nil {
				return errors.Trace(err)
			}
			if len(dataAppConnections) != 0 {
				if err := tx.Select("DataAppEnvironmentIdentifier").Delete(&dataAppConnections).Error; err != nil {
					return errors.Trace(err)
				}
			}

			if err := tx.Where("vercel_integration_configuration_id = ?", ID).Delete(&Check{}).Error; err != nil {
				return errors.Trace(err)
			}

			if err := tx.Where("id = ?", ID).Delete(&VercelIntegrationConfiguration{}).Error; err != nil {
				return errors.Trace(err)
			}
			return nil
		})

	if err != nil {
		return err
	}

	tilog.Info(ctx, "Delete integration configuration from DB")
	return nil
}

func (d *composeEntityDAOImpl) DeleteClusterConnection(ctx context.Context, tiDBClusterID uint64, vercelProjectID string) (err error) {
	ctx, span := Tracer.Start(ctx, "composeEntityDAOImpl.DeleteClusterConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	ctxDB, cancel := context.WithTimeout(ctx, config.DefaultDBOperationTimeout)
	defer cancel()

	err = d.db.WithContext(ctxDB).Transaction(
		func(tx *gorm.DB) error {
			var connection ClusterConnection
			if err := tx.Where("tidb_cluster_id = ? AND vercel_project_id = ?", tiDBClusterID, vercelProjectID).First(&connection).Error; err != nil {
				if serrors.Is(err, gorm.ErrRecordNotFound) {
					// not found, return immediately
					return nil
				} else {
					return errors.Trace(err)
				}
			}

			if err := tx.Select("EnvironmentIdentifier", "BranchEnvironmentIdentifiers").Delete(&connection).Error; err != nil {
				return errors.Trace(err)
			}

			return nil
		})

	if err != nil {
		return err
	}

	tilog.Info(ctx, "Delete connection between Vercel project and TiDB cluster")
	return nil
}

func (d *composeEntityDAOImpl) DeleteVercelConnectionsByVercelProjectIDs(ctx context.Context, vercelProjectIDs []string) (err error) {
	ctx, span := Tracer.Start(ctx, "composeEntityDAOImpl.DeleteClusterConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	ctxDB, cancel := context.WithTimeout(ctx, config.DefaultDBOperationTimeout)
	defer cancel()

	err = d.db.WithContext(ctxDB).Transaction(
		func(tx *gorm.DB) error {
			var connections []ClusterConnection
			if err := tx.Where("vercel_project_id IN ?", vercelProjectIDs).Find(&connections).Error; err != nil {
				return errors.Trace(err)
			}
			if len(connections) != 0 {
				if err := tx.Select("EnvironmentIdentifier", "BranchEnvironmentIdentifiers").Delete(&connections).Error; err != nil {
					return errors.Trace(err)
				}
			}

			var dataAppConnections []DataAppConnection
			if err := tx.Where("vercel_project_id IN ?", vercelProjectIDs).Find(&dataAppConnections).Error; err != nil {
				return errors.Trace(err)
			}
			if len(dataAppConnections) != 0 {
				if err := tx.Select("DataAppEnvironmentIdentifier").Delete(&dataAppConnections).Error; err != nil {
					return errors.Trace(err)
				}
			}
			return nil
		})

	if err != nil {
		return err
	}

	tilog.Info(ctx, "Delete connections under Vercel projects", zap.Any("vercelProjectIDs", vercelProjectIDs))
	return nil
}

func (d *composeEntityDAOImpl) DeleteClusterConnectionsByIDs(ctx context.Context, IDs []uint64) (err error) {
	ctx, span := Tracer.Start(ctx, "composeEntityDAOImpl.DeleteClusterConnectionsByIDs")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	ctxDB, cancel := context.WithTimeout(ctx, config.DefaultDBOperationTimeout)
	defer cancel()

	err = d.db.WithContext(ctxDB).Transaction(
		func(tx *gorm.DB) error {
			var connections []ClusterConnection
			if err := tx.Where("id IN ?", IDs).Find(&connections).Error; err != nil {
				return errors.Trace(err)
			}

			if len(connections) != 0 {
				if err := tx.Select("EnvironmentIdentifier").Delete(&connections).Error; err != nil {
					return errors.Trace(err)
				}
			}

			return nil
		})

	if err != nil {
		return err
	}

	tilog.Info(ctx, "Delete connections", zap.Any("ids", IDs))
	return nil
}

func (d *composeEntityDAOImpl) DeleteDataAppConnection(ctx context.Context, dataAppID uint64, vercelProjectID string) (err error) {
	ctx, span := Tracer.Start(ctx, "composeEntityDAOImpl.DeleteDataAppConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	ctxDB, cancel := context.WithTimeout(ctx, config.DefaultDBOperationTimeout)
	defer cancel()

	err = d.db.WithContext(ctxDB).Transaction(
		func(tx *gorm.DB) error {
			var connection DataAppConnection
			if err := tx.Where("data_app_id = ? AND vercel_project_id = ?", dataAppID, vercelProjectID).First(&connection).Error; err != nil {
				if serrors.Is(err, gorm.ErrRecordNotFound) {
					// not found, return immediately
					return nil
				} else {
					return errors.Trace(err)
				}
			}

			if err := tx.Select("DataAppEnvironmentIdentifier").Delete(&connection).Error; err != nil {
				return errors.Trace(err)
			}

			return nil
		})

	if err != nil {
		return err
	}

	tilog.Info(ctx, "Delete connection between Vercel project and data app")
	return nil
}

func (d *composeEntityDAOImpl) AddClusterConnection(ctx context.Context, connection *ClusterConnection, identifier *EnvironmentIdentifier) (err error) {
	ctx, span := Tracer.Start(ctx, "composeEntityDAOImpl.AddClusterConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	if connection == nil || identifier == nil {
		return errors.Errorf("cluster connection or identifier is nil")
	}
	ctxDB, cancel := context.WithTimeout(ctx, config.DefaultDBOperationTimeout)
	defer cancel()

	err = d.db.WithContext(ctxDB).Transaction(
		func(tx *gorm.DB) error {
			connResult, err := d.clusterConnectionDAO.AddVercelConnection(ctxDB, *connection)
			if err != nil {
				return err
			}

			identifier.ClusterConnectionID = connResult.ID
			err = d.environmentIdentifierDAO.AddEnvironmentIdentifier(ctxDB, *identifier)
			if err != nil {
				return err
			}

			return nil
		})

	if err != nil {
		return err
	}

	tilog.Info(ctx, "Add connection between Vercel project and serverless cluster")
	return nil
}

func (d *composeEntityDAOImpl) AddDataAppConnection(ctx context.Context, connection *DataAppConnection, identifier *DataAppEnvironmentIdentifier) (err error) {
	ctx, span := Tracer.Start(ctx, "composeEntityDAOImpl.AddDataAppConnection")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	if connection == nil || identifier == nil {
		return errors.Errorf("data app connection or identifier is nil")
	}
	ctxDB, cancel := context.WithTimeout(ctx, config.DefaultDBOperationTimeout)
	defer cancel()

	err = d.db.WithContext(ctxDB).Transaction(
		func(tx *gorm.DB) error {
			connResult, err := d.dataAppConnectionDAO.AddVercelConnection(ctxDB, *connection)
			if err != nil {
				return err
			}

			identifier.DataAppConnectionID = connResult.ID
			err = d.dataAppEnvironmentIdentifierDAO.AddEnvironmentIdentifier(ctxDB, *identifier)
			if err != nil {
				return err
			}

			return nil
		})

	if err != nil {
		return err
	}

	tilog.Info(ctx, "Add connection between Vercel project and data app")
	return nil
}
