package clustermaintenance

import (
	"context"
	"fmt"
	"slices"
	"time"

	maintent "github.com/tidbcloud/devops/entity/maintenance"

	"github.com/tidbcloud/devops/api"
	"github.com/tidbcloud/devops/entity/event"
	larknotify "github.com/tidbcloud/devops/pkg/infra/clients/larkcli/notify"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	cmxapi "github.com/tidbcloud/devops/pkg/service/clustermaintenance/api"
	"google.golang.org/protobuf/types/known/timestamppb"

	mgmtproto "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

func (s *svc) ListMaintenances(ctx context.Context, param cmxapi.ListMaintenanceParam) ([]*maintent.ClusterMaintenance, int64, error) {
	param, err := param.Normalize()
	if err != nil {
		return nil, 0, errs.APIErrorNew(api.ErrCodeInvalidArgument, err)
	}

	filter := &mgmtproto.MaintenanceTaskFilter{}

	if len(param.Statues) > 0 {
		filter.States = container.TransformSlice(param.Statues, MaintenanceTaskStatusToCS)
	}
	if len(param.ProjectIDs) > 0 {
		filter.ProjectIds = param.ProjectIDs
	} else {
		filter.ProjectIds = []uint64{}
	}

	// count tasks firstly
	creq := &mgmtproto.CountMaintenanceTasksRequest{
		Filter: filter,
	}
	cresp, err := s.coreSvcCli.Mgmt.Maintenance.CountMaintenanceTasks(ctx, creq)
	if err != nil {
		return nil, 0, err
	}

	total := cresp.Count

	// query tasks from mgmt
	req := &mgmtproto.QueryMaintenanceTasksRequest{
		Filter: filter,
	}
	// if param.Page > 0 {
	// 	req.Pager = &base.Pager{
	// 		Page:     param.Page,
	// 		PageSize: param.PerPage,
	// 	}
	// 	req.Sorters = []*base.Sorter{
	// 		{
	// 			OrderBy: "created_at",
	// 			Sort:    base.Sort_SORT_DESC,
	// 		},
	// 	}
	// }

	resp, err := s.coreSvcCli.Mgmt.Maintenance.QueryMaintenanceTasks(ctx, req)
	if err != nil {
		return nil, 0, err
	}

	tasksInCS := resp.MaintenanceTasks

	// query tasks from devops
	opt := s.repo.Maintenance.Options().InInstanceIDs(container.TransformSlice(tasksInCS, func(t *mgmtproto.MaintenanceTask) uint64 { return t.MaintenanceTaskId }))
	tasksInRepo, err := s.repo.Maintenance.List(ctx, opt)
	if err != nil {
		return nil, 0, err
	}

	tasks := make([]*maintent.ClusterMaintenance, 0, len(tasksInRepo))
	for _, taskInCS := range tasksInCS {
		index := slices.IndexFunc(tasksInRepo,
			func(t *maintent.ClusterMaintenanceInRepo) bool { return t.InstanceID == taskInCS.MaintenanceTaskId })
		if index < 0 {
			// FIXME: Total is not correct if some tasks are not found in repo
			s.logger.Info("ignore a maintenance task because not found in repo",
				"instance_id", taskInCS.MaintenanceTaskId)
			continue
		}
		taskInRepo := tasksInRepo[index]

		task := assembleMaintenance(taskInRepo, taskInCS)

		err := s.fillInMaintenance(ctx, task, param.GetMaintenanceParam)
		if err != nil {
			return nil, 0, err
		}

		tasks = append(tasks, task)
	}

	return tasks, int64(total), nil
}

func (s *svc) GetMaintenance(ctx context.Context, id uint64, param cmxapi.GetMaintenanceParam) (*maintent.ClusterMaintenance, error) {
	param, err := param.Normalize()
	if err != nil {
		return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, err)
	}

	taskInRepo, err := s.repo.Maintenance.Get(ctx, id)
	if err != nil {
		return nil, err
	}

	req := &mgmtproto.QueryMaintenanceTasksRequest{
		Filter: &mgmtproto.MaintenanceTaskFilter{
			MaintenanceTaskIds: []uint64{taskInRepo.InstanceID},
		},
	}
	resp, err := s.coreSvcCli.Mgmt.Maintenance.QueryMaintenanceTasks(ctx, req)
	if err != nil {
		return nil, err
	}

	taskInCS := findMaintenanceInCoreSvcByInstanceID(resp.MaintenanceTasks, taskInRepo.InstanceID)
	if taskInCS == nil {
		return nil, fmt.Errorf("maintenance task %d not found in core svc", taskInRepo.InstanceID)
	}

	task := assembleMaintenance(taskInRepo, taskInCS)

	err = s.fillInMaintenance(ctx, task, param)
	if err != nil {
		return nil, err
	}

	return task, nil
}

