package maintenance

import (
	"context"
	"fmt"
	"strconv"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/sdk/auditlog"

	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (s serverImpl) ListMaintenanceTasks(
	ctx context.Context,
	req *openapidedicatedv1beta1.ListMaintenanceTasksRequest,
) (*openapidedicatedv1beta1.ListMaintenanceTasksResponse, error) {
	ctx, span := tracer.Start(ctx, "ListMaintenanceTasks")
	defer span.End()

	// Extract request meta.
	reqMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	projectId := reqMeta.DefaultProjectID
	if req.GetProjectId() != "" {
		projectId, err = strconv.ParseUint(req.GetProjectId(), 10, 64)
		if err != nil {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid project id")
		}
	}

	tasks, err := s.maintenanceSvc.ListMaintenanceTasks(
		ctx,
		&tidbmgmtservicev1.ListMaintenanceTasksRequest{ProjectId: projectId},
	)
	if err != nil {
		return nil, err
	}
	openapiTasks := lo.Map(
		tasks.GetMaintenanceTasks(),
		func(task *tidbmgmtservicev1.MaintenanceTask, _ int) *openapidedicatedv1beta1.MaintenanceTask {
			return cvtMaintenanceTask(task)
		},
	)
	pagedRes, nextToken, err := common.LocalPagination(
		openapiTasks,
		req,
		common.ListDefaultPageSize,
		common.ListMaxPageSize,
	)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeUnknown, "failed to paginate maintenance tasks")
	}

	return &openapidedicatedv1beta1.ListMaintenanceTasksResponse{
		MaintenanceTasks: pagedRes,
		TotalSize:        int32(len(tasks.GetMaintenanceTasks())),
		NextPageToken:    nextToken,
	}, nil
}

func (s serverImpl) GetMaintenanceTask(
	ctx context.Context,
	req *openapidedicatedv1beta1.GetMaintenanceTaskRequest,
) (*openapidedicatedv1beta1.MaintenanceTask, error) {
	ctx, span := tracer.Start(ctx, "GetMaintenanceTask")
	defer span.End()

	// Extract request meta.
	_, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	taskId, err := strconv.ParseUint(req.GetMaintenanceTaskId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(
			err,
			bizerr.ErrCodeParams,
			"invalid maintenance task id %s",
			req.GetMaintenanceTaskId(),
		)
	}

	taskResp, err := s.maintenanceSvc.GetMaintenanceTask(
		ctx,
		&tidbmgmtservicev1.GetMaintenanceTaskRequest{MaintenanceTaskId: taskId},
	)
	if err != nil {
		return nil, err
	}

	return cvtMaintenanceTask(taskResp), nil
}

func (s serverImpl) UpdateMaintenanceTask(
	ctx context.Context,
	req *openapidedicatedv1beta1.UpdateMaintenanceTaskRequest,
) (*openapidedicatedv1beta1.MaintenanceTask, error) {
	ctx, span := tracer.Start(ctx, "UpdateMaintenanceTask")
	defer span.End()
	var (
		err     error
		reqMeta *metadata.RequestMeta
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeDedicated)
		if req.GetMaintenanceTask() != nil {
			details[auditlog.KeyMaintenanceTaskId] = req.GetMaintenanceTask().GetMaintenanceTaskId()
			details["scheduled_apply_time"] = req.GetMaintenanceTask().GetScheduledApplyTime()
		}
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventDeferMaintenanceTask, reqMeta, detailsFunc, err)
	}()

	// Extract request meta.
	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	if req.GetMaintenanceTask() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "missing maintenance task")
	}
	reqTask := req.GetMaintenanceTask()

	taskId, err := strconv.ParseUint(reqTask.GetMaintenanceTaskId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid maintenance task id")
	}

	taskResp, err := s.maintenanceSvc.UpdateMaintenanceTask(ctx, &tidbmgmtservicev1.UpdateMaintenanceTaskRequest{
		MaintenanceTask: &tidbmgmtservicev1.MaintenanceTask{
			MaintenanceTaskId:  taskId,
			ScheduledApplyTime: reqTask.GetScheduledApplyTime(),
		},
	})
	if err != nil {
		return nil, err
	}

	return cvtMaintenanceTask(taskResp), nil
}

func cvtMaintenanceTask(task *tidbmgmtservicev1.MaintenanceTask) *openapidedicatedv1beta1.MaintenanceTask {
	return &openapidedicatedv1beta1.MaintenanceTask{
		Name:               fmt.Sprintf("maintenanceTasks/%d", task.GetMaintenanceTaskId()),
		MaintenanceTaskId:  strconv.FormatUint(task.GetMaintenanceTaskId(), 10),
		ProjectId:          strconv.FormatUint(task.GetProjectId(), 10),
		Description:        task.Description,
		State:              cvtTaskStatus(task.GetState()),
		CreateTime:         task.GetCreateTime(),
		ScheduledApplyTime: task.GetScheduledApplyTime(),
		Deadline:           task.GetDeadline(),
	}
}
func cvtTaskStatus(s tidbmgmtservicev1.MaintenanceTask_State) openapidedicatedv1beta1.MaintenanceTask_State {
	switch s {
	case tidbmgmtservicev1.MaintenanceTask_DONE:
		return openapidedicatedv1beta1.MaintenanceTask_DONE
	case tidbmgmtservicev1.MaintenanceTask_PENDING:
		return openapidedicatedv1beta1.MaintenanceTask_PENDING
	case tidbmgmtservicev1.MaintenanceTask_RUNNING:
		return openapidedicatedv1beta1.MaintenanceTask_RUNNING
	default:
		return openapidedicatedv1beta1.MaintenanceTask_STATE_UNSPECIFIED
	}
}
