package dedicatedtopremiumprecheck

import (
	"context"
	"errors"
	"strings"

	"github.com/samber/lo"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/timestamppb"
	"gorm.io/gorm"

	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/dedicated/cluster"
	prechecksvc "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/dedicatedtopremiumprecheck"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/precheckcatalog"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func NewServer(
	service prechecksvc.Service,
	dedicatedClusterSvc cluster.Service,
) tidbmgmtservicev1.DedicatedToPremiumPrecheckServiceServer {
	return &server{service: service, dedicatedClusterSvc: dedicatedClusterSvc}
}

type server struct {
	service             prechecksvc.Service
	dedicatedClusterSvc cluster.Service
}

func (s *server) CreateDedicatedToPremiumPrecheck(
	ctx context.Context,
	req *tidbmgmtservicev1.CreateDedicatedToPremiumPrecheckRequest,
) (*tidbmgmtservicev1.DedicatedToPremiumPrecheck, error) {
	if req == nil || req.GetSourceClusterId() == 0 {
		return nil, status.Error(codes.InvalidArgument, "source_cluster_id is required")
	}
	if strings.TrimSpace(req.GetApplicant()) == "" {
		return nil, status.Error(codes.InvalidArgument, "applicant is required")
	}
	var targetProjectID *uint64
	if req.GetTargetPremiumProjectId() != 0 {
		targetProjectID = &req.TargetPremiumProjectId
	}
	sourceCluster, err := s.dedicatedClusterSvc.GetCluster(
		ctx,
		req.GetSourceClusterId(),
		entity.ClusterViewClusterViewLocal,
		false,
		false,
	)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerror.NewNotFound(err, "dedicated cluster not found")
		}
		return nil, bizerror.NewInternal(err, "failed to get dedicated cluster")
	}

	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		OrgID:             sourceCluster.OrganizationId,
		DefaultProjectID:  sourceCluster.ProjectId,
		ValidProjectList:  []uint64{sourceCluster.ProjectId},
		UserID:            "0",
		IsCheckPermission: lo.ToPtr(true),
	})

	precheck, err := s.service.Create(
		ctx,
		req.GetSourceClusterId(),
		req.GetMigrationAccountEmail(),
		targetProjectID,
		req.GetApplicant(),
	)
	if err != nil {
		return nil, err
	}
	return toProto(precheck), nil
}

func (s *server) GetDedicatedToPremiumPrecheck(
	ctx context.Context,
	req *tidbmgmtservicev1.GetDedicatedToPremiumPrecheckRequest,
) (*tidbmgmtservicev1.DedicatedToPremiumPrecheck, error) {
	if req == nil || req.GetId() == 0 {
		return nil, status.Error(codes.InvalidArgument, "precheck id is required")
	}
	precheck, err := s.service.Get(ctx, req.GetId())
	if err != nil {
		return nil, err
	}
	return toProto(precheck), nil
}

func (s *server) ListDedicatedToPremiumPrechecks(
	ctx context.Context,
	req *tidbmgmtservicev1.ListDedicatedToPremiumPrechecksRequest,
) (*tidbmgmtservicev1.ListDedicatedToPremiumPrechecksResponse, error) {
	if req == nil {
		req = &tidbmgmtservicev1.ListDedicatedToPremiumPrechecksRequest{}
	}
	prechecks, total, err := s.service.List(
		ctx,
		req.GetSourceClusterId(),
		req.GetPageSize(),
		req.GetPage(),
	)
	if err != nil {
		return nil, err
	}
	response := &tidbmgmtservicev1.ListDedicatedToPremiumPrechecksResponse{
		Prechecks: make([]*tidbmgmtservicev1.DedicatedToPremiumPrecheck, 0, len(prechecks)),
		Total:     total,
	}
	for _, precheck := range prechecks {
		response.Prechecks = append(response.Prechecks, toProto(precheck))
	}
	return response, nil
}

