package core

import (
	"context"
	"time"

	"github.com/samber/lo"
	coreapi "github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"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/service/maintenancewindow"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type Controller struct {
	coreapi.UnimplementedCoreServiceServer

	mwSvc maintenancewindow.Service
}

func NewCoreServer(mwSvc maintenancewindow.Service) coreapi.CoreServiceServer {
	return &Controller{mwSvc: mwSvc}
}

func (c *Controller) GetMaintenanceWindowByProjectID(
	ctx context.Context,
	req *coreapi.GetMaintenanceWindowByProjectIDReq,
) (*coreapi.GetMaintenanceWindowByProjectIDResp, error) {
	resp, err := c.mwSvc.ListMaintenanceWindows(
		ctx,
		&tidbmgmtservicev1.ListMaintenanceWindowsRequest{ProjectId: req.GetProjectId()},
	)
	if err != nil {
		return &coreapi.GetMaintenanceWindowByProjectIDResp{BaseResp: &base.BaseResp{}}, err
	}
	if resp == nil || resp.GetMaintenanceWindows() == nil || len(resp.GetMaintenanceWindows()) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "no maintenance window found")
	}
	target := resp.GetMaintenanceWindows()[0]
	return &coreapi.GetMaintenanceWindowByProjectIDResp{
		BaseResp:            &base.BaseResp{},
		MaintenanceWindow:   mgmtServiceToCoreServiceMaintenanceWindow(target),
		NextMaintenanceDate: target.GetNextMaintenanceDate().AsTime().Unix(),
	}, nil
}

func (c *Controller) UpdateMaintenanceWindow(
	ctx context.Context,
	req *coreapi.UpdateMaintenanceWindowReq,
) (*coreapi.UpdateMaintenanceWindowResp, error) {
	if req.GetMaintenanceWindow() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "missing maintenance window")
	}
	maintenanceWindow := req.GetMaintenanceWindow()
	_, err := c.mwSvc.UpdateMaintenanceWindow(ctx, &tidbmgmtservicev1.UpdateMaintenanceWindowRequest{
		MaintenanceWindow: &tidbmgmtservicev1.MaintenanceWindow{
			ProjectId:  maintenanceWindow.GetProjectId(),
			WeekDay:    maintenanceWindow.GetDayOfWeek(),
			DayHour:    maintenanceWindow.GetHourOfDay(),
			HourMinute: maintenanceWindow.GetMinuteOfHour(),
		},
	})
	if err != nil {
		return &coreapi.UpdateMaintenanceWindowResp{BaseResp: &base.BaseResp{}}, err
	}
	tasks, err := c.mwSvc.ListMaintenanceTasks(
		ctx,
		&tidbmgmtservicev1.ListMaintenanceTasksRequest{ProjectId: maintenanceWindow.GetProjectId()},
	)
	if err != nil {
		return &coreapi.UpdateMaintenanceWindowResp{BaseResp: &base.BaseResp{}}, err
	}

	if tasks == nil || tasks.GetMaintenanceTasks() == nil || len(tasks.GetMaintenanceTasks()) == 0 {
		return &coreapi.UpdateMaintenanceWindowResp{BaseResp: &base.BaseResp{}}, nil
	}
	unchangedTasks := lo.Map(
		tasks.GetMaintenanceTasks(),
		func(task *tidbmgmtservicev1.MaintenanceTask, _ int) *coreapi.UnchangedTask {
			return &coreapi.UnchangedTask{
				Id:          task.GetMaintenanceTaskId(),
				Description: task.GetDescription(),
			}
		},
	)

	return &coreapi.UpdateMaintenanceWindowResp{
		UnchangedTasks: unchangedTasks,
		BaseResp:       &base.BaseResp{},
	}, nil
}

