package tape_vtl

import (
	"fmt"
	"net/http"

	"github.com/atlasos/calypso/internal/common/database"
	"github.com/atlasos/calypso/internal/common/logger"
	"github.com/atlasos/calypso/internal/tasks"
	"github.com/gin-gonic/gin"
)

// Handler handles virtual tape library API requests
type Handler struct {
	service    *Service
	taskEngine *tasks.Engine
	db         *database.DB
	logger     *logger.Logger
}

// NewHandler creates a new VTL handler
func NewHandler(db *database.DB, log *logger.Logger) *Handler {
	return &Handler{
		service:    NewService(db, log),
		taskEngine: tasks.NewEngine(db, log),
		db:         db,
		logger:     log,
	}
}

// ListLibraries lists all virtual tape libraries
func (h *Handler) ListLibraries(c *gin.Context) {
	h.logger.Info("ListLibraries called")
	libraries, err := h.service.ListLibraries(c.Request.Context())
	if err != nil {
		h.logger.Error("Failed to list libraries", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list libraries"})
		return
	}

	h.logger.Info("ListLibraries result", "count", len(libraries), "is_nil", libraries == nil)

	// Ensure we return an empty array instead of null
	if libraries == nil {
		h.logger.Warn("Libraries is nil, converting to empty array")
		libraries = []VirtualTapeLibrary{}
	}

	h.logger.Info("Returning libraries", "count", len(libraries), "libraries", libraries)

	// Ensure we always return an array, never null
	if libraries == nil {
		libraries = []VirtualTapeLibrary{}
	}

	// Force empty array if nil (double check)
	if libraries == nil {
		h.logger.Warn("Libraries is still nil in handler, forcing empty array")
		libraries = []VirtualTapeLibrary{}
	}

	// Use explicit JSON marshalling to ensure empty array, not null
	response := map[string]interface{}{
		"libraries": libraries,
	}

	h.logger.Info("Response payload", "count", len(libraries), "response_type", fmt.Sprintf("%T", libraries))

	// Use JSON marshalling that handles empty slices correctly
	c.JSON(http.StatusOK, response)
}

// GetLibrary retrieves a library by ID
func (h *Handler) GetLibrary(c *gin.Context) {
	libraryID := c.Param("id")

	lib, err := h.service.GetLibrary(c.Request.Context(), libraryID)
	if err != nil {
		if err.Error() == "library not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "library not found"})
			return
		}
		h.logger.Error("Failed to get library", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get library"})
		return
	}

	// Get drives
	drives, _ := h.service.GetLibraryDrives(c.Request.Context(), libraryID)

	// Get tapes
	tapes, _ := h.service.GetLibraryTapes(c.Request.Context(), libraryID)

	c.JSON(http.StatusOK, gin.H{
		"library": lib,
		"drives":  drives,
		"tapes":   tapes,
	})
}

// CreateLibraryRequest represents a library creation request
type CreateLibraryRequest struct {
	Name             string `json:"name" binding:"required"`
	Description      string `json:"description"`
	BackingStorePath string `json:"backing_store_path" binding:"required"`
	SlotCount        int    `json:"slot_count" binding:"required"`
	DriveCount       int    `json:"drive_count" binding:"required"`
}

// CreateLibrary creates a new virtual tape library
func (h *Handler) CreateLibrary(c *gin.Context) {
	var req CreateLibraryRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}

	// Validate slot and drive counts
	if req.SlotCount < 1 || req.SlotCount > 1000 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "slot_count must be between 1 and 1000"})
		return
	}
	if req.DriveCount < 1 || req.DriveCount > 8 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "drive_count must be between 1 and 8"})
		return
	}

	userID, _ := c.Get("user_id")

	lib, err := h.service.CreateLibrary(
		c.Request.Context(),
		req.Name,
		req.Description,
		req.BackingStorePath,
		req.SlotCount,
		req.DriveCount,
		userID.(string),
	)
	if err != nil {
		h.logger.Error("Failed to create library", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, lib)
}

// DeleteLibrary deletes a virtual tape library
func (h *Handler) DeleteLibrary(c *gin.Context) {
	libraryID := c.Param("id")

	if err := h.service.DeleteLibrary(c.Request.Context(), libraryID); err != nil {
		if err.Error() == "library not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "library not found"})
			return
		}
		h.logger.Error("Failed to delete library", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "library deleted successfully"})
}

