package maintenance

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"google.golang.org/protobuf/types/known/timestamppb"

	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/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (s serverImpl) ListMaintenanceWindows(
	ctx context.Context,
	req *openapidedicatedv1beta1.ListMaintenanceWindowsRequest,
) (*openapidedicatedv1beta1.ListMaintenanceWindowsResponse, error) {
	ctx, span := tracer.Start(ctx, "ListMaintenanceWindows")
	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")
		}
	}

	windowResps, err := s.maintenanceSvc.ListMaintenanceWindows(
		ctx,
		&tidbmgmtservicev1.ListMaintenanceWindowsRequest{ProjectId: projectId},
	)
	if err != nil {
		return nil, err
	}

	if len(windowResps.GetMaintenanceWindows()) == 0 {
		return &openapidedicatedv1beta1.ListMaintenanceWindowsResponse{
			MaintenanceWindows: []*openapidedicatedv1beta1.MaintenanceWindow{},
			TotalSize:          0,
		}, nil
	}

	// only one maintenance window for each project
	target := windowResps.GetMaintenanceWindows()[0]
	return &openapidedicatedv1beta1.ListMaintenanceWindowsResponse{
		MaintenanceWindows: []*openapidedicatedv1beta1.MaintenanceWindow{
			cvtMaintenanceWindow(target, target.GetNextMaintenanceDate(), nil),
		},
		TotalSize: 1,
	}, nil
}

func (s serverImpl) GetMaintenanceWindow(
	ctx context.Context,
	req *openapidedicatedv1beta1.GetMaintenanceWindowRequest,
) (*openapidedicatedv1beta1.MaintenanceWindow, error) {
	ctx, span := tracer.Start(ctx, "GetMaintenanceWindow")
	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")
	}

	windowResp, err := s.maintenanceSvc.GetMaintenanceWindow(ctx, &tidbmgmtservicev1.GetMaintenanceWindowRequest{
		MaintenanceWindowId: req.GetMaintenanceWindowId(),
	})
	if err != nil {
		return nil, err
	}

	return cvtMaintenanceWindow(windowResp, windowResp.NextMaintenanceDate, nil), nil
}

func (s serverImpl) UpdateMaintenanceWindow(
	ctx context.Context,
	req *openapidedicatedv1beta1.UpdateMaintenanceWindowRequest,
) (*openapidedicatedv1beta1.MaintenanceWindow, error) {
	ctx, span := tracer.Start(ctx, "UpdateMaintenanceWindow")
	defer span.End()

	var (
		err     error
		reqMeta *metadata.RequestMeta
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeDedicated)
		details[auditlog.KeyDayOfWeek] = req.GetMaintenanceWindow().GetWeekDay()
		details[auditlog.KeyHourOfDay] = req.GetMaintenanceWindow().GetDayHour()
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventUpdateMaintenanceWindow, 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")
	}

	// get project id from request
	// maintenancewindowId is in format "project-<project_id>"
	var projectId uint64
	projectId, err = strconv.ParseUint(
		strings.TrimPrefix(req.GetMaintenanceWindow().GetMaintenanceWindowId(), "project-"),
		10,
		64,
	)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid maintenance window id")
	}

	updateResp, err := s.maintenanceSvc.UpdateMaintenanceWindow(ctx, &tidbmgmtservicev1.UpdateMaintenanceWindowRequest{
		MaintenanceWindow: &tidbmgmtservicev1.MaintenanceWindow{
			MaintenanceWindowId: req.GetMaintenanceWindow().GetMaintenanceWindowId(),
			ProjectId:           projectId,
			WeekDay:             req.GetMaintenanceWindow().GetWeekDay(),
			DayHour:             req.GetMaintenanceWindow().GetDayHour(),
			HourMinute:          req.GetMaintenanceWindow().GetHourMinute(),
		},
	})
	if err != nil {
		return nil, err
	}
	tasks, err := s.maintenanceSvc.ListMaintenanceTasks(
		ctx,
		&tidbmgmtservicev1.ListMaintenanceTasksRequest{ProjectId: projectId},
	)
	if err != nil {
		return nil, err
	}

	return cvtMaintenanceWindow(updateResp, updateResp.GetNextMaintenanceDate(), tasks.GetMaintenanceTasks()), nil
}

func cvtMaintenanceWindow(
	mw *tidbmgmtservicev1.MaintenanceWindow,
	nextMaintenanceDate *timestamppb.Timestamp,
	tasks []*tidbmgmtservicev1.MaintenanceTask,
) *openapidedicatedv1beta1.MaintenanceWindow {
	maintenancewindowId := fmt.Sprintf("project-%d", mw.ProjectId)

	return &openapidedicatedv1beta1.MaintenanceWindow{
		Name:                fmt.Sprintf("maintenanceWindows/%s", maintenancewindowId),
		MaintenanceWindowId: maintenancewindowId,
		ProjectId:           strconv.FormatUint(mw.ProjectId, 10),
		WeekDay:             mw.GetWeekDay(),
		DayHour:             mw.GetDayHour(),
		HourMinute:          mw.GetHourMinute(),
		NextMaintenanceDate: nextMaintenanceDate,
		UnchangedMaintenanceTasks: lo.Map(
			tasks,
			func(task *tidbmgmtservicev1.MaintenanceTask, _ int) *openapidedicatedv1beta1.MaintenanceTask {
				return &openapidedicatedv1beta1.MaintenanceTask{
					Name:               fmt.Sprintf("maintenanceTasks/%d", task.GetMaintenanceTaskId()),
					MaintenanceTaskId:  strconv.FormatUint(task.GetMaintenanceTaskId(), 10),
					ProjectId:          strconv.FormatUint(task.GetProjectId(), 10),
					ScheduledApplyTime: task.GetScheduledApplyTime(),
				}
			},
		),
	}
}