func (c *Controller) CreateMaintenanceTask(
	ctx context.Context,
	req *coreapi.CreateMaintenanceTaskReq,
) (*coreapi.CreateMaintenanceTaskResp, error) {
	resp, err := c.mwSvc.CreateMaintenanceTask(ctx, &tidbmgmtservicev1.CreateMaintenanceTaskRequest{
		MaintenanceTask: &tidbmgmtservicev1.MaintenanceTask{
			ProjectId:   req.GetProjectId(),
			Description: req.GetDescription(),
			Deadline:    timestamppb.New(time.Unix(req.GetDeadline(), 0)),
			ClusterIds:  req.GetClusterIds(),
		},
	})
	if err != nil {
		return &coreapi.CreateMaintenanceTaskResp{BaseResp: &base.BaseResp{}}, err
	}
	return &coreapi.CreateMaintenanceTaskResp{
		Id:       resp.GetMaintenanceTaskId(),
		BaseResp: &base.BaseResp{},
	}, nil
}

func (c *Controller) QueryMaintenanceTasks(
	ctx context.Context,
	req *coreapi.QueryMaintenanceTasksReq,
) (*coreapi.QueryMaintenanceTasksResp, error) {
	if req.GetFilter() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "missing filter")
	}
	filter := req.GetFilter()
	resp, err := c.mwSvc.QueryMaintenanceTasks(ctx, &tidbmgmtservicev1.QueryMaintenanceTasksRequest{
		Filter: &tidbmgmtservicev1.MaintenanceTaskFilter{
			ProjectIds: filter.GetProjectIds(),
			States: lo.Map(
				filter.GetStatuses(),
				func(status coreapi.MaintenanceTask_Status, _ int) tidbmgmtservicev1.MaintenanceTask_State {
					return coreServiceToMgmtServiceMaintenanceTaskStatus(status)
				},
			),
			MaintenanceTaskIds: filter.GetIds(),
		},
	})
	if err != nil {
		return &coreapi.QueryMaintenanceTasksResp{BaseResp: &base.BaseResp{}}, err
	}
	if resp == nil || resp.GetMaintenanceTasks() == nil || len(resp.GetMaintenanceTasks()) == 0 {
		return &coreapi.QueryMaintenanceTasksResp{BaseResp: &base.BaseResp{}}, nil
	}
	outputs := lo.Map(
		resp.GetMaintenanceTasks(),
		func(task *tidbmgmtservicev1.MaintenanceTask, _ int) *coreapi.MaintenanceTask {
			return mgmtServiceToCoreServiceMaintenanceTask(task)
		},
	)
	return &coreapi.QueryMaintenanceTasksResp{
		Tasks: outputs,
	}, nil
}

func (c *Controller) CountMaintenanceTasks(
	ctx context.Context,
	req *coreapi.CountMaintenanceTasksReq,
) (*coreapi.CountMaintenanceTasksResp, error) {
	if req.GetFilter() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "missing filter")
	}
	filter := req.GetFilter()
	resp, err := c.mwSvc.CountMaintenanceTasks(ctx, &tidbmgmtservicev1.CountMaintenanceTasksRequest{
		Filter: &tidbmgmtservicev1.MaintenanceTaskFilter{
			ProjectIds: filter.GetProjectIds(),
			States: lo.Map(
				filter.GetStatuses(),
				func(status coreapi.MaintenanceTask_Status, _ int) tidbmgmtservicev1.MaintenanceTask_State {
					return coreServiceToMgmtServiceMaintenanceTaskStatus(status)
				},
			),
			MaintenanceTaskIds: filter.GetIds(),
		},
	})

	if err != nil {
		return &coreapi.CountMaintenanceTasksResp{BaseResp: &base.BaseResp{}}, err
	}

	return &coreapi.CountMaintenanceTasksResp{
		Count: int64(resp.GetCount()),
	}, nil
}

func (c *Controller) DeferMaintenanceTask(
	ctx context.Context,
	req *coreapi.DeferMaintenanceTaskReq,
) (*coreapi.DeferMaintenanceTaskResp, error) {
	task, err := c.mwSvc.GetMaintenanceTask(
		ctx,
		&tidbmgmtservicev1.GetMaintenanceTaskRequest{MaintenanceTaskId: req.GetId()},
	)
	if err != nil {
		return &coreapi.DeferMaintenanceTaskResp{BaseResp: &base.BaseResp{}}, err
	}
	if err := deferToNextSchedule(task); err != nil {
		return &coreapi.DeferMaintenanceTaskResp{BaseResp: &base.BaseResp{}}, err
	}
	if _, err := c.mwSvc.UpdateMaintenanceTask(ctx, &tidbmgmtservicev1.UpdateMaintenanceTaskRequest{
		MaintenanceTask: task,
	}); err != nil {
		return &coreapi.DeferMaintenanceTaskResp{BaseResp: &base.BaseResp{}}, err
	}
	return &coreapi.DeferMaintenanceTaskResp{
		BaseResp: &base.BaseResp{},
	}, nil
}

