package openapi

import (
	context "context"
	"fmt"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/errcode"
	opapiv1beta1 "github.com/tidbcloud/metrics-integration/global/api/integration/openapi/v1beta1"
	"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"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/protobuf/types/known/timestamppb"
	"gorm.io/gorm"
)

type integrationServiceServer struct {
	opapiv1beta1.UnimplementedIntegrationServiceServer
}

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

func (i *integrationServiceServer) CreateIntegration(ctx context.Context, req *opapiv1beta1.CreateIntegrationRequest) (resp *opapiv1beta1.IntegrationStatusResp, err error) {
	user := account.GetUserFromContext(ctx)
	switch req.Type {
	case opapiv1beta1.IntegrationType_DATADOG:
		// Validate Datadog api key first.
		valid, err := integration.ValidateDatadogAPIKey(ctx, req.GetDatadog().ApiKey, cvt.DatadogSiteOpAPIToDB(req.GetDatadog().Site))
		if err != nil {
			log.Error(ctx, "ValidateDatadogAPIKey failed", zap.Error(err))
			return nil, errcode.NewInternalErr(err)
		}
		if !valid {
			return nil, errcode.NewInvalidInputErr(errors.New("invalid datadog api key"))
		}

		// call controller to create datadog integration
		integrationResp, err := integration.CreateDatadogIntegration(ctx, entity.ID(req.ClusterId), cvt.DatadogSiteOpAPIToDB(req.GetDatadog().Site), req.GetDatadog().ApiKey, user.Email)
		if err != nil {
			return nil, err
		}
		s := integrationResp.Status
		masked := integrationResp.MaskedDatadogApiKey
		resp = &opapiv1beta1.IntegrationStatusResp{
			Id:   uint64(integrationResp.ID),
			Type: opapiv1beta1.IntegrationType_DATADOG,
			Spec: &opapiv1beta1.IntegrationStatusResp_Datadog{Datadog: &opapiv1beta1.DatadogIntegrationStatusResp{
				State:        cvt.StatusDBToOpenAPIState(s),
				StatusReason: "",
				Site:         req.GetDatadog().Site,
				MaskedApiKey: masked,
				SiteUrl:      cvt.GetDatadogSiteUrl(cvt.DatadogSiteOpAPIToDB(req.GetDatadog().Site)),
				DashboardUrl: cvt.GetDatadogDashboardUrl(cvt.DatadogSiteOpAPIToDB(req.GetDatadog().Site)),
			}},
			CreateTime: timestamppb.New(integrationResp.CreatedAt),
			UpdateTime: func() *timestamppb.Timestamp {
				if integrationResp.UpdatedAt != nil {
					return timestamppb.New(*integrationResp.UpdatedAt)
				}
				return nil
			}(),
			Creator: user.Email,
		}
		return resp, nil
	case opapiv1beta1.IntegrationType_NEWRELIC:
		// Validate Newrelic api key first.
		valid, err := integration.ValidateNewrelicAPIKey(ctx, req.GetNewrelic().ApiKey, cvt.NewrelicSiteOpAPIToDB(req.GetNewrelic().Site))
		if !valid || err != nil {
			if err != nil {
				log.Error(ctx, "ValidateNewrelicAPIKey failed", zap.Error(err))
			}
			err = errcode.NewInvalidInputErr(errors.New("invalid newrelic api key"))
			return nil, err
		}

		// call controller create with forced id
		integrationResp, err := integration.CreateNewrelicIntegration(ctx, entity.ID(req.ClusterId), cvt.NewrelicSiteOpAPIToDB(req.GetNewrelic().Site), req.GetNewrelic().ApiKey, user.Email)
		if err != nil {
			return nil, err
		}
		s := integrationResp.Status
		masked := integrationResp.MaskedNewrelicApiKey
		resp = &opapiv1beta1.IntegrationStatusResp{
			Id:   uint64(integrationResp.ID),
			Type: opapiv1beta1.IntegrationType_NEWRELIC,
			Spec: &opapiv1beta1.IntegrationStatusResp_Newrelic{Newrelic: &opapiv1beta1.NewrelicIntegrationStatusResp{
				State:        cvt.StatusDBToOpenAPIState(s),
				StatusReason: "",
				Site:         req.GetNewrelic().Site,
				MaskedApiKey: masked,
				SiteUrl:      cvt.GetNewrelicSiteEndpoint(cvt.NewrelicSiteOpAPIToDB(req.GetNewrelic().Site)),
				DashboardUrl: cvt.GetNewrelicSiteDashboard(cvt.NewrelicSiteOpAPIToDB(req.GetNewrelic().Site)),
			}},
			CreateTime: timestamppb.Now(),
			UpdateTime: timestamppb.Now(),
			Creator:    user.Email,
		}
		return resp, nil
	case opapiv1beta1.IntegrationType_PROMETHEUS:
		// create metrics key with forced id
		mk, queryEndpoint, err := integration.CreateMetricsKey(ctx, entity.ID(req.ClusterId), user.Email)
		if err != nil {
			return nil, err
		}
		resp = &opapiv1beta1.IntegrationStatusResp{
			Id:   uint64(mk.ID),
			Type: opapiv1beta1.IntegrationType_PROMETHEUS,
			Spec: &opapiv1beta1.IntegrationStatusResp_Prometheus{Prometheus: &opapiv1beta1.CreateMetricsKeyResp{
				Key:           mk.KeyHash,
				QueryEndpoint: queryEndpoint,
			}},
			CreateTime: timestamppb.Now(),
			UpdateTime: timestamppb.Now(),
			Creator:    user.Email,
		}
		return resp, nil
	default:
		return nil, fmt.Errorf("unsupported integration type: %v", req.Type)
	}
}

func (i *integrationServiceServer) ListIntegrations(ctx context.Context, req *opapiv1beta1.ListIntegrationsRequest) (resp *opapiv1beta1.ListIntegrationsResp, err error) {
	resp, err = integration.ListIntegrationsByClusterForOpAPI(ctx, req.ClusterId)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (i *integrationServiceServer) DeleteIntegration(ctx context.Context, req *opapiv1beta1.DeleteIntegrationRequest) (resp *emptypb.Empty, err error) {
	// find general integration
	var gen entity.GeneralIntegration
	if err := di.DiCall(func(db *gorm.DB) error {
		return db.Where("id = ?", req.Id).First(&gen).Error
	}); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// nothing to do
			return &emptypb.Empty{}, nil
		}
		err = errcode.NewInternalErr(errors.Annotate(err, "query general integration failed"))
		return nil, err
	}

	// delete detailed record first
	switch gen.ProviderType {
	case entity.IntegrationTypeDatadog, entity.IntegrationTypeNewrelic:
		if derr := integration.DeleteDatadogNewrelicIntegration(ctx, entity.ID(req.Id)); derr != nil {
			log.Error(ctx, "DeleteIntegration delete detail failed", derr)
			err = errcode.NewInternalErr(errors.Annotate(derr, "delete detail failed"))
			return nil, err
		}
	case entity.IntegrationTypePrometheus:
		if derr := integration.DeleteMetricsKey(ctx, req.Id); derr != nil {
			log.Error(ctx, "DeleteIntegration delete metrics key failed", derr)
			err = errcode.NewInternalErr(errors.Annotate(derr, "delete detail failed"))
			return nil, err
		}
	}

	return &emptypb.Empty{}, nil
}
