package catalog

import (
	"errors"
	"strings"
	"time"

	"github.com/get-drexa/drexa/internal/account"
	"github.com/get-drexa/drexa/internal/httperr"
	"github.com/get-drexa/drexa/internal/virtualfs"
	"github.com/gofiber/fiber/v2"
)

// FileInfo represents file metadata
// @Description File information including name, size, and timestamps
type FileInfo struct {
	// Item type, always "file"
	Kind string `json:"kind" example:"file"`
	// Unique file identifier
	ID string `json:"id" example:"mElnUNCm8F22"`
	// ParentID is the public ID of the directory this file is in
	ParentID string `json:"parentId,omitempty" example:"kRp2XYTq9A55"`
	// File name
	Name string `json:"name" example:"document.pdf"`
	// File size in bytes
	Size int64 `json:"size" example:"1048576"`
	// MIME type of the file
	MimeType string `json:"mimeType" example:"application/pdf"`
	// When the file was created (ISO 8601)
	CreatedAt time.Time `json:"createdAt" example:"2024-12-13T15:04:05Z"`
	// When the file was last updated (ISO 8601)
	UpdatedAt time.Time `json:"updatedAt" example:"2024-12-13T16:30:00Z"`
	// When the file was trashed, null if not trashed (ISO 8601)
	DeletedAt *time.Time `json:"deletedAt,omitempty" example:"2024-12-14T10:00:00Z"`
}

func mustCurrentFileNode(c *fiber.Ctx) *virtualfs.Node {
	return c.Locals("file").(*virtualfs.Node)
}

func (h *HTTPHandler) currentFileMiddleware(c *fiber.Ctx) error {
	account := account.CurrentAccount(c)
	if account == nil {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	fileID := c.Params("fileID")
	node, err := h.vfs.FindNodeByPublicID(c.Context(), h.db, account.ID, fileID)
	if err != nil {
		if errors.Is(err, virtualfs.ErrNodeNotFound) {
			return c.SendStatus(fiber.StatusNotFound)
		}
		return httperr.Internal(err)
	}

	c.Locals("file", node)

	return c.Next()
}

// fetchFile returns file metadata
// @Summary Get file info
// @Description Retrieve metadata for a specific file
// @Tags files
// @Produce json
// @Security BearerAuth
// @Param accountID path string true "Account ID" format(uuid)
// @Param fileID path string true "File ID"
// @Success 200 {object} FileInfo "File metadata"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "File not found"
// @Router /accounts/{accountID}/files/{fileID} [get]
func (h *HTTPHandler) fetchFile(c *fiber.Ctx) error {
	node := mustCurrentFileNode(c)
	i := FileInfo{
		Kind:      DirItemKindFile,
		ID:        node.PublicID,
		Name:      node.Name,
		Size:      node.Size,
		MimeType:  node.MimeType,
		CreatedAt: node.CreatedAt,
		UpdatedAt: node.UpdatedAt,
		DeletedAt: node.DeletedAt,
	}
	return c.JSON(i)
}

// downloadFile streams file content
// @Summary Download file
// @Description Download the file content. May redirect to a signed URL for external storage.
// @Tags files
// @Produce application/octet-stream
// @Security BearerAuth
// @Param accountID path string true "Account ID" format(uuid)
// @Param fileID path string true "File ID"
// @Success 200 {file} binary "File content stream"
// @Success 307 {string} string "Redirect to download URL"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "File not found"
// @Router /accounts/{accountID}/files/{fileID}/content [get]
func (h *HTTPHandler) downloadFile(c *fiber.Ctx) error {
	node := mustCurrentFileNode(c)

	content, err := h.vfs.ReadFile(c.Context(), h.db, node)
	if err != nil {
		if errors.Is(err, virtualfs.ErrUnsupportedOperation) {
			return c.SendStatus(fiber.StatusNotFound)
		}
		return httperr.Internal(err)
	}

	if content.URL != "" {
		return c.Redirect(content.URL, fiber.StatusTemporaryRedirect)
	}

	if content.Reader != nil {
		if node.MimeType != "" {
			c.Set("Content-Type", node.MimeType)
		}
		if content.Size > 0 {
			return c.SendStream(content.Reader, int(content.Size))
		}
		return c.SendStream(content.Reader)
	}

	return httperr.Internal(errors.New("vfs returned neither a reader nor a URL"))
}

// patchFile updates file properties
// @Summary Update file
// @Description Update file properties such as name (rename)
// @Tags files
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param accountID path string true "Account ID" format(uuid)
// @Param fileID path string true "File ID"
// @Param request body patchFileRequest true "File update"
// @Success 200 {object} FileInfo "Updated file metadata"
// @Failure 400 {object} map[string]string "Invalid request"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "File not found"
// @Router /accounts/{accountID}/files/{fileID} [patch]
func (h *HTTPHandler) patchFile(c *fiber.Ctx) error {
	node := mustCurrentFileNode(c)

	patch := new(patchFileRequest)
	if err := c.BodyParser(patch); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
	}

	tx, err := h.db.BeginTx(c.Context(), nil)
	if err != nil {
		return httperr.Internal(err)
	}
	defer tx.Rollback()

	if patch.Name != "" {
		err := h.vfs.RenameNode(c.Context(), tx, node, patch.Name)
		if err != nil {
			if errors.Is(err, virtualfs.ErrNodeNotFound) {
				return c.SendStatus(fiber.StatusNotFound)
			}
			return httperr.Internal(err)
		}
	}

	err = tx.Commit()
	if err != nil {
		return httperr.Internal(err)
	}

	return c.JSON(FileInfo{
		ID:        node.PublicID,
		Name:      node.Name,
		Size:      node.Size,
		MimeType:  node.MimeType,
		CreatedAt: node.CreatedAt,
		UpdatedAt: node.UpdatedAt,
		DeletedAt: node.DeletedAt,
	})
}