func (c *Controller) UpdateMaintenanceTaskStatus(
	ctx context.Context,
	req *coreapi.UpdateMaintenanceTaskStatusReq,
) (*coreapi.UpdateMaintenanceTaskStatusResp, error) {
	_, err := c.mwSvc.UpdateMaintenanceTask(ctx, &tidbmgmtservicev1.UpdateMaintenanceTaskRequest{
		MaintenanceTask: &tidbmgmtservicev1.MaintenanceTask{
			MaintenanceTaskId: req.GetId(),
			State:             coreServiceToMgmtServiceMaintenanceTaskStatus(req.GetStatus()),
		},
	})
	if err != nil {
		return &coreapi.UpdateMaintenanceTaskStatusResp{BaseResp: &base.BaseResp{}}, err
	}
	return &coreapi.UpdateMaintenanceTaskStatusResp{
		BaseResp: &base.BaseResp{},
	}, nil
}

func (c *Controller) QueryMaintenanceTaskExecutions(
	ctx context.Context,
	req *coreapi.QueryMaintenanceTaskExecutionsReq,
) (*coreapi.QueryMaintenanceTaskExecutionsResp, error) {
	if req.GetFilter() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "missing filter")
	}
	filter := req.GetFilter()
	resp, err := c.mwSvc.QueryMaintenanceTaskExecutions(ctx, &tidbmgmtservicev1.QueryMaintenanceTaskExecutionsRequest{
		Filter: &tidbmgmtservicev1.QueryMaintenanceTaskExecutionsRequest_Filter{
			ClusterIds:                  filter.GetClusterIds(),
			MaintenanceTaskIds:          filter.GetMaintenanceTaskIds(),
			MaintenanceTaskExecutionIds: filter.GetIds(),
			States: lo.Map(
				req.GetFilter().GetStatuses(),
				func(status coreapi.MaintenanceTaskExecution_Status, _ int) tidbmgmtservicev1.MaintenanceTaskExecution_State {
					return tidbmgmtservicev1.MaintenanceTaskExecution_State(status)
				},
			),
		},
	})
	if err != nil {
		return &coreapi.QueryMaintenanceTaskExecutionsResp{BaseResp: &base.BaseResp{}}, err
	}
	if resp == nil || resp.GetMaintenanceTaskExecutions() == nil || len(resp.GetMaintenanceTaskExecutions()) == 0 {
		return &coreapi.QueryMaintenanceTaskExecutionsResp{BaseResp: &base.BaseResp{}}, nil
	}
	output := lo.Map(
		resp.GetMaintenanceTaskExecutions(),
		func(execution *tidbmgmtservicev1.MaintenanceTaskExecution, _ int) *coreapi.MaintenanceTaskExecution {
			return mgmtServiceToCoreServiceMaintenanceTaskExecution(execution)
		},
	)
	return &coreapi.QueryMaintenanceTaskExecutionsResp{
		Executions: output,
	}, nil
}

func (c *Controller) UpdateMaintenanceTaskExecutionStatus(
	ctx context.Context,
	req *coreapi.UpdateMaintenanceTaskExecutionStatusReq,
) (*coreapi.UpdateMaintenanceTaskExecutionStatusResp, error) {
	if req.GetStatus() == coreapi.MaintenanceTaskExecution_STATUS_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "missing status")
	}
	_, err := c.mwSvc.UpdateMaintenanceTaskExecution(ctx, &tidbmgmtservicev1.UpdateMaintenanceTaskExecutionRequest{
		MaintenanceTaskExecution: &tidbmgmtservicev1.MaintenanceTaskExecution{
			MaintenanceTaskExecutionId: req.GetId(),
			State:                      coreServiceToMgmtServiceMaintenanceTaskExecutionStatus(req.GetStatus()),
		},
	})
	if err != nil {
		return &coreapi.UpdateMaintenanceTaskExecutionStatusResp{BaseResp: &base.BaseResp{}}, err
	}
	return &coreapi.UpdateMaintenanceTaskExecutionStatusResp{
		BaseResp: &base.BaseResp{},
	}, nil
}

