package file

import (
	"bytes"
	"context"
	"io"
	"mime/multipart"
	"net/http"
	"time"

	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/internal/types"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/perfect-panel/server/pkg/xerr"
	"github.com/pkg/errors"
)

type FileUploadLogic struct {
	logger.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

// Upload file to RustFS
func NewFileUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FileUploadLogic {
	return &FileUploadLogic{
		Logger: logger.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *FileUploadLogic) FileUpload(req *types.FileUploadRequest, fileHeader *multipart.FileHeader, file multipart.File) (resp *types.FileUploadResponse, err error) {
	u, err := currentUserFromContext(l.ctx)
	if err != nil {
		return nil, err
	}

	if fileHeader == nil {
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "file is required")
	}

	contentType, err := sniffContentType(fileHeader, file)
	if err != nil {
		return nil, err
	}
	if err := validateInitRequest(l.svcCtx, req.BizType, fileHeader.Filename, contentType, fileHeader.Size); err != nil {
		return nil, err
	}

	now := time.Now()
	fileID := buildFileID(u.Id, req.BizType, fileHeader.Filename)
	objectKey := buildObjectKey(l.svcCtx.Config.S3.Prefix, u.Id, req.BizType, fileID, fileHeader.Filename, now)

	putResult, err := l.svcCtx.S3Store.PutObject(l.ctx, objectKey, file, fileHeader.Size, contentType)
	if err != nil {
		l.Errorw("put object failed", logger.Field("error", err.Error()), logger.Field("user_id", u.Id), logger.Field("file_id", fileID))
		return nil, err
	}

	return &types.FileUploadResponse{
		FileId:      fileID,
		FileName:    fileHeader.Filename,
		ObjectKey:   objectKey,
		Size:        fileHeader.Size,
		ContentType: contentType,
		Etag:        putResult.ETag,
		Status:      fileUploadCompleteStatus,
	}, nil
}

func sniffContentType(fileHeader *multipart.FileHeader, file multipart.File) (string, error) {
	if headerType := fileHeader.Header.Get("Content-Type"); headerType != "" {
		return headerType, nil
	}
	if seeker, ok := file.(io.ReadSeeker); ok {
		buf := make([]byte, 512)
		n, readErr := seeker.Read(buf)
		if readErr != nil && readErr != io.EOF {
			return "", readErr
		}
		if _, err := seeker.Seek(0, io.SeekStart); err != nil {
			return "", err
		}
		return http.DetectContentType(bytes.TrimRight(buf[:n], "\x00")), nil
	}
	return "application/octet-stream", nil
}
