package vercel

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"net/url"
	"strconv"

	"github.com/tidbcloud/ecosystem-service/config"
	"github.com/tidbcloud/ecosystem-service/internal/service/grpc/branch"
	vercelProxy "github.com/tidbcloud/ecosystem-service/internal/service/proxy/vercel"
	vercelDB "github.com/tidbcloud/ecosystem-service/internal/storage/db/vercel"
	"github.com/tidbcloud/ecosystem-service/internal/utils"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.uber.org/zap"
	"golang.org/x/exp/slices"
)

const (
	BranchAnnotationKey   = "created-by"
	BranchAnnotationValue = "tidbcloud/branching-vercel"
)

type BranchLogic struct {
	clusterConnectionDAO   vercelDB.ClusterConnectionDAO
	checkDAO               vercelDB.CheckDAO
	configurationDAO       vercelDB.VercelIntegrationConfigurationDAO
	vercelAPIClient        vercelProxy.APIClient
	branchSvc              branch.BranchSvc
	conf                   *config.ConfigWrapper
	branchEnvIdentifierDAO vercelDB.BranchEnvironmentIdentifierDAO
	branchTaskDAO          vercelDB.BranchTaskDAO
}

func NewBranchLogic(clusterConnectionDAO vercelDB.ClusterConnectionDAO,
	checkDAO vercelDB.CheckDAO,
	configurationDAO vercelDB.VercelIntegrationConfigurationDAO,
	vercelAPIClient vercelProxy.APIClient,
	branchSvc branch.BranchSvc,
	conf *config.ConfigWrapper,
	branchEnvIdentifierDAO vercelDB.BranchEnvironmentIdentifierDAO,
	branchTaskDAO vercelDB.BranchTaskDAO,
) *BranchLogic {
	return &BranchLogic{
		clusterConnectionDAO:   clusterConnectionDAO,
		checkDAO:               checkDAO,
		configurationDAO:       configurationDAO,
		vercelAPIClient:        vercelAPIClient,
		branchSvc:              branchSvc,
		conf:                   conf,
		branchEnvIdentifierDAO: branchEnvIdentifierDAO,
		branchTaskDAO:          branchTaskDAO,
	}
}

