package integration

import (
	context "context"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/errcode"
	consolev1 "github.com/tidbcloud/metrics-integration/global/api/integration/v1"
	"github.com/tidbcloud/metrics-integration/global/internal/domain/controller/integration"
	"github.com/tidbcloud/metrics-integration/global/internal/domain/entity"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/account"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/di"
	"github.com/tidbcloud/metrics-integration/global/pkg/cvt"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type integrationServiceServer struct {
	consolev1.UnimplementedIntegrationServiceServer
}

func NewIntegrationServiceServer() consolev1.IntegrationServiceServer {
	return &integrationServiceServer{}
}

func (i *integrationServiceServer) CreateIntegration(ctx context.Context, req *consolev1.CreateIntegrationRequest) (resp *consolev1.IntegrationStatusResp, err error) {
	if len(req.Payloads) == 0 {
		err := errcode.NewInvalidInputErr(errors.New("empty payloads"))
		return nil, err
	}

	// validate and create integration
	var (
		integrationResp entity.Integration
		s               consolev1.IntegrationStatus
		ddUrl           string
		ddDashboardUrl  string
		ddMaskedKey     string

		nrUrl          string
		nrDashboardUrl string
		nrMaskedKey    string
	)

	user := account.GetUserFromContext(ctx)

	switch req.Type {
	case consolev1.IntegrationType_INTEGRATION_TYPE_DATADOG:
		// Validate Datadog api key first.
		valid, err := integration.ValidateDatadogAPIKey(ctx, req.DatadogApiKey, cvt.DatadogSiteConsoleAPIToDB(req.DatadogSite))
		if err != nil {
			err = errcode.NewInternalErr(errors.Annotate(err, "validate datadog api key failed"))
			return nil, err
		}
		if !valid {
			err = errcode.NewInvalidInputErr(errors.New("invalid datadog api key"))
			return nil, err
		}

		integrationResp, err = integration.CreateDatadogIntegration(ctx, entity.ID(req.ClusterId), cvt.DatadogSiteConsoleAPIToDB(req.DatadogSite), req.DatadogApiKey, user.Email)
		if err != nil {
			err = errcode.NewInternalErr(errors.Annotate(err, "creating datadog integration failed"))
			return nil, err
		}
		s = cvt.StatusDBToConsoleAPI(integrationResp.Status)
		ddMaskedKey = integrationResp.MaskedDatadogApiKey
		ddUrl = cvt.GetDatadogSiteUrl(cvt.DatadogSiteConsoleAPIToDB(req.DatadogSite))
		ddDashboardUrl = cvt.GetDatadogDashboardUrl(cvt.DatadogSiteConsoleAPIToDB(req.DatadogSite))
	case consolev1.IntegrationType_INTEGRATION_TYPE_NEWRELIC:
		// Validate newrelic api key first.
		valid, err := integration.ValidateNewrelicAPIKey(ctx, req.NewrelicApiKey, cvt.NewrelicSiteConsoleAPIToDB(req.NewrelicSite))
		if err != nil {
			err = errcode.NewInternalErr(errors.Annotate(err, "validate newrelic api key failed"))
			return nil, err
		}
		if !valid {
			err = errcode.NewInvalidInputErr(errors.New("invalid newrelic api key"))
			return nil, err
		}

		integrationResp, err = integration.CreateNewrelicIntegration(ctx, entity.ID(req.ClusterId), cvt.NewrelicSiteConsoleAPIToDB(req.NewrelicSite), req.NewrelicApiKey, user.Email)
		if err != nil {
			err = errcode.NewInternalErr(errors.Annotate(err, "creating newrelic integration failed"))
			return nil, err
		}
		s = cvt.StatusDBToConsoleAPI(integrationResp.Status)
		nrMaskedKey = integrationResp.MaskedNewrelicApiKey
		nrUrl = cvt.GetNewrelicSiteEndpoint(cvt.NewrelicSiteConsoleAPIToDB(req.NewrelicSite))
		nrDashboardUrl = cvt.GetNewrelicSiteDashboard(cvt.NewrelicSiteConsoleAPIToDB(req.NewrelicSite))
	default:
		err := errcode.NewInvalidInputErr(errors.New("invalid integration type"))
		return nil, err
	}

	resp = &consolev1.IntegrationStatusResp{
		Id:                   uint64(integrationResp.ID),
		OrgId:                req.OrgId,
		ProjectId:            req.ProjectId,
		ClusterId:            req.ClusterId,
		Type:                 req.Type,
		Status:               s,
		Payloads:             req.Payloads,
		DatadogSite:          req.DatadogSite,
		DatadogSiteUrl:       ddUrl,
		DatadogDashboardUrl:  ddDashboardUrl,
		DatadogMaskedApiKey:  ddMaskedKey,
		NewrelicSite:         req.NewrelicSite,
		NewrelicSiteUrl:      nrUrl,
		NewrelicDashboardUrl: nrDashboardUrl,
		NewrelicMaskedApiKey: nrMaskedKey,
	}

	return resp, nil
}

func (i *integrationServiceServer) DeleteIntegration(ctx context.Context, req *consolev1.DeleteIntegrationRequest) (resp *consolev1.IntegrationStatusResp, err error) {
	defer func() {
		_ = di.DiCall(func(db *gorm.DB) error {
			integration := entity.Integration{}
			_ = db.Transaction(func(tx *gorm.DB) error {
				if req.Type == consolev1.IntegrationType_INTEGRATION_TYPE_DATADOG {
					queryErr := tx.Where("cluster_id = ? AND provider_type = ?", req.ClusterId, entity.IntegrationTypeDatadog).First(&integration).Error
					if queryErr != nil {
						user := account.GetUserFromContext(ctx)
						log.Error(ctx, "DeleteIntegration query datadog integration info fail",
							zap.Uint64("tenantID", user.TenantID),
							zap.Uint64("userID", user.ID),
							zap.Error(queryErr))
					}
				}

				if req.Type == consolev1.IntegrationType_INTEGRATION_TYPE_NEWRELIC {
					queryErr := tx.Where("cluster_id = ? AND provider_type = ?", req.ClusterId, entity.IntegrationTypeNewrelic).First(&integration).Error
					if queryErr != nil {
						user := account.GetUserFromContext(ctx)
						log.Error(ctx, "DeleteIntegration query newrelic integration info fail",
							zap.Uint64("tenantID", user.TenantID),
							zap.Uint64("userID", user.ID),
							zap.Error(queryErr))
					}
				}

				return nil
			})
			details := auditlog.Details{
				auditlog.KeyDescription: "a metrics integration pipeline is deleted.",
				auditlog.KeyError:       err,
			}
			eventType := auditlog.EventDeleteMetricsIntegration
			switch req.Type {
			case consolev1.IntegrationType_INTEGRATION_TYPE_DATADOG:
				details[auditlog.KeyApiKey] = integration.MaskedDatadogApiKey
				details[auditlog.KeySite] = string(integration.DatadogSite)
				details[auditlog.KeyIntegrationType] = "datadog"
			case consolev1.IntegrationType_INTEGRATION_TYPE_NEWRELIC:
				details[auditlog.KeyApiKey] = integration.MaskedNewrelicApiKey
				details[auditlog.KeySite] = string(integration.NewrelicSite)
				details[auditlog.KeyIntegrationType] = "newrelic"
			}

			eventSubmitErr := auditlog.NewEvent(ctx, eventType).SetResult(err == nil).SetDetails(details.Build()).SetOrgID(req.OrgId).Submit()
			if eventSubmitErr != nil {
				user := account.GetUserFromContext(ctx)
				log.Error(ctx, "DeleteIntegration submit event fail",
					zap.Uint64("tenantID", user.TenantID),
					zap.Uint64("userID", user.ID),
					zap.Error(eventSubmitErr))
				return eventSubmitErr
			}
			return nil
		})
	}()

	var s consolev1.IntegrationStatus
	// TODO: it will support more integration management in the future.
	s, err = integration.DisableIntegrationofCluster(ctx, entity.ID(req.ClusterId), cvt.TypeConsoleAPIToDB(req.Type))
	if err != nil {
		err = errcode.NewInternalErr(errors.Annotate(err, "deleting integration failed"))
		return nil, err
	}

	resp = &consolev1.IntegrationStatusResp{
		OrgId:     req.OrgId,
		ProjectId: req.ProjectId,
		ClusterId: req.ClusterId,
		Type:      req.Type,
		Status:    s,
	}
	return resp, nil
}

func (i *integrationServiceServer) ListIntegrations(ctx context.Context, req *consolev1.ListIntegrationsRequest) (resp *consolev1.ListIntegrationsResp, err error) {
	statuses, err := integration.ListIntegrationsForConsoleAPI(ctx, req.ClusterId, cvt.TypeConsoleAPIToDB(req.Type))
	if err != nil {
		err = errcode.NewInternalErr(errors.Annotate(err, "list integrations failed"))
		return nil, err
	}

	resp = &consolev1.ListIntegrationsResp{
		Items: statuses,
		Total: int32(len(statuses)),
	}

	return resp, nil
}
