package export_task

import (
	"context"
	"time"

	"github.com/bytedance/sonic"
	"github.com/hibiken/asynq"
	"gorm.io/datatypes"
	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/internal/exporter"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	"github.com/break/junhong_cmp_fiber/internal/store"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
	"github.com/break/junhong_cmp_fiber/pkg/queue"
	"github.com/break/junhong_cmp_fiber/pkg/storage"
)

// Service 导出任务服务。
type Service struct {
	db            *gorm.DB
	taskStore     *postgres.ExportTaskStore
	queueClient   *queue.Client
	storageSvc    *storage.Service
	sceneRegistry *exporter.Registry
}

type dispatchPayload struct {
	TaskID uint `json:"task_id"`
}

// New 创建导出任务服务。
func New(db *gorm.DB, taskStore *postgres.ExportTaskStore, queueClient *queue.Client, storageSvc *storage.Service) *Service {
	return &Service{
		db:            db,
		taskStore:     taskStore,
		queueClient:   queueClient,
		storageSvc:    storageSvc,
		sceneRegistry: exporter.NewDefaultRegistry(db),
	}
}

// CreateTask 创建导出任务并入队 dispatch。
func (s *Service) CreateTask(ctx context.Context, req *dto.CreateExportTaskRequest) (*dto.CreateExportTaskResponse, error) {
	userID := middleware.GetUserIDFromContext(ctx)
	if userID == 0 {
		return nil, errors.New(errors.CodeUnauthorized)
	}

	userType := middleware.GetUserTypeFromContext(ctx)
	if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform && userType != constants.UserTypeAgent {
		return nil, errors.New(errors.CodeForbidden, "当前账号无权限创建导出任务")
	}

	if req.Scene == "" || !s.sceneRegistry.IsSupported(req.Scene) {
		return nil, errors.New(errors.CodeInvalidParam, "导出场景不支持")
	}
	if req.Format != constants.ExportTaskFormatXLSX && req.Format != constants.ExportTaskFormatCSV {
		return nil, errors.New(errors.CodeInvalidParam, "导出格式不支持")
	}

	queryJSON := datatypes.JSON("{}")
	if req.Query != nil {
		raw, err := sonic.Marshal(req.Query)
		if err != nil {
			return nil, errors.Wrap(errors.CodeInvalidParam, err, "导出筛选参数格式不正确")
		}
		queryJSON = datatypes.JSON(raw)
	}

	shopID := middleware.GetShopIDFromContext(ctx)
	enterpriseID := middleware.GetEnterpriseIDFromContext(ctx)

	var (
		creatorShopID       *uint
		creatorEnterpriseID *uint
	)
	if shopID > 0 {
		creatorShopID = &shopID
	}
	if enterpriseID > 0 {
		creatorEnterpriseID = &enterpriseID
	}

	scopeShopIDs := model.UIntListJSON{}
	if userType == constants.UserTypeAgent {
		subordinateShopIDs := middleware.GetSubordinateShopIDs(ctx)
		if subordinateShopIDs == nil {
			if shopID == 0 {
				return nil, errors.New(errors.CodeForbidden, "代理账号缺少店铺信息")
			}
			subordinateShopIDs = []uint{shopID}
		}
		if len(subordinateShopIDs) == 0 {
			return nil, errors.New(errors.CodeForbidden, "当前账号无可导出的数据范围")
		}
		scopeShopIDs = subordinateShopIDs
	}

	task := &model.ExportTask{
		TaskNo:              s.taskStore.GenerateTaskNo(),
		Scene:               req.Scene,
		Format:              req.Format,
		Status:              constants.ExportTaskStatusPending,
		Progress:            0,
		QueryJSON:           queryJSON,
		ScopeShopIDs:        scopeShopIDs,
		CreatorUserID:       userID,
		CreatorUserType:     userType,
		CreatorShopID:       creatorShopID,
		CreatorEnterpriseID: creatorEnterpriseID,
	}
	task.Creator = userID
	task.Updater = userID

	if err := s.taskStore.Create(ctx, task); err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建导出任务失败")
	}

	if err := s.queueClient.EnqueueTask(
		ctx,
		constants.TaskTypeExportDispatch,
		dispatchPayload{TaskID: task.ID},
		asynq.MaxRetry(constants.ExportDispatchRetryMax),
		asynq.Timeout(constants.ExportDispatchTaskTimeout),
		asynq.Queue(constants.QueueForTaskType(constants.TaskTypeExportDispatch)),
	); err != nil {
		_ = s.taskStore.MarkFailed(ctx, task.ID, userID, "导出任务入队失败")
		return nil, errors.Wrap(errors.CodeTaskQueueError, err, "导出任务入队失败")
	}

	return &dto.CreateExportTaskResponse{
		TaskID:     task.ID,
		TaskNo:     task.TaskNo,
		Status:     task.Status,
		StatusName: constants.GetExportTaskStatusName(task.Status),
		Message:    "导出任务创建成功，系统将异步处理",
	}, nil
}