func (l *BranchLogic) HandleCreated(ctx context.Context, payload DeploymentPayload, logger *zap.Logger) (err error) {
	logger = logger.With(zap.String("deployment_id", payload.Deployment.ID)).With(zap.String("deployment_name", payload.Deployment.Name)).
		With(zap.String("project_id", payload.Project.ID))
	logger.Info("start to handle deployment created event")
	conn, err := l.clusterConnectionDAO.GetVercelConnectionByVercelProjectID(ctx, payload.Project.ID)
	if err != nil {
		logger.Error("failed to get vercel connection", zap.Error(err))
		return err
	}
	// There are three cases that we need to skip:
	// 1. The project is not connected to a cluster.
	// 2. Branching is disabled in the connection.
	// 3. The deployment is not a pull request preview.
	if conn == nil {
		logger.Info("project is not connected to cluster, skip this event")
		return nil
	}
	if !conn.BranchEnabled {
		logger.Info("branching is disabled, skip this event", zap.Uint64("connection_id", conn.ID))
		return nil
	}
	if payload.Target != nil {
		logger.Info("target is not nil, skip.", zap.String("target", *payload.Target))
		return nil
	}

	teamID := utils.GetTeamIDString(conn.VercelIntegrationConfiguration.TeamID)
	detailsPage := utils.GenerateDetailUrl(l.conf.ServerConfig.TiDBCloudHost, conn)

	check, err := l.checkDAO.GetCheckByDeploymentID(ctx, payload.Deployment.ID)
	if err != nil {
		logger.Error("failed to get check", zap.Error(err))
		return err
	}

	// Consider the case that the event is retried.
	if check == nil {
		checkResp, err := l.vercelAPIClient.CreateCheck(ctx, payload.Deployment.ID, teamID, conn.VercelIntegrationConfiguration.ApiAccessToken)
		if err != nil {
			logger.Error("failed to create check", zap.Error(err))
			var bizErr bizerr.BizError
			if errors.As(err, &bizErr) && bizErr.ErrCode() == vercelProxy.ErrorCodeVercelApiUnrecoverableError {
				logger.Error("skip this event because of unrecoverable error", zap.Error(err))
				return nil
			}
			return err
		}

		repoID, exist := payload.Deployment.Meta["githubRepoId"]
		if !exist {
			err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, checkResp.ID, conn.VercelIntegrationConfiguration.ApiAccessToken,
				detailsPage, "githubRepoId not found, only support GitHub for now", teamID, logger)
			if err != nil {
				return err
			}

			logger.Error("githubRepoId not found, skip this event")
			return nil
		}

		c := vercelDB.Check{
			ID:                               checkResp.ID,
			DeploymentID:                     payload.Deployment.ID,
			VercelProjectID:                  payload.Project.ID,
			VercelIntegrationConfigurationID: conn.VercelIntegrationConfiguration.ID,
			GitBranchRef:                     payload.Deployment.Meta["githubCommitRef"],
			GitHubRepoID:                     repoID,
		}
		err = l.checkDAO.AddCheck(ctx, c)
		if err != nil {
			logger.Error("failed to add check to DB", zap.Error(err))
			return err
		}
		check = &c
	}

	defer func() {
		if err != nil {
			err = l.vercelAPIClient.UpdateCheck(ctx, payload.Deployment.ID, check.ID, vercelProxy.UpdateCheckBody{
				Conclusion: vercelProxy.ConclusionFailed,
				DetailsUrl: detailsPage,
				Name:       err.Error(),
			}, teamID, conn.VercelIntegrationConfiguration.ApiAccessToken)
			if err != nil {
				logger.Error("failed to update check", zap.Error(err))
			}
		}
	}()

	// Only support GitHub for now.
	ref, exist := payload.Deployment.Meta["githubCommitRef"]
	if !exist {
		err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, conn.VercelIntegrationConfiguration.ApiAccessToken,
			detailsPage, "githubCommitRef not found, only support GitHub for now", teamID, logger)
		if err != nil {
			logger.Error("failed to update check", zap.Error(err))
			return err
		}

		// Skip if the deployment is not a GitHub pull request preview.
		logger.Error("githubCommitRef not found, skip this event")
		return nil
	}
	logger = logger.With(zap.String("branch_name", ref))

	var brID string
	branches, err := l.branchSvc.ListBranches(ctx, conn.TiDBClusterID)
	if err != nil {
		logger.Error("failed to list branches", zap.Error(err))
		return fmt.Errorf("failed to list branches")
	}
	for _, b := range branches {
		if b.DisplayName == ref {
			logger.Info("branch already exists", zap.String("branchName", ref))
			brID = b.Id
		}
	}

	if brID == "" {
		id, err := l.branchSvc.CreateBranch(ctx, conn.TiDBClusterID, strconv.FormatUint(conn.TiDBClusterID, 10), ref,
			map[string]string{BranchAnnotationKey: BranchAnnotationValue})
		if err != nil {
			logger.Error("failed to create branch", zap.Error(err))
			return fmt.Errorf("failed to create branch")
		}
		brID = *id
	}

	err = l.branchTaskDAO.AddTask(ctx, vercelDB.BranchTask{
		BranchID:        brID,
		DeploymentID:    payload.Deployment.ID,
		DeploymentName:  payload.Deployment.Name,
		VercelProjectID: payload.Project.ID,
		Status:          vercelDB.Pending,
	})
	if err != nil {
		logger.Error("failed to add branch task", zap.Error(err))
		return err
	}

	return nil
}

