package vercel

import (
	"context"
	"fmt"

	"github.com/pingcap/errors"
	dataapipb "github.com/tidbcloud/dataservices/idl/pbgen/proto/dataapi"
	"github.com/tidbcloud/ecosystem-service/internal/service/grpc/dataapi"
	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"
	"go.uber.org/zap"
)

type DataAppConnLogic struct {
	connectionDAO    vercelDB.DataAppConnectionDAO
	configurationDAO vercelDB.VercelIntegrationConfigurationDAO
	vercelAPIClient  vercelProxy.APIClient
	dataApiSvc       dataapi.DataApiSvc
	composeEntityDAO vercelDB.ComposeEntityDAO
}

func NewDataAppConnLogic(configurationDAO vercelDB.VercelIntegrationConfigurationDAO,
	vercelAPIClient vercelProxy.APIClient,
	connectionDAO vercelDB.DataAppConnectionDAO,
	dataApiSvc dataapi.DataApiSvc,
	composeEntityDAO vercelDB.ComposeEntityDAO) *DataAppConnLogic {
	return &DataAppConnLogic{
		configurationDAO: configurationDAO,
		vercelAPIClient:  vercelAPIClient,
		connectionDAO:    connectionDAO,
		dataApiSvc:       dataApiSvc,
		composeEntityDAO: composeEntityDAO,
	}
}

func (l *DataAppConnLogic) CheckConnIfExists(ctx context.Context, dataAppID uint64, vercelProjectID string) (bool, error) {
	c, err := l.connectionDAO.GetVercelConnection(ctx, dataAppID, vercelProjectID)
	if err != nil {
		return false, errors.Annotate(err, "GetVercelConnection failed")
	}
	if c != nil {
		return true, nil
	}
	return false, nil
}

func (l *DataAppConnLogic) CheckVercelProjectIfConfigured(ctx context.Context, vercelProjectID string) (bool, error) {
	c, err := l.connectionDAO.GetVercelConnectionByVercelProjectID(ctx, vercelProjectID)
	if err != nil {
		return false, errors.Annotate(err, "GetVercelConnection failed")
	}
	if c != nil {
		return true, nil
	}
	return false, nil
}

func (l *DataAppConnLogic) AddDataAppConnection(ctx context.Context, vercelProjectID string, vercelConfigID string, environments *DataAppEnvironments, connection *vercelDB.DataAppConnection) error {
	configuration, err := l.configurationDAO.GetIntegrationConfigurationByID(ctx, vercelConfigID)
	if err != nil {
		return err
	}

	descriptions := []vercelProxy.EnvironmentDescription{
		{
			Key:               envDataAppBaseUrl,
			TargetEnvironment: []string{"production", "preview", "development"},
			Type:              "encrypted",
			Value:             *environments.DataAppBaseUrl,
		},
		{
			Key:               envDataAppPublicKey,
			TargetEnvironment: []string{"production", "preview", "development"},
			Type:              "encrypted",
			Value:             *environments.DataAppPublicKey,
		},
		{
			Key:               envDataAppPrivateKey,
			TargetEnvironment: []string{"production", "preview", "development"},
			Type:              "encrypted",
			Value:             *environments.DataAppPrivateKey,
		},
	}

	result, err := l.vercelAPIClient.CreateEnvironmentVariables(ctx, vercelProjectID, utils.GetTeamIDString(configuration.TeamID), descriptions, configuration.ApiAccessToken)
	if err != nil {
		return err
	}

	identifier, err := transferToDataAppIdentifierEntity(result)
	if err != nil {
		return err
	}

	err = l.composeEntityDAO.AddDataAppConnection(ctx, connection, identifier)
	if err != nil {
		return err
	}

	return nil
}

func (l *DataAppConnLogic) PrepareEnvironmentVariables(ctx context.Context, dataApp *dataapipb.AppInfo, userEmail, vercelProject string) (*DataAppEnvironments, error) {
	apiKey, err := l.dataApiSvc.CreateApiKey(ctx, dataApp.Id, userEmail, fmt.Sprintf("Created by Vercel integration, used by Vercel project %s", vercelProject))
	if err != nil {
		return nil, err
	}

	return &DataAppEnvironments{
		DataAppBaseUrl:    &dataApp.ServiceUrl,
		DataAppPublicKey:  &apiKey.AccessKey,
		DataAppPrivateKey: &apiKey.SecretKey,
	}, nil
}

func (l *DataAppConnLogic) DeleteVercelEnvs(ctx context.Context, dataAppID uint64, req VercelProject, logger *zap.Logger) error {
	connection, err := l.connectionDAO.GetVercelConnection(ctx, dataAppID, req.VercelProjectID)
	if err != nil {
		return err
	}

	if connection == nil {
		return errors.NotFoundf("connection")
	}

	if err := l.DeleteEnvsByEnvIdentifier(ctx, connection.VercelProjectID, utils.GetTeamIDString(connection.VercelIntegrationConfiguration.TeamID), connection.DataAppEnvironmentIdentifier, connection.VercelIntegrationConfiguration.ApiAccessToken); err != nil {
		return err
	}

	logger.Info(fmt.Sprintf("DeleteVercelEnvs for %s", connection.VercelProjectID))
	return nil
}

func (l *DataAppConnLogic) DeleteEnvsByEnvIdentifier(ctx context.Context, vercelProjectID string, teamID string, identifier vercelDB.DataAppEnvironmentIdentifier, token string) error {
	var envsToDelete []string
	if identifier.DataAppBaseUrl.Valid && identifier.DataAppBaseUrl.String != "" {
		envsToDelete = append(envsToDelete, identifier.DataAppBaseUrl.String)
	}
	if identifier.DataAppPublicKey.Valid && identifier.DataAppPublicKey.String != "" {
		envsToDelete = append(envsToDelete, identifier.DataAppPublicKey.String)
	}
	if identifier.DataAppPrivateKey.Valid && identifier.DataAppPrivateKey.String != "" {
		envsToDelete = append(envsToDelete, identifier.DataAppPrivateKey.String)
	}

	return l.vercelAPIClient.DeleteEnvironmentVariables(ctx, vercelProjectID, teamID, envsToDelete, token)
}

func (l *DataAppConnLogic) validateDataApp(ctx context.Context, dataApp *dataapipb.AppInfo, orgID, projectID, dataAppID uint64) error {
	if dataApp == nil {
		return errors.New("data app not found")
	}

	if dataApp.OrgId != orgID {
		return errors.New("data app is not in this organization")
	}

	if dataApp.ProjectId != projectID {
		return errors.New("data app is not in this project")
	}

	if dataApp.Id != dataAppID {
		return errors.Errorf("data app id is not consistent, dataAppID in query: %d, ID in dataApp: %d", dataAppID, dataApp.Id)
	}

	if dataApp.IsDeleted == DataAppIsDeleted {
		return errors.New("data app is deleted")
	}

	return nil
}