func toProto(precheck *entity.DedicatedToPremiumPrecheck) *tidbmgmtservicev1.DedicatedToPremiumPrecheck {
	response := &tidbmgmtservicev1.DedicatedToPremiumPrecheck{
		Id: precheck.ID, SourceClusterId: precheck.SourceClusterID,
		MigrationAccountEmail: precheck.MigrationAccountEmail,
		Applicant:             precheck.Applicant,
		State:                 precheckState(precheck.State), FailureMessage: precheck.FailureMessage,
		CreateTime: timestamppb.New(precheck.CreatedAt), UpdateTime: timestamppb.New(precheck.UpdatedAt),
	}
	if precheck.TargetPremiumProjectID != nil {
		response.TargetPremiumProjectId = *precheck.TargetPremiumProjectID
	}
	if precheck.StartedAt != nil {
		response.StartTime = timestamppb.New(*precheck.StartedAt)
	}
	if precheck.FinishedAt != nil {
		response.FinishTime = timestamppb.New(*precheck.FinishedAt)
	}
	response.Reports = []*tidbmgmtservicev1.PrecheckReport{
		buildReport(precheck.Items, entity.PrecheckCategoryFeatureGap, len(precheckcatalog.FeatureGapChecks)),
		buildReport(precheck.Items, entity.PrecheckCategoryMigrationReadiness, len(precheckcatalog.MigrationReadinessChecks)),
	}
	return response
}

func buildReport(
	items []*entity.DedicatedToPremiumPrecheckItem,
	category entity.PrecheckCategory,
	expected int,
) *tidbmgmtservicev1.PrecheckReport {
	report := &tidbmgmtservicev1.PrecheckReport{
		Category: reportCategory(category), Summary: &tidbmgmtservicev1.PrecheckSummary{}, Complete: true,
	}
	var executionErrors []string
	for _, item := range items {
		if item.Category != category {
			continue
		}
		report.Items = append(report.Items, &tidbmgmtservicev1.PrecheckItem{
			CheckKey: item.CheckKey, Title: item.Title, Status: itemStatus(item.Status),
			Why: item.Why, Evidence: item.Evidence, Action: item.Action,
		})
		report.Summary.TotalCount++
		switch item.Status {
		case entity.PrecheckItemStatusRiskFound:
			report.Summary.RiskFoundCount++
		case entity.PrecheckItemStatusNoRiskFound:
			report.Summary.NoRiskFoundCount++
		case entity.PrecheckItemStatusNeedsInput:
			report.Summary.NeedsInputCount++
		}
		if item.ExecutionState != entity.PrecheckItemExecutionStateCompleted {
			report.Complete = false
		}
		if item.ExecutionError != "" {
			executionErrors = append(executionErrors, item.ExecutionError)
		}
	}
	report.Complete = report.Complete && len(report.Items) == expected
	if len(executionErrors) > 0 {
		report.ExecutionError = strings.Join(executionErrors, "; ")
	}
	return report
}

func precheckState(value entity.DedicatedToPremiumPrecheckState) tidbmgmtservicev1.DedicatedToPremiumPrecheckState {
	switch value {
	case entity.DedicatedToPremiumPrecheckStatePending:
		return tidbmgmtservicev1.DedicatedToPremiumPrecheckState_PENDING
	case entity.DedicatedToPremiumPrecheckStateRunning:
		return tidbmgmtservicev1.DedicatedToPremiumPrecheckState_RUNNING
	case entity.DedicatedToPremiumPrecheckStateSucceeded:
		return tidbmgmtservicev1.DedicatedToPremiumPrecheckState_SUCCEEDED
	case entity.DedicatedToPremiumPrecheckStateFailed:
		return tidbmgmtservicev1.DedicatedToPremiumPrecheckState_FAILED
	default:
		return tidbmgmtservicev1.DedicatedToPremiumPrecheckState_DEDICATED_TO_PREMIUM_PRECHECK_STATE_UNSPECIFIED
	}
}

func reportCategory(value entity.PrecheckCategory) tidbmgmtservicev1.PrecheckCategory {
	if value == entity.PrecheckCategoryFeatureGap {
		return tidbmgmtservicev1.PrecheckCategory_FEATURE_GAP
	}
	return tidbmgmtservicev1.PrecheckCategory_MIGRATION_READINESS
}

func itemStatus(value entity.PrecheckItemStatus) tidbmgmtservicev1.PrecheckItemStatus {
	switch value {
	case entity.PrecheckItemStatusRiskFound:
		return tidbmgmtservicev1.PrecheckItemStatus_RISK_FOUND
	case entity.PrecheckItemStatusNoRiskFound:
		return tidbmgmtservicev1.PrecheckItemStatus_NO_RISK_FOUND
	case entity.PrecheckItemStatusNeedsInput:
		return tidbmgmtservicev1.PrecheckItemStatus_NEEDS_INPUT
	default:
		return tidbmgmtservicev1.PrecheckItemStatus_PRECHECK_ITEM_STATUS_UNSPECIFIED
	}
}