func (l *BranchLogic) HandleReady(ctx context.Context, payload DeploymentPayload, logger *zap.Logger) (err error) {
	logger = logger.With(zap.String("deployment_id", payload.Deployment.ID)).With(zap.String("deployment_name", payload.Deployment.Name)).
		With(zap.String("project_id", payload.Project.ID))

	logger.Info("start to handle deployment ready event")
	conn, err := l.clusterConnectionDAO.GetVercelConnectionByVercelProjectID(ctx, payload.Project.ID)
	if err != nil {
		logger.Error("failed to get vercel connection", zap.Error(err))
		return err
	}
	// Skip if the project is not connected to a cluster.
	if conn == nil {
		logger.Error("connection not found, skip this event")
		return nil
	}

	check, err := l.checkDAO.GetCheckByDeploymentID(ctx, payload.Deployment.ID)
	if err != nil {
		logger.Error("failed to get check", zap.Error(err))
		return err
	}
	// Skip if the check is not found, which means the deployment is finished regardless of the result.
	if check == nil {
		logger.Error("check not found, skip this event")
		return nil
	}

	teamID := utils.GetTeamIDString(check.VercelIntegrationConfiguration.TeamID)
	detailsPage := utils.GenerateDetailUrl(l.conf.ServerConfig.TiDBCloudHost, conn)

	// Only support GitHub for now.
	ref, exist := payload.Deployment.Meta["githubCommitRef"]
	if !exist {
		err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, conn.VercelIntegrationConfiguration.ApiAccessToken,
			detailsPage, "githubCommitRef not found, only support GitHub for now", teamID, logger)
		if err != nil {
			return err
		}

		// Skip if the deployment is not a GitHub pull request preview.
		logger.Error("githubCommitRef not found, skip this event")
		return nil
	}
	logger = logger.With(zap.String("branch_name", ref))

	defer func() {
		if err != nil {
			err = l.vercelAPIClient.UpdateCheck(ctx, payload.Deployment.ID, check.ID, vercelProxy.UpdateCheckBody{
				Conclusion: vercelProxy.ConclusionFailed,
				DetailsUrl: detailsPage,
				Name:       err.Error(),
			}, teamID, check.VercelIntegrationConfiguration.ApiAccessToken)
			if err != nil {
				logger.Error("failed to update check", zap.Error(err))
			}
		}
	}()

	deployment, err := l.vercelAPIClient.GetDeployment(ctx, payload.Deployment.ID, teamID, check.VercelIntegrationConfiguration.ApiAccessToken)
	if err != nil {
		var bizErr bizerr.BizError
		if errors.As(err, &bizErr) && bizErr.ErrCode() == vercelProxy.ErrorCodeVercelApiUnrecoverableError {
			err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, check.VercelIntegrationConfiguration.ApiAccessToken,
				detailsPage, "failed to get deployment from Vercel API", teamID, logger)
			if err != nil {
				return err
			}
			logger.Error("skip this event because of unrecoverable error", zap.Error(err))
			return nil
		}

		logger.Error("failed to get deployment", zap.Error(err))
		return fmt.Errorf("failed to get deployment")
	}

	envs, err := l.branchEnvIdentifierDAO.GetEnvironmentIdentifierByGitBranch(ctx, check.GitHubRepoID, check.GitBranchRef, conn.ID)
	if err != nil {
		logger.Error("failed to get environment identifier", zap.Error(err))
		return fmt.Errorf("failed to get environment identifier")
	}

	if envs == nil {
		err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, check.VercelIntegrationConfiguration.ApiAccessToken,
			detailsPage, "envs not found in deployment, please redeploy", teamID, logger)
		if err != nil {
			return err
		}

		logger.Info("envs not found in deployment, please redeploy")
		return nil
	}

	if envs.TiDBHostID.Valid {
		if !slices.Contains(deployment.Env, "TIDB_HOST") {
			err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, check.VercelIntegrationConfiguration.ApiAccessToken,
				detailsPage, "TIDB_HOST not found in deployment env, please redeploy", teamID, logger)
			if err != nil {
				return err
			}

			logger.Error("TIDB_HOST not found in deployment env")
			return nil
		}
	}
	if envs.TiDBPortID.Valid {
		if !slices.Contains(deployment.Env, "TIDB_PORT") {
			err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, check.VercelIntegrationConfiguration.ApiAccessToken,
				detailsPage, "TIDB_PORT not found in deployment env, please redeploy", teamID, logger)
			if err != nil {
				return err
			}

			logger.Error("TIDB_PORT not found in deployment env")
			return nil
		}
	}
	if envs.TiDBUserID.Valid {
		if !slices.Contains(deployment.Env, "TIDB_USER") {
			err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, check.VercelIntegrationConfiguration.ApiAccessToken,
				detailsPage, "TIDB_USER not found in deployment env, please redeploy", teamID, logger)
			if err != nil {
				return err
			}

			logger.Error("TIDB_USER not found in deployment env")
			return nil
		}
	}
	if envs.TiDBPasswordID.Valid {
		if !slices.Contains(deployment.Env, "TIDB_PASSWORD") {
			err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, check.VercelIntegrationConfiguration.ApiAccessToken,
				detailsPage, "TIDB_PASSWORD not found in deployment env, please redeploy", teamID, logger)
			if err != nil {
				return err
			}

			logger.Error("TIDB_PASSWORD not found in deployment env")
			return nil
		}
	}
	if envs.TiDBDatabaseID.Valid {
		if !slices.Contains(deployment.Env, "TIDB_DATABASE") {
			err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, check.VercelIntegrationConfiguration.ApiAccessToken,
				detailsPage, "TIDB_DATABASE not found in deployment env, please redeploy", teamID, logger)
			if err != nil {
				return err
			}

			logger.Error("TIDB_DATABASE not found in deployment env")
			return nil
		}
	}
	if envs.DatabaseUrlID.Valid {
		if !slices.Contains(deployment.Env, "DATABASE_URL") {
			err := l.updateCheckWithFailedConclusion(ctx, payload.Deployment.ID, check.ID, check.VercelIntegrationConfiguration.ApiAccessToken,
				detailsPage, "DATABASE_URL not found in deployment env, please redeploy", teamID, logger)
			if err != nil {
				return err
			}

			logger.Error("DATABASE_URL not found in deployment env")
			return nil
		}
	}

	err = l.vercelAPIClient.UpdateCheck(ctx, payload.Deployment.ID, check.ID, vercelProxy.UpdateCheckBody{
		Conclusion: vercelProxy.ConclusionSucceeded,
		DetailsUrl: detailsPage,
		Name:       "TiDB Serverless Branching Check",
	}, teamID, check.VercelIntegrationConfiguration.ApiAccessToken)
	if err != nil {
		logger.Error("failed to update check", zap.Error(err))
		var bizErr bizerr.BizError
		if errors.As(err, &bizErr) && bizErr.ErrCode() == vercelProxy.ErrorCodeVercelApiUnrecoverableError {
			logger.Error("skip this event because of unrecoverable error", zap.Error(err))
			return nil
		}
		return fmt.Errorf("failed to update check")
	}

	err = l.checkDAO.DeleteCheck(ctx, check.ID)
	if err != nil {
		logger.Error("failed to delete check in DB", zap.Error(err))
	}

	logger.Info("branch is ready, the check is succeeded")

	return nil
}

