package handlers

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"sheetless-server/database"
	"sheetless-server/models"
	"sheetless-server/utils"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

const uploadDir = "./uploads"

func init() {
	// Create uploads directory if it doesn't exist
	if err := os.MkdirAll(uploadDir, 0755); err != nil {
		panic("Failed to create uploads directory: " + err.Error())
	}
}

func UploadSheet(c *gin.Context) {
	userID, exists := c.Get("user_id")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
		return
	}

	// Get form data
	title := c.PostForm("title")
	if title == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Title is required"})
		return
	}

	composer := c.PostForm("composer")
	description := c.PostForm("description")

	// Get uploaded file
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "File upload failed"})
		return
	}
	defer file.Close()

	// Validate file type (should be PDF)
	if filepath.Ext(header.Filename) != ".pdf" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Only PDF files are allowed"})
		return
	}

	// Generate unique filename
	filename := fmt.Sprintf("%d_%d%s", userID.(uint), time.Now().Unix(), filepath.Ext(header.Filename))
	filePath := filepath.Join(uploadDir, filename)

	// Save file
	out, err := os.Create(filePath)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"})
		return
	}
	defer out.Close()

	_, err = io.Copy(out, file)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"})
		return
	}

	// Get file size
	fileInfo, err := out.Stat()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get file info"})
		return
	}

	fileHash, err := utils.FileHashFromUpload(out)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed calculating hash"})
		return
	}

	uuid, err := GenerateNonexistentSheetUuid()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed generating sheet uuid"})
		return
	}

	// Create database record
	sheet := models.Sheet{
		Uuid:        *uuid,
		Title:       title,
		Composer:    composer,
		Description: description,
		FilePath:    filePath,
		FileSize:    fileInfo.Size(),
		FileHash:    fileHash,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	if err := database.DB.Create(&sheet).Error; err != nil {
		// Clean up file if database insert fails
		os.Remove(filePath)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save sheet metadata"})
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"message": "Sheet uploaded successfully",
		"sheet":   sheet,
	})
}

func ListSheets(c *gin.Context) {
	var sheets []models.Sheet

	query := database.DB.Preload("User")

	// Filter by user if specified
	if userIDStr := c.Query("user_id"); userIDStr != "" {
		userID, err := strconv.ParseUint(userIDStr, 10, 32)
		if err == nil {
			query = query.Where("user_id = ?", uint(userID))
		}
	}

	if err := query.Find(&sheets).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch sheets"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"sheets": sheets})
}

func DownloadSheet(c *gin.Context) {
	idStr := c.Param("id")
	id, err := strconv.ParseUint(idStr, 10, 32)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid sheet ID"})
		return
	}

	var sheet models.Sheet
	if err := database.DB.First(&sheet, uint(id)).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Sheet not found"})
		return
	}

	// Check if file exists
	if _, err := os.Stat(sheet.FilePath); os.IsNotExist(err) {
		c.JSON(http.StatusNotFound, gin.H{"error": "File not found"})
		return
	}

	// Set headers for file download
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(sheet.FilePath)))
	c.Header("Content-Type", "application/pdf")
	c.File(sheet.FilePath)
}

func GenerateNonexistentSheetUuid() (*uuid.UUID, error) {
	for i := 0; i < 10; i++ {
		uuid := uuid.New()

		var exists bool
		err := database.DB.Model(&models.Sheet{}).
			Select("count(*) > 0").
			Where("uuid = ?", uuid).
			Find(&exists).
			Error

		if err != nil {
			return nil, err
		}

		if !exists {
			return &uuid, nil
		}
	}
	return nil, errors.New("Somehow unable to generate new uuid for sheet.")
}