// deleteFile removes a file
// @Summary Delete file
// @Description Delete a file permanently or move it to trash
// @Tags files
// @Produce json
// @Security BearerAuth
// @Param accountID path string true "Account ID" format(uuid)
// @Param fileID path string true "File ID"
// @Param trash query bool false "Move to trash instead of permanent delete" default(false)
// @Success 200 {object} FileInfo "Trashed file info (when trash=true)"
// @Success 204 {string} string "Permanently deleted (when trash=false)"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "File not found"
// @Router /accounts/{accountID}/files/{fileID} [delete]
func (h *HTTPHandler) deleteFile(c *fiber.Ctx) error {
	node := mustCurrentFileNode(c)

	tx, err := h.db.BeginTx(c.Context(), nil)
	if err != nil {
		return httperr.Internal(err)
	}
	defer tx.Rollback()

	shouldTrash := c.Query("trash") == "true"
	if shouldTrash {
		deleted, err := h.vfs.SoftDeleteNode(c.Context(), tx, node)
		if err != nil {
			if errors.Is(err, virtualfs.ErrNodeNotFound) {
				return c.SendStatus(fiber.StatusNotFound)
			}
			return httperr.Internal(err)
		}

		err = tx.Commit()
		if err != nil {
			return httperr.Internal(err)
		}

		return c.JSON(fileInfoFromNode(deleted))
	} else {
		err = h.vfs.PermanentlyDeleteNode(c.Context(), tx, node)
		if err != nil {
			return httperr.Internal(err)
		}

		err = tx.Commit()
		if err != nil {
			return httperr.Internal(err)
		}

		return c.SendStatus(fiber.StatusNoContent)
	}
}

// deleteFiles removes multiple files
// @Summary Bulk delete files
// @Description Delete multiple files permanently or move them to trash. All items must be files.
// @Tags files
// @Security BearerAuth
// @Param accountID path string true "Account ID" format(uuid)
// @Param id query string true "Comma-separated list of file IDs to delete" example:"mElnUNCm8F22,kRp2XYTq9A55"
// @Param trash query bool false "Move to trash instead of permanent delete" default(false)
// @Success 204 {string} string "Files deleted"
// @Failure 400 {object} map[string]string "All items must be files"
// @Failure 401 {string} string "Not authenticated"
// @Router /accounts/{accountID}/files [delete]
func (h *HTTPHandler) deleteFiles(c *fiber.Ctx) error {
	account := account.CurrentAccount(c)
	if account == nil {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	idq := c.Query("id", "")
	if idq == "" {
		return c.SendStatus(fiber.StatusNoContent)
	}

	ids := strings.Split(idq, ",")
	if len(ids) == 0 {
		return c.SendStatus(fiber.StatusNoContent)
	}

	shouldTrash := c.Query("trash") == "true"

	tx, err := h.db.BeginTx(c.Context(), nil)
	if err != nil {
		return httperr.Internal(err)
	}
	defer tx.Rollback()

	nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, account.ID, ids)
	if err != nil {
		return httperr.Internal(err)
	}

	if len(nodes) == 0 {
		return c.SendStatus(fiber.StatusNoContent)
	}

	if shouldTrash {
		deleted, err := h.vfs.SoftDeleteNodes(c.Context(), tx, nodes)
		if err != nil {
			return httperr.Internal(err)
		}

		err = tx.Commit()
		if err != nil {
			return httperr.Internal(err)
		}

		res := make([]FileInfo, 0, len(deleted))
		for _, node := range deleted {
			res = append(res, fileInfoFromNode(node))
		}

		return c.JSON(res)
	} else {
		err = h.vfs.PermanentlyDeleteFiles(c.Context(), tx, nodes)
		if err != nil {
			if errors.Is(err, virtualfs.ErrUnsupportedOperation) {
				return httperr.NewHTTPError(fiber.StatusBadRequest, "all items must be files", err)
			}
			return httperr.Internal(err)
		}

		err = tx.Commit()
		if err != nil {
			return httperr.Internal(err)
		}

		return c.SendStatus(fiber.StatusNoContent)
	}

}