func (l *BranchLogic) HandleCanceled(ctx context.Context, p DeploymentPayload, logger *zap.Logger) error {
	logger = logger.With(zap.String("deployment_id", p.Deployment.ID)).With(zap.String("deployment_name", p.Deployment.Name)).
		With(zap.String("project_id", p.Project.ID))
	logger.Info("start to handle deployment canceled event")

	check, err := l.checkDAO.GetCheckByDeploymentID(ctx, p.Deployment.ID)
	if err != nil {
		logger.Error("failed to get check", zap.Error(err))
		return err
	}

	if check != nil {
		err := l.checkDAO.DeleteCheck(ctx, check.ID)
		if err != nil {
			logger.Error("failed to delete check", zap.Error(err))
			return err
		}
	}

	return nil
}

func (l *BranchLogic) HandleError(ctx context.Context, p DeploymentPayload, logger *zap.Logger) error {
	logger = logger.With(zap.String("deployment_id", p.Deployment.ID)).With(zap.String("deployment_name", p.Deployment.Name)).
		With(zap.String("project_id", p.Project.ID))
	logger.Info("start to handle deployment error event")

	check, err := l.checkDAO.GetCheckByDeploymentID(ctx, p.Deployment.ID)
	if err != nil {
		logger.Error("failed to get check", zap.Error(err))
		return err
	}

	if check != nil {
		err := l.checkDAO.DeleteCheck(ctx, check.ID)
		if err != nil {
			logger.Error("failed to delete check", zap.Error(err))
			return err
		}
	}

	return nil
}

