package handlers

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"git.elijahkuntz.com/Elijah/drive/config"
	"git.elijahkuntz.com/Elijah/drive/database"
	"git.elijahkuntz.com/Elijah/drive/workers"
	"github.com/gofiber/fiber/v2"
)

type ArchiveHandler struct {
	DB     *database.DB
	Config *config.Config
}

// Zip creates a zip archive of a folder.
// POST /api/files/zip
func (h *ArchiveHandler) Zip(c *fiber.Ctx) error {
	var body struct {
		SourcePath string `json:"source_path"`
		DestPath   string `json:"dest_path"`
	}
	if err := c.BodyParser(&body); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}

	srcFull, err := resolveSafe(h.Config.StorageDir, body.SourcePath)
	if err != nil {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
	}

	info, err := os.Stat(srcFull)
	if err != nil || !info.IsDir() {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "source must be a valid directory"})
	}

	// Default dest path if not provided
	if body.DestPath == "" {
		body.DestPath = filepath.ToSlash(filepath.Clean(body.SourcePath)) + ".zip"
	}

	dstFull, err := resolveSafe(h.Config.StorageDir, body.DestPath)
	if err != nil {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
	}

	task := workers.Tasks.CreateTask("zip", "Starting zip process...")
	
	// Start async worker
	go func() {
		workers.ZipFolderAsync(task.ID, srcFull, dstFull)
		// Sync to DB when complete
		h.syncArchiveToDB(body.DestPath)
	}()

	h.DB.AddAuditLog("zip_started", fmt.Sprintf("%s -> %s", body.SourcePath, body.DestPath), c.IP())

	return c.JSON(fiber.Map{"message": "Zip process started", "task_id": task.ID})
}

// Unzip extracts a zip archive to a folder.
// POST /api/files/unzip
func (h *ArchiveHandler) Unzip(c *fiber.Ctx) error {
	var body struct {
		SourcePath string `json:"source_path"`
		DestPath   string `json:"dest_path"` // Folder to extract into
	}
	if err := c.BodyParser(&body); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}

	srcFull, err := resolveSafe(h.Config.StorageDir, body.SourcePath)
	if err != nil {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
	}

	// Default dest folder
	if body.DestPath == "" {
		body.DestPath = strings.TrimSuffix(body.SourcePath, filepath.Ext(body.SourcePath))
	}

	dstFull, err := resolveSafe(h.Config.StorageDir, body.DestPath)
	if err != nil {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
	}

	task := workers.Tasks.CreateTask("unzip", "Starting unzip process...")

	// Start async worker
	go func() {
		workers.UnzipAsync(task.ID, srcFull, dstFull)
		// Trigger a DB resync for the extracted folder (lightweight for now)
		h.syncArchiveToDB(body.DestPath)
	}()

	h.DB.AddAuditLog("unzip_started", fmt.Sprintf("%s -> %s", body.SourcePath, body.DestPath), c.IP())

	return c.JSON(fiber.Map{"message": "Unzip process started", "task_id": task.ID})
}

// Tasks returns the list of active background tasks.
// GET /api/tasks
func (h *ArchiveHandler) Tasks(c *fiber.Ctx) error {
	tasks := workers.Tasks.GetTasks()
	return c.JSON(fiber.Map{"tasks": tasks})
}

// syncArchiveToDB records the output of the zip/unzip operation.
func (h *ArchiveHandler) syncArchiveToDB(relPath string) {
	fullPath := filepath.Join(h.Config.StorageDir, filepath.FromSlash(relPath))
	info, err := os.Stat(fullPath)
	if err != nil {
		return
	}

	cleanPath := filepath.ToSlash(relPath)
	
	// Just insert the top-level record, deeper sync isn't strictly needed 
	// unless FTS is required immediately for all extracted contents.
	h.DB.Exec(`
		INSERT INTO files (path, name, size, is_dir)
		VALUES (?, ?, ?, ?)
		ON CONFLICT(path) DO UPDATE SET
			size = excluded.size,
			updated_at = CURRENT_TIMESTAMP
	`, cleanPath, filepath.Base(cleanPath), info.Size(), info.IsDir())
}

func resolveSafe(root, rel string) (string, error) {
	cleaned := filepath.Clean(rel)
	if strings.Contains(cleaned, "..") {
		return "", fmt.Errorf("path traversal detected")
	}

	full := filepath.Join(root, cleaned)
	abs, err := filepath.Abs(full)
	if err != nil {
		return "", fmt.Errorf("invalid path")
	}

	absRoot, _ := filepath.Abs(root)
	if abs != absRoot && !strings.HasPrefix(abs, absRoot+string(filepath.Separator)) {
		return "", fmt.Errorf("access denied")
	}

	return abs, nil
}