func (c *Controller) SyncMaintenanceWindow(
	ctx context.Context,
	req *coreapi.SyncMaintenanceWindowReq,
) (*coreapi.SyncMaintenanceWindowResp, error) {
	_, err := c.mwSvc.SyncMaintenanceWindow(ctx, &tidbmgmtservicev1.SyncMaintenanceWindowRequest{})
	if err != nil {
		return &coreapi.SyncMaintenanceWindowResp{BaseResp: &base.BaseResp{}}, err
	}
	return &coreapi.SyncMaintenanceWindowResp{}, nil
}

func deferToNextSchedule(task *tidbmgmtservicev1.MaintenanceTask) bizerr.BizError {
	scheduledApplyTime := task.GetScheduledApplyTime().AsTime().Unix() + 7*24*60*60 // 7 days later
	if task.Deadline != nil && task.GetDeadline().AsTime().Unix() < scheduledApplyTime {
		return bizerr.New(bizerr.ErrCodeParams, "deferred time is after deadline")
	}

	// return updateScheduledApplyTime(task, scheduledApplyTime)
	if task.State != tidbmgmtservicev1.MaintenanceTask_PENDING {
		return bizerr.New(bizerr.ErrCodeParams, "cannot update scheduled apply time on a task that is not pending")
	}

	if task.ScheduledApplyTime.AsTime().Unix() == scheduledApplyTime {
		return nil
	}

	task.ScheduledApplyTime = timestamppb.New(time.Unix(scheduledApplyTime, 0))
	return nil
}

func mgmtServiceToCoreServiceMaintenanceTaskExecution(
	mt *tidbmgmtservicev1.MaintenanceTaskExecution,
) *coreapi.MaintenanceTaskExecution {
	return &coreapi.MaintenanceTaskExecution{
		Id:                 mt.GetMaintenanceTaskExecutionId(),
		MaintenanceTaskId:  mt.GetMaintenanceTaskId(),
		ClusterId:          mt.GetClusterId(),
		Status:             mgmtServiceToCoreServiceMaintenanceTaskExecutionStatus(mt.GetState()),
		CancellationReason: mgmtServiceToCoreServiceMaintenanceTaskExecutionCancelledReason(mt.GetCancellationReason()),
	}
}

func mgmtServiceToCoreServiceMaintenanceTaskExecutionCancelledReason(
	reason tidbmgmtservicev1.MaintenanceTaskExecution_CancellationReason,
) coreapi.MaintenanceTaskExecution_CancellationReason {
	switch reason {
	case tidbmgmtservicev1.MaintenanceTaskExecution_REASON_UNSPECIFIED:
		return coreapi.MaintenanceTaskExecution_REASON_UNSPECIFIED
	case tidbmgmtservicev1.MaintenanceTaskExecution_OPS_CANCELED:
		return coreapi.MaintenanceTaskExecution_REASON_OPS_CANCELED
	case tidbmgmtservicev1.MaintenanceTaskExecution_CLUSTER_DELETED:
		return coreapi.MaintenanceTaskExecution_REASON_CLUSTER_DELETED
	default:
		return coreapi.MaintenanceTaskExecution_REASON_UNSPECIFIED
	}
}

