package filestore

import (
	"errors"
	"net/http"
	"strconv"
	"time"

	"tm/pkg/logger"

	"github.com/labstack/echo/v4"
)

// Handler handles HTTP requests for file operations
type Handler struct {
	service FileStoreService
	logger  logger.Logger
}

// NewHandler creates a new file handler
func NewHandler(service FileStoreService, logger logger.Logger) *Handler {
	return &Handler{
		service: service,
		logger:  logger,
	}
}

// UploadResponse represents the response for a successful upload
type UploadResponse struct {
	FileID      string `json:"file_id"`
	Filename    string `json:"filename"`
	ContentType string `json:"content_type"`
	Size        int64  `json:"size"`
	Message     string `json:"message"`
}

// BatchUploadResponse represents the response for a multi-file upload
type BatchUploadResponse struct {
	Uploaded []UploadResponse     `json:"uploaded"`
	Failed   []FailedUploadResult `json:"failed,omitempty"`
	Message  string               `json:"message"`
}

// FileInfoResponse represents file information in API response
type FileInfoResponse struct {
	FileID      string            `json:"file_id"`
	Filename    string            `json:"filename"`
	ContentType string            `json:"content_type"`
	Size        int64             `json:"size"`
	UploadedBy  string            `json:"uploaded_by,omitempty"`
	UploadedAt  time.Time         `json:"uploaded_at"`
	Category    string            `json:"category,omitempty"`
	Tags        []string          `json:"tags,omitempty"`
	Description string            `json:"description,omitempty"`
	Extra       map[string]string `json:"extra,omitempty"`
}

// ListFilesResponse represents the response for list files operation
type ListFilesResponse struct {
	Files []FileInfoResponse `json:"files"`
	Total int64              `json:"total"`
	Limit int64              `json:"limit"`
	Skip  int64              `json:"skip"`
}