// updateCheckWithFailedConclusion update check with failed conclusion, return task id if update success or with unrecoverable error, otherwise return nil
func (l *BranchLogic) updateCheckWithFailedConclusion(ctx context.Context, deploymentID, checkID, token string,
	detailedUrl string, errMessage string, teamID string, taskLogger *zap.Logger) error {
	err := l.vercelAPIClient.UpdateCheck(ctx, deploymentID, checkID, vercelProxy.UpdateCheckBody{
		Conclusion: vercelProxy.ConclusionFailed,
		DetailsUrl: detailedUrl,
		Name:       errMessage,
	}, teamID, token)
	if err != nil {
		var bizErr bizerr.BizError
		if errors.As(err, &bizErr) && bizErr.ErrCode() == vercelProxy.ErrorCodeVercelApiUnrecoverableError {
			taskLogger.Error("skip this event because of unrecoverable error when updating the check", zap.Error(err))
			return nil
		} else {
			// update check failed with recoverable error, so we still need to retry this task
			taskLogger.Error("failed to update check", zap.Error(err))
			return err
		}
	} else {
		return nil
	}
}

func (l *BranchLogic) CreateVercelEnvs(ctx context.Context, conn *vercelDB.ClusterConnection, branchID string, logger *zap.Logger, check *vercelDB.Check, teamID string) error {
	logger.Info("start to create vercel envs")
	b, bizErr := l.branchSvc.GetBranch(ctx, conn.TiDBClusterID, branchID)
	if bizErr != nil {
		logger.Error("failed to get branch", zap.Error(bizErr))
		return bizErr
	}
	user, bizErr := l.branchSvc.CreateBranchUser(ctx, branchID)
	if bizErr != nil {
		logger.Error("failed to create branch user", zap.Error(bizErr))
		return bizErr
	}

	var descriptions []vercelProxy.EnvironmentDescription
	if conn.FrameworkType == "General" {
		descriptions = []vercelProxy.EnvironmentDescription{
			{
				Key:               "TIDB_HOST",
				TargetEnvironment: []string{"preview"},
				GitBranch:         &check.GitBranchRef,
				Type:              "encrypted",
				Value:             b.Endpoints.PublicEndpoint.Host,
			},
			{
				Key:               "TIDB_PORT",
				TargetEnvironment: []string{"preview"},
				GitBranch:         &check.GitBranchRef,
				Type:              "encrypted",
				Value:             strconv.FormatInt(int64(b.Endpoints.PublicEndpoint.Port), 10),
			},
			{
				Key:               "TIDB_USER",
				TargetEnvironment: []string{"preview"},
				GitBranch:         &check.GitBranchRef,
				Type:              "encrypted",
				Value:             user.Username,
			},
			{
				Key:               "TIDB_PASSWORD",
				TargetEnvironment: []string{"preview"},
				GitBranch:         &check.GitBranchRef,
				Type:              "encrypted",
				Value:             user.Password,
			},
			{
				Key:               "TIDB_DATABASE",
				TargetEnvironment: []string{"preview"},
				GitBranch:         &check.GitBranchRef,
				Type:              "encrypted",
				Value:             conn.Database,
			},
		}
	} else if conn.FrameworkType == "Prisma" {
		descriptions = []vercelProxy.EnvironmentDescription{
			{
				Key:               "DATABASE_URL",
				TargetEnvironment: []string{"preview"},
				GitBranch:         &check.GitBranchRef,
				Type:              "encrypted",
				Value:             fmt.Sprintf("mysql://%s:%s@%s:%d/%s?sslaccept=%s", user.Username, url.QueryEscape(user.Password), b.Endpoints.PublicEndpoint.Host, b.Endpoints.PublicEndpoint.Port, url.QueryEscape(conn.Database), "strict"),
			},
		}
	} else if conn.FrameworkType == "TiDB Cloud Serverless Driver" {
		descriptions = []vercelProxy.EnvironmentDescription{
			{
				Key:               "DATABASE_URL",
				TargetEnvironment: []string{"preview"},
				GitBranch:         &check.GitBranchRef,
				Type:              "encrypted",
				Value:             fmt.Sprintf("mysql://%s:%s@%s/%s", user.Username, url.QueryEscape(user.Password), b.Endpoints.PublicEndpoint.Host, url.QueryEscape(conn.Database)),
			},
		}
	} else {
		return fmt.Errorf("unsupported framework type: %s", conn.FrameworkType)
	}

	result, err := l.vercelAPIClient.CreateEnvironmentVariables(ctx, check.VercelProjectID, teamID, descriptions, check.VercelIntegrationConfiguration.ApiAccessToken)
	if err != nil {
		logger.Error("failed to create environment variables", zap.Error(err))
		return err
	}
	identifierEntity, err := transferToBranchIdentifierEntity(conn.ID, check.GitHubRepoID, check.GitBranchRef, branchID, result)
	if err != nil {
		logger.Error("failed to transfer to branch identifier entity", zap.Error(err))
		return err
	}
	err = l.branchEnvIdentifierDAO.AddEnvironmentIdentifier(ctx, *identifierEntity)
	if err != nil {
		logger.Error("failed to add environment identifier", zap.Error(err))
		return err
	}
	return nil
}

