package handlers

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

	"git.elijahkuntz.com/Elijah/drive/config"
	"git.elijahkuntz.com/Elijah/drive/database"
	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
	"golang.org/x/crypto/bcrypt"
)

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

type ShareRequest struct {
	Path         string `json:"path"`
	Password     string `json:"password,omitempty"`
	ExpiresIn    int    `json:"expires_in,omitempty"` // Hours
	MaxDownloads int    `json:"max_downloads,omitempty"`
}

// CreateShare creates a new public share link.
// POST /api/share
func (h *ShareHandler) CreateShare(c *fiber.Ctx) error {
	var req ShareRequest
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request"})
	}

	// Validate path
	fullPath := filepath.Join(h.Config.StorageDir, filepath.FromSlash(req.Path))
	if _, err := os.Stat(fullPath); err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "file not found"})
	}

	id := uuid.New().String()
	var hash *string
	if req.Password != "" {
		hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
		if err == nil {
			s := string(hashed)
			hash = &s
		}
	}

	var expiresAt *time.Time
	if req.ExpiresIn > 0 {
		t := time.Now().Add(time.Duration(req.ExpiresIn) * time.Hour)
		expiresAt = &t
	}

	var maxDownloads *int
	if req.MaxDownloads > 0 {
		maxDownloads = &req.MaxDownloads
	}

	_, err := h.DB.Exec(`
		INSERT INTO share_links (id, file_path, password_hash, expires_at, max_downloads)
		VALUES (?, ?, ?, ?, ?)
	`, id, req.Path, hash, expiresAt, maxDownloads)

	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to create share"})
	}

	h.DB.AddAuditLog("share_created", fmt.Sprintf("Shared %s (ID: %s)", req.Path, id), c.IP())

	return c.JSON(fiber.Map{
		"id": id,
		"link": fmt.Sprintf("/share/%s", id), // Frontend route
	})
}

// ListShares returns all active shares.
// GET /api/share
func (h *ShareHandler) ListShares(c *fiber.Ctx) error {
	rows, err := h.DB.Query(`
		SELECT id, file_path, expires_at, downloads, max_downloads, created_at,
		       (password_hash IS NOT NULL) as has_password
		FROM share_links
		ORDER BY created_at DESC
	`)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "db error"})
	}
	defer rows.Close()

	var shares []map[string]interface{}
	for rows.Next() {
		var id, path, createdAt string
		var expiresAt *string
		var downloads int
		var maxDownloads *int
		var hasPassword bool

		rows.Scan(&id, &path, &expiresAt, &downloads, &maxDownloads, &createdAt, &hasPassword)

		shares = append(shares, map[string]interface{}{
			"id": id,
			"path": path,
			"expires_at": expiresAt,
			"downloads": downloads,
			"max_downloads": maxDownloads,
			"created_at": createdAt,
			"has_password": hasPassword,
		})
	}

	return c.JSON(fiber.Map{"shares": shares})
}

// RevokeShare removes a share.
// DELETE /api/share/:id
func (h *ShareHandler) RevokeShare(c *fiber.Ctx) error {
	id := c.Params("id")
	_, err := h.DB.Exec(`DELETE FROM share_links WHERE id = ?`, id)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to revoke"})
	}
	
	h.DB.AddAuditLog("share_revoked", fmt.Sprintf("Revoked share %s", id), c.IP())
	return c.JSON(fiber.Map{"message": "revoked"})
}

// GetPublicShare returns share info without auth (used by the public page).
// POST /api/public/share/:id
// Body might contain {"password": "..."}
func (h *ShareHandler) GetPublicShare(c *fiber.Ctx) error {
	id := c.Params("id")
	var req struct {
		Password string `json:"password"`
	}
	c.BodyParser(&req)

	var path string
	var hash *string
	var expiresAt *time.Time
	var maxDownloads *int
	var downloads int

	err := h.DB.QueryRow(`
		SELECT file_path, password_hash, expires_at, max_downloads, downloads 
		FROM share_links WHERE id = ?
	`, id).Scan(&path, &hash, &expiresAt, &maxDownloads, &downloads)

	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "share not found"})
	}

	// Check expiry
	if expiresAt != nil && time.Now().After(*expiresAt) {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "link expired"})
	}

	// Check download limits (we only count actual downloads, not views)
	if maxDownloads != nil && downloads >= *maxDownloads {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "download limit reached"})
	}

	// Check password
	if hash != nil {
		if req.Password == "" {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"require_password": true})
		}
		if err := bcrypt.CompareHashAndPassword([]byte(*hash), []byte(req.Password)); err != nil {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid password"})
		}
	}

	// Send file info
	fullPath := filepath.Join(h.Config.StorageDir, filepath.FromSlash(path))
	info, err := os.Stat(fullPath)
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "file no longer exists"})
	}

	// We return a signed download token or just allow the immediate request. 
	// For simplicity, we'll return a short-lived download token JWT.
	// Actually, easier: the frontend calls GET /api/public/download/:id?pwd=xxx
	
	return c.JSON(fiber.Map{
		"name": filepath.Base(path),
		"size": info.Size(),
		"is_dir": info.IsDir(),
	})
}

// DownloadPublicShare serves the file.
// GET /api/public/download/:id
func (h *ShareHandler) DownloadPublicShare(c *fiber.Ctx) error {
	id := c.Params("id")
	pwd := c.Query("pwd")

	var path string
	var hash *string
	var expiresAt *time.Time
	var maxDownloads *int
	var downloads int

	err := h.DB.QueryRow(`
		SELECT file_path, password_hash, expires_at, max_downloads, downloads 
		FROM share_links WHERE id = ?
	`, id).Scan(&path, &hash, &expiresAt, &maxDownloads, &downloads)

	if err != nil {
		return c.Status(fiber.StatusNotFound).SendString("not found")
	}

	if expiresAt != nil && time.Now().After(*expiresAt) {
		return c.Status(fiber.StatusForbidden).SendString("expired")
	}

	if maxDownloads != nil && downloads >= *maxDownloads {
		return c.Status(fiber.StatusForbidden).SendString("limit reached")
	}

	if hash != nil {
		if err := bcrypt.CompareHashAndPassword([]byte(*hash), []byte(pwd)); err != nil {
			return c.Status(fiber.StatusUnauthorized).SendString("unauthorized")
		}
	}

	fullPath := filepath.Join(h.Config.StorageDir, filepath.FromSlash(path))
	
	// Increment download counter
	h.DB.Exec(`UPDATE share_links SET downloads = downloads + 1 WHERE id = ?`, id)
	h.DB.AddAuditLog("share_downloaded", fmt.Sprintf("Downloaded share %s", id), c.IP())

	return c.SendFile(fullPath)
}