// ListTasks 查询导出任务列表。
func (s *Service) ListTasks(ctx context.Context, req *dto.ListExportTaskRequest) (*dto.ListExportTaskResponse, error) {
	page := req.Page
	if page <= 0 {
		page = 1
	}

	pageSize := req.PageSize
	if pageSize <= 0 {
		pageSize = constants.DefaultPageSize
	}
	if pageSize > constants.MaxPageSize {
		pageSize = constants.MaxPageSize
	}

	filters := make(map[string]any)
	if req.Scene != "" {
		filters["scene"] = req.Scene
	}
	if req.Status != nil {
		filters["status"] = *req.Status
	}
	if req.StartTime != nil {
		filters["start_time"] = *req.StartTime
	}
	if req.EndTime != nil {
		filters["end_time"] = *req.EndTime
	}

	items, total, err := s.taskStore.List(ctx, &store.QueryOptions{
		Page:     page,
		PageSize: pageSize,
		OrderBy:  "created_at DESC",
	}, filters)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询导出任务列表失败")
	}

	result := make([]*dto.ExportTaskItem, 0, len(items))
	for _, item := range items {
		result = append(result, toTaskItemDTO(item))
	}

	return &dto.ListExportTaskResponse{
		List:     result,
		Total:    total,
		Page:     page,
		PageSize: pageSize,
	}, nil
}

// GetTaskDetail 查询导出任务详情。
func (s *Service) GetTaskDetail(ctx context.Context, id uint) (*dto.ExportTaskDetailResponse, error) {
	task, err := s.taskStore.GetByID(ctx, id)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询导出任务详情失败")
	}

	resp := &dto.ExportTaskDetailResponse{ExportTaskItem: *toTaskItemDTO(task)}
	if task.Status == constants.ExportTaskStatusCompleted && task.FileKey != "" && s.storageSvc != nil && s.storageSvc.Provider() != nil {
		url, err := s.storageSvc.Provider().GetDownloadURL(ctx, task.FileKey, constants.ExportDownloadURLExpire)
		if err != nil {
			return nil, errors.Wrap(errors.CodeInternalError, err, "生成下载链接失败")
		}
		expiresAt := time.Now().Add(constants.ExportDownloadURLExpire)
		resp.DownloadURL = url
		resp.DownloadExpiresAt = &expiresAt
	}

	return resp, nil
}

// CancelTask 取消导出任务。
func (s *Service) CancelTask(ctx context.Context, id uint) (*dto.CancelExportTaskResponse, error) {
	userID := middleware.GetUserIDFromContext(ctx)
	if userID == 0 {
		return nil, errors.New(errors.CodeUnauthorized)
	}

	task, err := s.taskStore.GetByID(ctx, id)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询导出任务失败")
	}

	message := "取消请求已提交"
	switch task.Status {
	case constants.ExportTaskStatusPending:
		ok, err := s.taskStore.CancelPendingTask(ctx, id, userID)
		if err != nil {
			return nil, errors.Wrap(errors.CodeDatabaseError, err, "取消导出任务失败")
		}
		if !ok {
			return nil, errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
		}
		message = "任务已取消"
	case constants.ExportTaskStatusProcessing:
		if !task.CancelRequested {
			ok, err := s.taskStore.SetCancelRequested(ctx, id, userID)
			if err != nil {
				return nil, errors.Wrap(errors.CodeDatabaseError, err, "提交取消请求失败")
			}
			if !ok {
				return nil, errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
			}
		} else {
			message = "取消请求已提交，请稍后刷新状态"
		}
	case constants.ExportTaskStatusCompleted, constants.ExportTaskStatusFailed, constants.ExportTaskStatusCancelled:
		return nil, errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
	default:
		return nil, errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
	}

	latestTask, err := s.taskStore.GetByID(ctx, id)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询最新任务状态失败")
	}

	return &dto.CancelExportTaskResponse{
		TaskID:          latestTask.ID,
		Status:          latestTask.Status,
		StatusName:      constants.GetExportTaskStatusName(latestTask.Status),
		CancelRequested: latestTask.CancelRequested,
		Message:         message,
	}, nil
}

func toTaskItemDTO(task *model.ExportTask) *dto.ExportTaskItem {
	return &dto.ExportTaskItem{
		ID:                  task.ID,
		TaskID:              task.ID,
		TaskNo:              task.TaskNo,
		Scene:               task.Scene,
		Format:              task.Format,
		Status:              task.Status,
		StatusName:          constants.GetExportTaskStatusName(task.Status),
		Progress:            task.Progress,
		TotalRows:           task.TotalRows,
		ProcessedRows:       task.ProcessedRows,
		TotalShards:         task.TotalShards,
		SuccessShards:       task.SuccessShards,
		FailedShards:        task.FailedShards,
		TotalCount:          task.TotalShards,
		SuccessCount:        task.SuccessShards,
		FailedCount:         task.FailedShards,
		CancelRequested:     task.CancelRequested,
		FileKey:             task.FileKey,
		ErrorMessage:        task.ErrorMessage,
		ErrorCode:           exportTaskErrorCode(task),
		ErrorSummary:        task.ErrorMessage,
		CreatedAt:           task.CreatedAt,
		UpdatedAt:           task.UpdatedAt,
		StartedAt:           task.StartedAt,
		CompletedAt:         task.CompletedAt,
		CreatorUserID:       task.CreatorUserID,
		CreatorUserType:     task.CreatorUserType,
		CreatorShopID:       task.CreatorShopID,
		CreatorEnterpriseID: task.CreatorEnterpriseID,
	}
}

func exportTaskErrorCode(task *model.ExportTask) string {
	if task.ErrorMessage == "" {
		return ""
	}
	return "EXPORT_TASK_FAILED"
}