func (l *BranchLogic) UpdateVercelEnvs(ctx context.Context, conn *vercelDB.ClusterConnection, branchID string, logger *zap.Logger, check *vercelDB.Check, teamID string, envs *vercelDB.BranchEnvironmentIdentifier) error {
	logger.Info("start to update vercel envs")
	b, bizErr := l.branchSvc.GetBranch(ctx, conn.TiDBClusterID, branchID)
	if bizErr != nil {
		logger.Error("failed to get branch", zap.Error(bizErr))
		return bizErr
	}
	user, bizErr := l.branchSvc.CreateBranchUser(ctx, branchID)
	if bizErr != nil {
		logger.Error("failed to create branch user", zap.Error(bizErr))
		return bizErr
	}

	updateEnvs := make(map[string]vercelProxy.EnvironmentDescription)
	if envs.DatabaseUrlID.Valid {
		updateEnvs[envs.DatabaseUrlID.String] = vercelProxy.EnvironmentDescription{
			Key:               "DATABASE_URL",
			TargetEnvironment: []string{"preview"},
			GitBranch:         &check.GitBranchRef,
			Type:              "encrypted",
			Value:             fmt.Sprintf("mysql://%s:%s@%s:%d/%s?sslaccept=%s", user.Username, user.Password, b.Endpoints.PublicEndpoint.Host, b.Endpoints.PublicEndpoint.Port, conn.Database, "strict"),
		}
	}
	if envs.TiDBHostID.Valid {
		updateEnvs[envs.TiDBHostID.String] = vercelProxy.EnvironmentDescription{
			Key:               "TIDB_HOST",
			TargetEnvironment: []string{"preview"},
			GitBranch:         &check.GitBranchRef,
			Type:              "encrypted",
			Value:             b.Endpoints.PublicEndpoint.Host,
		}
	}
	if envs.TiDBPortID.Valid {
		updateEnvs[envs.TiDBPortID.String] = vercelProxy.EnvironmentDescription{
			Key:               "TIDB_PORT",
			TargetEnvironment: []string{"preview"},
			GitBranch:         &check.GitBranchRef,
			Type:              "encrypted",
			Value:             strconv.FormatInt(int64(b.Endpoints.PublicEndpoint.Port), 10),
		}
	}
	if envs.TiDBUserID.Valid {
		updateEnvs[envs.TiDBUserID.String] = vercelProxy.EnvironmentDescription{
			Key:               "TIDB_USER",
			TargetEnvironment: []string{"preview"},
			GitBranch:         &check.GitBranchRef,
			Type:              "encrypted",
			Value:             user.Username,
		}
	}
	if envs.TiDBPasswordID.Valid {
		updateEnvs[envs.TiDBPasswordID.String] = vercelProxy.EnvironmentDescription{
			Key:               "TIDB_PASSWORD",
			TargetEnvironment: []string{"preview"},
			GitBranch:         &check.GitBranchRef,
			Type:              "encrypted",
			Value:             user.Password,
		}
	}
	if envs.TiDBDatabaseID.Valid {
		updateEnvs[envs.TiDBDatabaseID.String] = vercelProxy.EnvironmentDescription{
			Key:               "TIDB_DATABASE",
			TargetEnvironment: []string{"preview"},
			GitBranch:         &check.GitBranchRef,
			Type:              "encrypted",
			Value:             conn.Database,
		}
	}

	err := l.vercelAPIClient.UpdateEnvironmentVariables(ctx, check.VercelProjectID, teamID, updateEnvs, check.VercelIntegrationConfiguration.ApiAccessToken)
	if err != nil {
		logger.Error("failed to update environment variables", zap.Error(err))
		return err
	}

	return nil
}