func mgmtServiceToCoreServiceMaintenanceTaskExecutionStatus(
	status tidbmgmtservicev1.MaintenanceTaskExecution_State,
) coreapi.MaintenanceTaskExecution_Status {
	switch status {
	case tidbmgmtservicev1.MaintenanceTaskExecution_PENDING:
		return coreapi.MaintenanceTaskExecution_STATUS_PENDING
	case tidbmgmtservicev1.MaintenanceTaskExecution_RUNNING:
		return coreapi.MaintenanceTaskExecution_STATUS_RUNNING
	case tidbmgmtservicev1.MaintenanceTaskExecution_DONE:
		return coreapi.MaintenanceTaskExecution_STATUS_DONE
	case tidbmgmtservicev1.MaintenanceTaskExecution_CANCELED:
		return coreapi.MaintenanceTaskExecution_STATUS_CANCELED
	default:
		return coreapi.MaintenanceTaskExecution_STATUS_UNSPECIFIED
	}
}

func coreServiceToMgmtServiceMaintenanceTaskExecutionStatus(
	status coreapi.MaintenanceTaskExecution_Status,
) tidbmgmtservicev1.MaintenanceTaskExecution_State {
	switch status {
	case coreapi.MaintenanceTaskExecution_STATUS_PENDING:
		return tidbmgmtservicev1.MaintenanceTaskExecution_PENDING
	case coreapi.MaintenanceTaskExecution_STATUS_RUNNING:
		return tidbmgmtservicev1.MaintenanceTaskExecution_RUNNING
	case coreapi.MaintenanceTaskExecution_STATUS_DONE:
		return tidbmgmtservicev1.MaintenanceTaskExecution_DONE
	case coreapi.MaintenanceTaskExecution_STATUS_CANCELED:
		return tidbmgmtservicev1.MaintenanceTaskExecution_CANCELED
	default:
		return tidbmgmtservicev1.MaintenanceTaskExecution_STATE_UNSPECIFIED
	}
}

func mgmtServiceToCoreServiceMaintenanceWindow(mw *tidbmgmtservicev1.MaintenanceWindow) *coreapi.MaintenanceWindow {
	return &coreapi.MaintenanceWindow{
		ProjectId:    mw.GetProjectId(),
		DayOfWeek:    mw.GetWeekDay(),
		HourOfDay:    mw.GetDayHour(),
		MinuteOfHour: mw.GetHourMinute(),
	}
}

func mgmtServiceToCoreServiceMaintenanceTask(mt *tidbmgmtservicev1.MaintenanceTask) *coreapi.MaintenanceTask {
	return &coreapi.MaintenanceTask{
		Id:                 mt.GetMaintenanceTaskId(),
		ProjectId:          mt.GetProjectId(),
		Description:        mt.GetDescription(),
		Deadline:           lo.ToPtr(mt.GetDeadline().AsTime().Unix()),
		ScheduledApplyTime: mt.GetScheduledApplyTime().AsTime().Unix(),
		Status:             mgmtServiceToCoreServiceMaintenanceTaskStatus(mt.GetState()),
	}
}

func mgmtServiceToCoreServiceMaintenanceTaskStatus(
	status tidbmgmtservicev1.MaintenanceTask_State,
) coreapi.MaintenanceTask_Status {
	switch status {
	case tidbmgmtservicev1.MaintenanceTask_PENDING:
		return coreapi.MaintenanceTask_STATUS_PENDING
	case tidbmgmtservicev1.MaintenanceTask_RUNNING:
		return coreapi.MaintenanceTask_STATUS_RUNNING
	case tidbmgmtservicev1.MaintenanceTask_DONE:
		return coreapi.MaintenanceTask_STATUS_DONE
	default:
		return coreapi.MaintenanceTask_STATUS_UNSPECIFIED
	}
}

func coreServiceToMgmtServiceMaintenanceTaskStatus(
	status coreapi.MaintenanceTask_Status,
) tidbmgmtservicev1.MaintenanceTask_State {
	switch status {
	case coreapi.MaintenanceTask_STATUS_PENDING:
		return tidbmgmtservicev1.MaintenanceTask_PENDING
	case coreapi.MaintenanceTask_STATUS_RUNNING:
		return tidbmgmtservicev1.MaintenanceTask_RUNNING
	case coreapi.MaintenanceTask_STATUS_DONE:
		return tidbmgmtservicev1.MaintenanceTask_DONE
	default:
		return tidbmgmtservicev1.MaintenanceTask_STATE_UNSPECIFIED
	}
}