// GetLibraryDrives lists drives for a library
func (h *Handler) GetLibraryDrives(c *gin.Context) {
	libraryID := c.Param("id")

	drives, err := h.service.GetLibraryDrives(c.Request.Context(), libraryID)
	if err != nil {
		h.logger.Error("Failed to get drives", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get drives"})
		return
	}

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

// GetLibraryTapes lists tapes for a library
func (h *Handler) GetLibraryTapes(c *gin.Context) {
	libraryID := c.Param("id")

	tapes, err := h.service.GetLibraryTapes(c.Request.Context(), libraryID)
	if err != nil {
		h.logger.Error("Failed to get tapes", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get tapes"})
		return
	}

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

// CreateTapeRequest represents a tape creation request
type CreateTapeRequest struct {
	Barcode    string `json:"barcode" binding:"required"`
	SlotNumber int    `json:"slot_number" binding:"required"`
	TapeType   string `json:"tape_type" binding:"required"`
	SizeGB     int64  `json:"size_gb" binding:"required"`
}

// CreateTape creates a new virtual tape
func (h *Handler) CreateTape(c *gin.Context) {
	libraryID := c.Param("id")

	var req CreateTapeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}

	sizeBytes := req.SizeGB * 1024 * 1024 * 1024

	tape, err := h.service.CreateTape(
		c.Request.Context(),
		libraryID,
		req.Barcode,
		req.SlotNumber,
		req.TapeType,
		sizeBytes,
	)
	if err != nil {
		h.logger.Error("Failed to create tape", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, tape)
}

// LoadTapeRequest represents a load tape request
type LoadTapeRequest struct {
	SlotNumber  int `json:"slot_number" binding:"required"`
	DriveNumber int `json:"drive_number" binding:"required"`
}

// LoadTape loads a tape from slot to drive
func (h *Handler) LoadTape(c *gin.Context) {
	libraryID := c.Param("id")

	var req LoadTapeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		h.logger.Warn("Invalid load tape request", "error", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request", "details": err.Error()})
		return
	}

	userID, _ := c.Get("user_id")

	// Create async task
	taskID, err := h.taskEngine.CreateTask(c.Request.Context(),
		tasks.TaskTypeLoadUnload, userID.(string), map[string]interface{}{
			"operation":    "load_tape",
			"library_id":   libraryID,
			"slot_number":  req.SlotNumber,
			"drive_number": req.DriveNumber,
		})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"})
		return
	}

	// Run load in background
	go func() {
		ctx := c.Request.Context()
		h.taskEngine.StartTask(ctx, taskID)
		h.taskEngine.UpdateProgress(ctx, taskID, 50, "Loading tape...")

		if err := h.service.LoadTape(ctx, libraryID, req.SlotNumber, req.DriveNumber); err != nil {
			h.taskEngine.FailTask(ctx, taskID, err.Error())
			return
		}

		h.taskEngine.UpdateProgress(ctx, taskID, 100, "Tape loaded")
		h.taskEngine.CompleteTask(ctx, taskID, "Tape loaded successfully")
	}()

	c.JSON(http.StatusAccepted, gin.H{"task_id": taskID})
}

// UnloadTapeRequest represents an unload tape request
type UnloadTapeRequest struct {
	DriveNumber int `json:"drive_number" binding:"required"`
	SlotNumber  int `json:"slot_number" binding:"required"`
}

// UnloadTape unloads a tape from drive to slot
func (h *Handler) UnloadTape(c *gin.Context) {
	libraryID := c.Param("id")

	var req UnloadTapeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		h.logger.Warn("Invalid unload tape request", "error", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request", "details": err.Error()})
		return
	}

	userID, _ := c.Get("user_id")

	// Create async task
	taskID, err := h.taskEngine.CreateTask(c.Request.Context(),
		tasks.TaskTypeLoadUnload, userID.(string), map[string]interface{}{
			"operation":    "unload_tape",
			"library_id":   libraryID,
			"slot_number":  req.SlotNumber,
			"drive_number": req.DriveNumber,
		})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"})
		return
	}

	// Run unload in background
	go func() {
		ctx := c.Request.Context()
		h.taskEngine.StartTask(ctx, taskID)
		h.taskEngine.UpdateProgress(ctx, taskID, 50, "Unloading tape...")

		if err := h.service.UnloadTape(ctx, libraryID, req.DriveNumber, req.SlotNumber); err != nil {
			h.taskEngine.FailTask(ctx, taskID, err.Error())
			return
		}

		h.taskEngine.UpdateProgress(ctx, taskID, 100, "Tape unloaded")
		h.taskEngine.CompleteTask(ctx, taskID, "Tape unloaded successfully")
	}()

	c.JSON(http.StatusAccepted, gin.H{"task_id": taskID})
}
