package maintenancewindow

import (
	"context"
	"strconv"
	"strings"
	"time"

	"google.golang.org/protobuf/types/known/timestamppb"

	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/utilities/bizerr"
)

func (svc *service) GetMaintenanceWindow(
	ctx context.Context,
	req *tidbmgmtservicev1.GetMaintenanceWindowRequest,
) (*tidbmgmtservicev1.MaintenanceWindow, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "GetMaintenanceWindowSvc")
	defer span.End()

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

	utcOffset, err := svc.accountCli.GetTimeZoneByProjectID(ctx, projectId)
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "get time zone by project id")
	}

	var maintenanceWindowWithTimeZone *entity.MaintenanceWindowWithTimeZone
	maintenanceWindow, berr := svc.mwRepo.LoadByProjectID(ctx, projectId)
	if berr != nil {
		if berr.RawErrCode() != bizerr.ErrCodeRecordNotFound {
			return nil, berr
		}

		// if not found, return default maintenance window
		maintenanceWindowWithTimeZone = buildDefaultMaintenanceWindowWithTimeZone(projectId, utcOffset)
		maintenanceWindow = buildMaintenanceWindow(maintenanceWindowWithTimeZone)
	} else {
		maintenanceWindowWithTimeZone = buildMaintenanceWindowWithTimeZone(maintenanceWindow, utcOffset)
	}

	now := time.Now()
	nextMaintenanceDateSec := getCurrentApplyTime(now, maintenanceWindow).Unix()
	maintenanceWindowWithTimeZone.NextMaintenanceDate = timestamppb.New(time.Unix(nextMaintenanceDateSec, 0))

	return &maintenanceWindowWithTimeZone.MaintenanceWindow.MaintenanceWindow, nil
}

func buildDefaultMaintenanceWindowWithTimeZone(
	projectID uint64,
	utcOffset int32,
) *entity.MaintenanceWindowWithTimeZone {
	return &entity.MaintenanceWindowWithTimeZone{
		MaintenanceWindow: entity.MaintenanceWindow{
			MaintenanceWindow: tidbmgmtservicev1.MaintenanceWindow{
				ProjectId:  projectID,
				WeekDay:    3,
				DayHour:    3,
				HourMinute: 0,
			},
		},

		UTCOffset: utcOffset,
	}
}

func buildMaintenanceWindow(
	maintenanceWindowWithTimeZone *entity.MaintenanceWindowWithTimeZone,
) *entity.MaintenanceWindow {
	out := &entity.MaintenanceWindow{
		ID: maintenanceWindowWithTimeZone.ID,
		MaintenanceWindow: tidbmgmtservicev1.MaintenanceWindow{
			ProjectId: maintenanceWindowWithTimeZone.GetProjectId(),
		},
	}

	loc := time.FixedZone("", int(maintenanceWindowWithTimeZone.UTCOffset)*60*60)

	now := time.Now().UTC()
	weekday := time.Weekday(maintenanceWindowWithTimeZone.WeekDay)
	localTime := time.Date(
		now.Year(),
		now.Month(),
		now.Day()+(int(weekday)-int(now.Weekday())+7)%7, // Calculate the nearest DayOfWeek
		int(maintenanceWindowWithTimeZone.DayHour),
		int(maintenanceWindowWithTimeZone.HourMinute),
		0,
		0,
		loc,
	)

	t := localTime.UTC()

	out.WeekDay = int32(t.Weekday())
	out.DayHour = int32(t.Hour())
	out.HourMinute = int32(t.Minute())
	return out
}

func buildMaintenanceWindowWithTimeZone(
	mw *entity.MaintenanceWindow,
	utcOffset int32,
) *entity.MaintenanceWindowWithTimeZone {
	out := &entity.MaintenanceWindowWithTimeZone{
		MaintenanceWindow: entity.MaintenanceWindow{
			ID: mw.ID,
			MaintenanceWindow: tidbmgmtservicev1.MaintenanceWindow{
				ProjectId:  mw.GetProjectId(),
				WeekDay:    mw.GetWeekDay(),
				DayHour:    mw.GetDayHour(),
				HourMinute: mw.GetHourMinute(),
			},
		},
		UTCOffset: utcOffset,
	}

	loc := time.FixedZone("", int(utcOffset)*60*60)

	now := time.Now().UTC()
	weekday := time.Weekday(mw.WeekDay)
	utcTime := time.Date(
		now.Year(),
		now.Month(),
		now.Day()+(int(weekday)-int(now.Weekday())+7)%7, // Calculate the nearest DayOfWeek
		int(mw.DayHour),
		int(mw.HourMinute),
		0,
		0,
		time.UTC,
	)

	locTime := utcTime.In(loc)

	out.WeekDay = int32(locTime.Weekday())
	out.DayHour = int32(locTime.Hour())
	out.HourMinute = int32(locTime.Minute())
	return out
}