func (s *svc) CreateMaintenance(ctx context.Context, param cmxapi.CreateMaintenanceParam) (*uint64, error) {
	param, err := param.Normalize()
	if err != nil {
		return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, err)
	}

	var id uint64
	err = s.dbp.Transaction(ctx, func(ctx context.Context) error {
		// create task in repo
		taskInRepo, err := s.repo.Maintenance.Create(ctx, &maintent.ClusterMaintenanceInRepo{
			ID:             0,
			LarkNotifiedAt: nil,
			InstanceID:     0,
		})
		if err != nil {
			return err
		}
		id = taskInRepo.ID

		// create task in core svc
		req := &mgmtproto.CreateMaintenanceTaskRequest{
			MaintenanceTask: &mgmtproto.MaintenanceTask{ProjectId: param.ProjectID,
				Description: param.Description,
				Deadline:    timestamppb.New(param.Deadline),
				ClusterIds:  param.ClusterIDs},
		}
		resp, err := s.coreSvcCli.Mgmt.Maintenance.CreateMaintenanceTask(ctx, req)
		if err != nil {
			return err
		}

		// update instance id
		err = s.repo.Maintenance.Patch(ctx, taskInRepo, func(current *maintent.ClusterMaintenanceInRepo) error {
			current.InstanceID = resp.MaintenanceTaskId
			return nil
		})
		if err != nil {
			return err
		}

		// publish event
		ev := assembleEvent(taskInRepo.ID, resp.MaintenanceTaskId, actionOperator(ctx), event.ClusterMaintenanceEventTypeTaskCreate)
		err = s.eventBus.ClusterMaintenanceEvent.Publish(ev)
		if err != nil {
			return fmt.Errorf("publish event failed: %w", err)
		}

		return nil
	})

	return &id, err
}

func (s *svc) CloseMaintenance(ctx context.Context, id uint64) error {
	// query from repo
	taskInRepo, err := s.repo.Maintenance.Get(ctx, id)
	if err != nil {
		return err
	}

	// query from core svc
	req := &mgmtproto.QueryMaintenanceTasksRequest{
		Filter: &mgmtproto.MaintenanceTaskFilter{
			MaintenanceTaskIds: []uint64{taskInRepo.InstanceID},
		},
	}
	resp, err := s.coreSvcCli.Mgmt.Maintenance.QueryMaintenanceTasks(ctx, req)
	if err != nil {
		return err
	}

	taskInCS := findMaintenanceInCoreSvcByInstanceID(resp.MaintenanceTasks, taskInRepo.InstanceID)
	if taskInCS == nil {
		return fmt.Errorf("maintenance task %d not found in mgmt", taskInRepo.InstanceID)
	}

	// update status in core svc
	ureq := &mgmtproto.UpdateMaintenanceTaskRequest{
		MaintenanceTask: &mgmtproto.MaintenanceTask{
			MaintenanceTaskId: taskInRepo.InstanceID,
			State:             mgmtproto.MaintenanceTask_DONE},
	}
	_, err = s.coreSvcCli.Mgmt.Maintenance.UpdateMaintenanceTask(ctx, ureq)
	if err != nil {
		return err
	}

	// notify via lark
	param := larknotify.MaintenanceTaskClosedParam{
		ProjectID:  taskInCS.ProjectId,
		DetailLink: maintenanceLink(taskInRepo.ID),
	}
	_, err = s.larkCli.Notify.MaintenanceTaskClosed(ctx, s.conf.NotifyChatID, param)
	if err != nil {
		return fmt.Errorf("notify failed: %w", err)
	}

	// publish event
	ev := assembleEvent(taskInRepo.ID, taskInRepo.InstanceID, actionOperator(ctx), event.ClusterMaintenanceEventTypeTaskClose)
	err = s.eventBus.ClusterMaintenanceEvent.Publish(ev)
	if err != nil {
		return fmt.Errorf("publish event failed: %w", err)
	}

	return nil
}

func (s *svc) fillInMaintenance(ctx context.Context, task *maintent.ClusterMaintenance, param cmxapi.GetMaintenanceParam) error {
	if param.WithExecutions {
		req := &mgmtproto.QueryMaintenanceTaskExecutionsRequest{
			Filter: &mgmtproto.QueryMaintenanceTaskExecutionsRequest_Filter{
				MaintenanceTaskIds: []uint64{task.InstanceID},
			},
		}
		resp, err := s.coreSvcCli.Mgmt.Maintenance.QueryMaintenanceTaskExecutions(ctx, req)
		if err != nil {
			return err
		}

		task.Task.Executions = container.TransformSlice(resp.MaintenanceTaskExecutions, cvtMaintenanceExecutionToEntity)
	}

	return nil
}

func assembleMaintenance(taskInRepo *maintent.ClusterMaintenanceInRepo, taskInCS *mgmtproto.MaintenanceTask) *maintent.ClusterMaintenance {
	task := &maintent.ClusterMaintenance{
		ClusterMaintenanceInRepo: *taskInRepo,

		Task: cvtMaintenanceTaskToEntity(taskInCS),
	}

	return task
}

func cvtMaintenanceTaskToEntity(task *mgmtproto.MaintenanceTask) maintent.ClusterMaintenanceTask {
	e := maintent.ClusterMaintenanceTask{
		ID:           task.MaintenanceTaskId,
		ProjectID:    task.ProjectId,
		CreateAt:     time.Unix(task.CreateTime.AsTime().Unix(), 0),
		Description:  task.Description,
		Status:       MaintenanceTaskStatusToEntity(task.State),
		ScheduleTime: time.Unix(task.ScheduledApplyTime.AsTime().Unix(), 0),
	}

	if task.Deadline != nil {
		e.Deadline = pointer.Pointer(task.Deadline.AsTime())
	}

	return e
}

func MaintenanceTaskStatusToCS(status maintent.ClusterMaintenanceTaskStatus) mgmtproto.MaintenanceTask_State {
	if s, exist := TaskStatusMap[status]; exist {
		return s
	}
	return -1
}

func MaintenanceTaskStatusToEntity(s mgmtproto.MaintenanceTask_State) maintent.ClusterMaintenanceTaskStatus {
	for k, v := range TaskStatusMap {
		if v == s {
			return k
		}
	}

	return "Unknown"
}