// ErrorResponse represents an error response
type ErrorResponse struct {
	Error   string `json:"error"`
	Code    string `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

// Upload handles file upload
// @Summary Upload a file
// @Description Upload a file to GridFS storage
// @Tags Files
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "File to upload"
// @Param category formData string false "File category"
// @Param tags formData []string false "File tags"
// @Param description formData string false "File description"
// @Success 200 {object} UploadResponse
// @Failure 400 {object} ErrorResponse
// @Failure 413 {object} ErrorResponse "File too large"
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/files/upload [post]
func (h *Handler) Upload(c echo.Context) error {
	file, err := c.FormFile("file")
	if err != nil {
		h.logger.Warn("No file provided in upload request", map[string]interface{}{
			"error": err.Error(),
		})
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "No file provided",
			Code:    ErrCodeInvalidInput,
			Message: "Please provide a file to upload",
		})
	}

	uploadedBy := h.getUploaderID(c)
	if uploadedBy == "" {
		return c.JSON(http.StatusUnauthorized, ErrorResponse{
			Error:   "Unauthorized",
			Code:    ErrCodeAccessDenied,
			Message: "Authenticated user not found in request context",
		})
	}

	opts := h.buildUploadOptions(c, uploadedBy)
	result, err := UploadFromMultipartFile(h.service, file, opts)
	if err != nil {
		return h.writeUploadError(c, file.Filename, err)
	}

	return c.JSON(http.StatusOK, UploadResponse{
		FileID:      result.FileID,
		Filename:    result.Filename,
		ContentType: result.ContentType,
		Size:        result.Size,
		Message:     "File uploaded successfully",
	})
}

// UploadBatch handles multi-file upload in a single request
// @Summary Upload multiple files
// @Description Upload up to 20 files to GridFS storage in one request. Use form field "files" (repeat for each file) or a single "file".
// @Tags Files
// @Accept multipart/form-data
// @Produce json
// @Param files formData file true "Files to upload"
// @Param category formData string false "File category"
// @Param tags formData []string false "File tags"
// @Param description formData string false "File description"
// @Success 200 {object} BatchUploadResponse
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/files/upload/batch [post]
func (h *Handler) UploadBatch(c echo.Context) error {
	uploadedBy := h.getUploaderID(c)
	if uploadedBy == "" {
		return c.JSON(http.StatusUnauthorized, ErrorResponse{
			Error:   "Unauthorized",
			Code:    ErrCodeAccessDenied,
			Message: "Authenticated user not found in request context",
		})
	}

	form, err := c.MultipartForm()
	if err != nil {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "Invalid multipart form",
			Code:    ErrCodeInvalidInput,
			Message: err.Error(),
		})
	}

	files := CollectMultipartFiles(form)
	if len(files) == 0 {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "No files provided",
			Code:    ErrCodeInvalidInput,
			Message: "Use form field \"files\" with one or more files",
		})
	}
	if len(files) > MaxBatchUploadFiles {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "Too many files",
			Code:    ErrCodeInvalidInput,
			Message: "Maximum 20 files per request",
		})
	}

	opts := h.buildUploadOptions(c, uploadedBy)
	results, failed := UploadManyFromMultipart(h.service, files, opts)
	if len(results) == 0 {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "All uploads failed",
			Code:    ErrCodeUploadFailed,
			Message: "No files were uploaded successfully",
		})
	}

	uploaded := make([]UploadResponse, len(results))
	for i, r := range results {
		uploaded[i] = UploadResponse{
			FileID:      r.FileID,
			Filename:    r.Filename,
			ContentType: r.ContentType,
			Size:        r.Size,
			Message:     "File uploaded successfully",
		}
	}

	message := "Files uploaded successfully"
	if len(failed) > 0 {
		message = "Some files uploaded successfully"
	}

	return c.JSON(http.StatusOK, BatchUploadResponse{
		Uploaded: uploaded,
		Failed:   failed,
		Message:  message,
	})
}

func (h *Handler) buildUploadOptions(c echo.Context, uploadedBy string) UploadOptions {
	return UploadOptions{
		UploadedBy:  uploadedBy,
		Category:    c.FormValue("category"),
		Tags:        c.Request().Form["tags"],
		Description: c.FormValue("description"),
		MaxSize:     MaxUploadSize,
	}
}

func (h *Handler) writeUploadError(c echo.Context, filename string, err error) error {
	if IsFileTooLarge(err) {
		h.logger.Warn("File upload failed: file too large", map[string]interface{}{
			"filename": filename,
		})
		return c.JSON(http.StatusRequestEntityTooLarge, ErrorResponse{
			Error:   "File is too large",
			Code:    ErrCodeFileTooLarge,
			Message: "Maximum file size is 100 MB",
		})
	}
	if IsUnsupportedType(err) {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "Unsupported file type",
			Code:    ErrCodeUnsupported,
			Message: err.Error(),
		})
	}

	var fsErr *FileStoreError
	if errors.As(err, &fsErr) && fsErr.Code == ErrCodeInvalidInput {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "Invalid file",
			Code:    ErrCodeInvalidInput,
			Message: err.Error(),
		})
	}

	h.logger.Error("File upload failed", map[string]interface{}{
		"filename": filename,
		"error":    err.Error(),
	})
	return c.JSON(http.StatusInternalServerError, ErrorResponse{
		Error:   "Upload failed",
		Code:    ErrCodeUploadFailed,
		Message: "Failed to upload file to storage",
	})
}

// Download handles file download
// @Summary Download a file
// @Description Download a file by its ID
// @Tags Files
// @Produce application/octet-stream
// @Param file_id path string true "File ID"
// @Success 200 {file} bytes
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/files/{file_id}/download [get]
func (h *Handler) Download(c echo.Context) error {
	fileID := c.Param("file_id")
	if fileID == "" {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "File ID is required",
			Code:    ErrCodeInvalidInput,
			Message: "Please provide a valid file ID",
		})
	}

	// Download file
	reader, fileInfo, err := h.service.Download(fileID)
	if err != nil {
		if IsNotFound(err) {
			h.logger.Warn("File not found", map[string]interface{}{
				"file_id": fileID,
			})
			return c.JSON(http.StatusNotFound, ErrorResponse{
				Error:   "File not found",
				Code:    ErrCodeNotFound,
				Message: "The requested file does not exist",
			})
		}

		h.logger.Error("File download failed", map[string]interface{}{
			"file_id": fileID,
			"error":   err.Error(),
		})
		return c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error:   "Download failed",
			Code:    ErrCodeDownloadFailed,
			Message: "Failed to download file",
		})
	}
	defer reader.Close()

	// Set response headers
	c.Response().Header().Set("Content-Type", fileInfo.ContentType)
	c.Response().Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size, 10))
	c.Response().Header().Set("Content-Disposition", `attachment; filename="`+fileInfo.Filename+`"`)
	c.Response().Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")

	// Write file to response
	return c.Stream(http.StatusOK, fileInfo.ContentType, reader)
}

func (h *Handler) getUploaderID(c echo.Context) string {
	if userID, ok := c.Get("user_id").(string); ok && userID != "" {
		return userID
	}
	if customerID, ok := c.Get("customer_id").(string); ok && customerID != "" {
		return customerID
	}
	return ""
}

// GetInfo retrieves file information
// @Summary Get file information
// @Description Get metadata for a file by its ID
// @Tags Files
// @Produce json
// @Param file_id path string true "File ID"
// @Success 200 {object} FileInfoResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/files/{file_id}/info [get]
func (h *Handler) GetInfo(c echo.Context) error {
	fileID := c.Param("file_id")
	if fileID == "" {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "File ID is required",
			Code:    ErrCodeInvalidInput,
			Message: "Please provide a valid file ID",
		})
	}

	// Get file info
	fileInfo, err := h.service.GetFileInfo(fileID)
	if err != nil {
		if IsNotFound(err) {
			return c.JSON(http.StatusNotFound, ErrorResponse{
				Error:   "File not found",
				Code:    ErrCodeNotFound,
				Message: "The requested file does not exist",
			})
		}

		h.logger.Error("Failed to get file info", map[string]interface{}{
			"file_id": fileID,
			"error":   err.Error(),
		})
		return c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error:   "Failed to get file info",
			Code:    ErrCodeDownloadFailed,
			Message: "Unable to retrieve file information",
		})
	}

	return c.JSON(http.StatusOK, FileInfoResponse{
		FileID:      fileInfo.FileID,
		Filename:    fileInfo.Filename,
		ContentType: fileInfo.ContentType,
		Size:        fileInfo.Size,
		UploadedBy:  fileInfo.UploadedBy,
		UploadedAt:  fileInfo.UploadedAt,
		Category:    fileInfo.Category,
		Tags:        fileInfo.Tags,
		Description: fileInfo.Description,
		Extra:       fileInfo.Extra,
	})
}

// Delete removes a file
// @Summary Delete a file
// @Description Delete a file by its ID
// @Tags Files
// @Param file_id path string true "File ID"
// @Success 200 {object} map[string]string
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/files/{file_id} [delete]
func (h *Handler) Delete(c echo.Context) error {
	fileID := c.Param("file_id")
	if fileID == "" {
		return c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:   "File ID is required",
			Code:    ErrCodeInvalidInput,
			Message: "Please provide a valid file ID",
		})
	}

	// Delete file
	err := h.service.Delete(fileID)
	if err != nil {
		if IsNotFound(err) {
			return c.JSON(http.StatusNotFound, ErrorResponse{
				Error:   "File not found",
				Code:    ErrCodeNotFound,
				Message: "The requested file does not exist",
			})
		}

		h.logger.Error("File deletion failed", map[string]interface{}{
			"file_id": fileID,
			"error":   err.Error(),
		})
		return c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error:   "Deletion failed",
			Code:    ErrCodeDeleteFailed,
			Message: "Failed to delete file",
		})
	}

	return c.JSON(http.StatusOK, map[string]string{
		"message": "File deleted successfully",
	})
}

// ListFiles lists files by category
// @Summary List files
// @Description List files by category with optional filtering
// @Tags Files
// @Produce json
// @Param category query string false "File category"
// @Param tags query []string false "File tags"
// @Param limit query int false "Number of files to return (default 50, max 1000)"
// @Param skip query int false "Number of files to skip (default 0)"
// @Success 200 {object} ListFilesResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/files [get]
func (h *Handler) ListFiles(c echo.Context) error {
	category := c.QueryParam("category")
	tags := c.QueryParams()["tags"]
	limit := int64(50)
	skip := int64(0)

	// Parse pagination params
	if l := c.QueryParam("limit"); l != "" {
		if val, err := strconv.ParseInt(l, 10, 64); err != nil {
			return c.JSON(http.StatusBadRequest, ErrorResponse{
				Error:   "Invalid limit parameter",
				Code:    ErrCodeInvalidInput,
				Message: "Limit must be a valid integer",
			})
		} else {
			limit = val
		}
	}
	if s := c.QueryParam("skip"); s != "" {
		if val, err := strconv.ParseInt(s, 10, 64); err != nil {
			return c.JSON(http.StatusBadRequest, ErrorResponse{
				Error:   "Invalid skip parameter",
				Code:    ErrCodeInvalidInput,
				Message: "Skip must be a valid integer",
			})
		} else {
			skip = val
		}
	}

	// List files
	files, total, err := h.service.ListFiles(category, tags, limit, skip)
	if err != nil {
		h.logger.Error("Failed to list files", map[string]interface{}{
			"error": err.Error(),
		})
		return c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error:   "Failed to list files",
			Code:    ErrCodeDownloadFailed,
			Message: "Unable to retrieve file list",
		})
	}

	// Convert to response format
	fileResponses := make([]FileInfoResponse, len(files))
	for i, f := range files {
		fileResponses[i] = FileInfoResponse{
			FileID:      f.FileID,
			Filename:    f.Filename,
			ContentType: f.ContentType,
			Size:        f.Size,
			UploadedBy:  f.UploadedBy,
			UploadedAt:  f.UploadedAt,
			Category:    f.Category,
			Tags:        f.Tags,
			Description: f.Description,
			Extra:       f.Extra,
		}
	}

	return c.JSON(http.StatusOK, ListFilesResponse{
		Files: fileResponses,
		Total: total,
		Limit: limit,
		Skip:  skip,
	})
}