func transferToBranchIdentifierEntity(connID uint64, repoID string, branchRef string, branchID string, res []vercelProxy.EnvironmentInfo) (*vercelDB.BranchEnvironmentIdentifier, error) {
	identifier := vercelDB.BranchEnvironmentIdentifier{
		ClusterConnectionID: connID,
		GitHubRepoID:        repoID,
		GitBranchRef:        branchRef,
		BranchID:            branchID,
	}
	for _, elem := range res {
		if elem.Key == "TIDB_HOST" {
			identifier.TiDBHostID = sql.NullString{
				String: elem.ID,
				Valid:  true,
			}
		} else if elem.Key == "TIDB_PORT" {
			identifier.TiDBPortID = sql.NullString{
				String: elem.ID,
				Valid:  true,
			}
		} else if elem.Key == "TIDB_USER" {
			identifier.TiDBUserID = sql.NullString{
				String: elem.ID,
				Valid:  true,
			}
		} else if elem.Key == "TIDB_PASSWORD" {
			identifier.TiDBPasswordID = sql.NullString{
				String: elem.ID,
				Valid:  true,
			}
		} else if elem.Key == "TIDB_DATABASE" {
			identifier.TiDBDatabaseID = sql.NullString{
				String: elem.ID,
				Valid:  true,
			}
		} else if elem.Key == "DATABASE_URL" {
			identifier.DatabaseUrlID = sql.NullString{
				String: elem.ID,
				Valid:  true,
			}
		} else {
			return nil, fmt.Errorf("Unknown environment variable, key: %s", elem.Key)
		}
	}
	return &identifier, nil
}
