package filestore

import (
	"errors"
	"mime"
	"mime/multipart"
	"path/filepath"
	"strings"
)

const (
	// MaxUploadSize is the maximum allowed size per file (100 MB).
	MaxUploadSize = 100 * 1024 * 1024
	// MaxBatchUploadFiles is the maximum number of files per batch request.
	MaxBatchUploadFiles = 20
)

// UploadedFileResult holds metadata for a successfully stored file.
type UploadedFileResult struct {
	FileID      string `json:"file_id"`
	Filename    string `json:"filename"`
	ContentType string `json:"content_type"`
	Size        int64  `json:"size"`
}

// FailedUploadResult describes a file that could not be uploaded.
type FailedUploadResult struct {
	Filename string `json:"filename"`
	Error    string `json:"error"`
	Code     string `json:"code,omitempty"`
}

// UploadFromMultipartFile validates and stores a single multipart file.
func UploadFromMultipartFile(service FileStoreService, file *multipart.FileHeader, opts UploadOptions) (UploadedFileResult, error) {
	if file == nil {
		return UploadedFileResult{}, NewError(ErrCodeInvalidInput, "file is required")
	}

	contentType := detectContentType(file)

	validator := NewFileValidator(MaxUploadSize, SupportedMimeTypes)
	if err := validator.ValidateFile(file.Filename, contentType, file.Size); err != nil {
		return UploadedFileResult{}, err
	}

	src, err := file.Open()
	if err != nil {
		return UploadedFileResult{}, NewErrorWithCause(ErrCodeUploadFailed, "failed to open uploaded file", err)
	}
	defer src.Close()

	if opts.Filename == "" {
		opts.Filename = file.Filename
	}
	if opts.ContentType == "" {
		opts.ContentType = contentType
	}
	if opts.MaxSize == 0 {
		opts.MaxSize = MaxUploadSize
	}

	fileID, err := service.Upload("", file.Filename, src, opts)
	if err != nil {
		return UploadedFileResult{}, err
	}

	return UploadedFileResult{
		FileID:      fileID,
		Filename:    file.Filename,
		ContentType: contentType,
		Size:        file.Size,
	}, nil
}

// UploadManyFromMultipart uploads multiple files and returns successes and failures.
func UploadManyFromMultipart(service FileStoreService, files []*multipart.FileHeader, opts UploadOptions) ([]UploadedFileResult, []FailedUploadResult) {
	uploaded := make([]UploadedFileResult, 0, len(files))
	failed := make([]FailedUploadResult, 0)

	for _, file := range files {
		result, err := UploadFromMultipartFile(service, file, opts)
		if err != nil {
			code := ErrCodeUploadFailed
			var fsErr *FileStoreError
			if errors.As(err, &fsErr) {
				code = fsErr.Code
			}
			failed = append(failed, FailedUploadResult{
				Filename: file.Filename,
				Error:    err.Error(),
				Code:     code,
			})
			continue
		}
		uploaded = append(uploaded, result)
	}

	return uploaded, failed
}

func detectContentType(file *multipart.FileHeader) string {
	contentType := file.Header.Get("Content-Type")
	if contentType == "" {
		ext := filepath.Ext(file.Filename)
		if detected := mime.TypeByExtension(ext); detected != "" {
			contentType = detected
		} else {
			contentType = "application/octet-stream"
		}
	}
	return strings.TrimSpace(strings.Split(contentType, ";")[0])
}

// CollectMultipartFiles returns file headers from the "files" field, or a single "file" if present.
func CollectMultipartFiles(form *multipart.Form) []*multipart.FileHeader {
	if form == nil {
		return nil
	}

	files := form.File["files"]
	if len(files) > 0 {
		return files
	}
	if file := form.File["file"]; len(file) > 0 {
		return file
	}
	return nil
}
