package tape_physical

import (
	"context"
	"database/sql"
	"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 physical tape library API requests
type Handler struct {
	service    *Service
	taskEngine *tasks.Engine
	db         *database.DB
	logger     *logger.Logger
}

// NewHandler creates a new physical tape 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 physical tape libraries
func (h *Handler) ListLibraries(c *gin.Context) {
	query := `
		SELECT id, name, serial_number, vendor, model,
		       changer_device_path, changer_stable_path,
		       slot_count, drive_count, is_active,
		       discovered_at, last_inventory_at, created_at, updated_at
		FROM physical_tape_libraries
		ORDER BY name
	`

	rows, err := h.db.QueryContext(c.Request.Context(), query)
	if err != nil {
		h.logger.Error("Failed to list libraries", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list libraries"})
		return
	}
	defer rows.Close()

	var libraries []TapeLibrary
	for rows.Next() {
		var lib TapeLibrary
		var lastInventory sql.NullTime
		err := rows.Scan(
			&lib.ID, &lib.Name, &lib.SerialNumber, &lib.Vendor, &lib.Model,
			&lib.ChangerDevicePath, &lib.ChangerStablePath,
			&lib.SlotCount, &lib.DriveCount, &lib.IsActive,
			&lib.DiscoveredAt, &lastInventory, &lib.CreatedAt, &lib.UpdatedAt,
		)
		if err != nil {
			h.logger.Error("Failed to scan library", "error", err)
			continue
		}
		if lastInventory.Valid {
			lib.LastInventoryAt = &lastInventory.Time
		}
		libraries = append(libraries, lib)
	}

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

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

	query := `
		SELECT id, name, serial_number, vendor, model,
		       changer_device_path, changer_stable_path,
		       slot_count, drive_count, is_active,
		       discovered_at, last_inventory_at, created_at, updated_at
		FROM physical_tape_libraries
		WHERE id = $1
	`

	var lib TapeLibrary
	var lastInventory sql.NullTime
	err := h.db.QueryRowContext(c.Request.Context(), query, libraryID).Scan(
		&lib.ID, &lib.Name, &lib.SerialNumber, &lib.Vendor, &lib.Model,
		&lib.ChangerDevicePath, &lib.ChangerStablePath,
		&lib.SlotCount, &lib.DriveCount, &lib.IsActive,
		&lib.DiscoveredAt, &lastInventory, &lib.CreatedAt, &lib.UpdatedAt,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			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
	}

	if lastInventory.Valid {
		lib.LastInventoryAt = &lastInventory.Time
	}

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

	// Get slots
	slots, _ := h.GetLibrarySlots(c, libraryID)

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

// DiscoverLibraries discovers physical tape libraries (async)
func (h *Handler) DiscoverLibraries(c *gin.Context) {
	userID, _ := c.Get("user_id")

	// Create async task
	taskID, err := h.taskEngine.CreateTask(c.Request.Context(),
		tasks.TaskTypeRescan, userID.(string), map[string]interface{}{
			"operation": "discover_tape_libraries",
		})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"})
		return
	}

	// Run discovery in background
	go func() {
		ctx := c.Request.Context()
		h.taskEngine.StartTask(ctx, taskID)
		h.taskEngine.UpdateProgress(ctx, taskID, 30, "Discovering tape libraries...")

		libraries, err := h.service.DiscoverLibraries(ctx)
		if err != nil {
			h.taskEngine.FailTask(ctx, taskID, err.Error())
			return
		}

		h.taskEngine.UpdateProgress(ctx, taskID, 60, "Syncing libraries to database...")

		// Sync each library to database
		for _, lib := range libraries {
			if err := h.service.SyncLibraryToDatabase(ctx, &lib); err != nil {
				h.logger.Warn("Failed to sync library", "library", lib.Name, "error", err)
				continue
			}

			// Discover drives for this library
			if lib.ChangerDevicePath != "" {
				drives, err := h.service.DiscoverDrives(ctx, lib.ID, lib.ChangerDevicePath)
				if err == nil {
					// Sync drives to database
					for _, drive := range drives {
						h.syncDriveToDatabase(ctx, &drive)
					}
				}
			}
		}

		h.taskEngine.UpdateProgress(ctx, taskID, 100, "Discovery completed")
		h.taskEngine.CompleteTask(ctx, taskID, "Tape libraries discovered successfully")
	}()

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

// GetLibraryDrives lists drives for a library
func (h *Handler) GetLibraryDrives(c *gin.Context, libraryID string) ([]TapeDrive, error) {
	query := `
		SELECT id, library_id, drive_number, device_path, stable_path,
		       vendor, model, serial_number, drive_type, status,
		       current_tape_barcode, is_active, created_at, updated_at
		FROM physical_tape_drives
		WHERE library_id = $1
		ORDER BY drive_number
	`

	rows, err := h.db.QueryContext(c.Request.Context(), query, libraryID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var drives []TapeDrive
	for rows.Next() {
		var drive TapeDrive
		var barcode sql.NullString
		err := rows.Scan(
			&drive.ID, &drive.LibraryID, &drive.DriveNumber, &drive.DevicePath, &drive.StablePath,
			&drive.Vendor, &drive.Model, &drive.SerialNumber, &drive.DriveType, &drive.Status,
			&barcode, &drive.IsActive, &drive.CreatedAt, &drive.UpdatedAt,
		)
		if err != nil {
			h.logger.Error("Failed to scan drive", "error", err)
			continue
		}
		if barcode.Valid {
			drive.CurrentTapeBarcode = barcode.String
		}
		drives = append(drives, drive)
	}

	return drives, rows.Err()
}

// GetLibrarySlots lists slots for a library
func (h *Handler) GetLibrarySlots(c *gin.Context, libraryID string) ([]TapeSlot, error) {
	query := `
		SELECT id, library_id, slot_number, barcode, tape_present,
		       tape_type, last_updated_at
		FROM physical_tape_slots
		WHERE library_id = $1
		ORDER BY slot_number
	`

	rows, err := h.db.QueryContext(c.Request.Context(), query, libraryID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var slots []TapeSlot
	for rows.Next() {
		var slot TapeSlot
		err := rows.Scan(
			&slot.ID, &slot.LibraryID, &slot.SlotNumber, &slot.Barcode,
			&slot.TapePresent, &slot.TapeType, &slot.LastUpdatedAt,
		)
		if err != nil {
			h.logger.Error("Failed to scan slot", "error", err)
			continue
		}
		slots = append(slots, slot)
	}

	return slots, rows.Err()
}

// PerformInventory performs inventory of a library (async)
func (h *Handler) PerformInventory(c *gin.Context) {
	libraryID := c.Param("id")

	// Get library
	var changerPath string
	err := h.db.QueryRowContext(c.Request.Context(),
		"SELECT changer_device_path FROM physical_tape_libraries WHERE id = $1",
		libraryID,
	).Scan(&changerPath)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "library not found"})
		return
	}

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

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

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

		slots, err := h.service.PerformInventory(ctx, libraryID, changerPath)
		if err != nil {
			h.taskEngine.FailTask(ctx, taskID, err.Error())
			return
		}

		// Sync slots to database
		for _, slot := range slots {
			h.syncSlotToDatabase(ctx, &slot)
		}

		// Update last inventory time
		h.db.ExecContext(ctx,
			"UPDATE physical_tape_libraries SET last_inventory_at = NOW() WHERE id = $1",
			libraryID,
		)

		h.taskEngine.UpdateProgress(ctx, taskID, 100, "Inventory completed")
		h.taskEngine.CompleteTask(ctx, taskID, fmt.Sprintf("Inventory completed: %d slots", len(slots)))
	}()

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

// 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 (async)
func (h *Handler) LoadTape(c *gin.Context) {
	libraryID := c.Param("id")

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

	// Get library
	var changerPath string
	err := h.db.QueryRowContext(c.Request.Context(),
		"SELECT changer_device_path FROM physical_tape_libraries WHERE id = $1",
		libraryID,
	).Scan(&changerPath)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "library not found"})
		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, changerPath, req.SlotNumber, req.DriveNumber); err != nil {
			h.taskEngine.FailTask(ctx, taskID, err.Error())
			return
		}

		// Update drive status
		h.db.ExecContext(ctx,
			"UPDATE physical_tape_drives SET status = 'ready', updated_at = NOW() WHERE library_id = $1 AND drive_number = $2",
			libraryID, req.DriveNumber,
		)

		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 (async)
func (h *Handler) UnloadTape(c *gin.Context) {
	libraryID := c.Param("id")

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

	// Get library
	var changerPath string
	err := h.db.QueryRowContext(c.Request.Context(),
		"SELECT changer_device_path FROM physical_tape_libraries WHERE id = $1",
		libraryID,
	).Scan(&changerPath)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "library not found"})
		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, changerPath, req.DriveNumber, req.SlotNumber); err != nil {
			h.taskEngine.FailTask(ctx, taskID, err.Error())
			return
		}

		// Update drive status
		h.db.ExecContext(ctx,
			"UPDATE physical_tape_drives SET status = 'idle', current_tape_barcode = NULL, updated_at = NOW() WHERE library_id = $1 AND drive_number = $2",
			libraryID, req.DriveNumber,
		)

		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})
}

// syncDriveToDatabase syncs a drive to the database
func (h *Handler) syncDriveToDatabase(ctx context.Context, drive *TapeDrive) {
	query := `
		INSERT INTO physical_tape_drives (
			library_id, drive_number, device_path, stable_path,
			vendor, model, serial_number, drive_type, status, is_active
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
		ON CONFLICT (library_id, drive_number) DO UPDATE SET
			device_path = EXCLUDED.device_path,
			stable_path = EXCLUDED.stable_path,
			vendor = EXCLUDED.vendor,
			model = EXCLUDED.model,
			serial_number = EXCLUDED.serial_number,
			drive_type = EXCLUDED.drive_type,
			updated_at = NOW()
	`
	h.db.ExecContext(ctx, query,
		drive.LibraryID, drive.DriveNumber, drive.DevicePath, drive.StablePath,
		drive.Vendor, drive.Model, drive.SerialNumber, drive.DriveType, drive.Status, drive.IsActive,
	)
}

// syncSlotToDatabase syncs a slot to the database
func (h *Handler) syncSlotToDatabase(ctx context.Context, slot *TapeSlot) {
	query := `
		INSERT INTO physical_tape_slots (
			library_id, slot_number, barcode, tape_present, tape_type, last_updated_at
		) VALUES ($1, $2, $3, $4, $5, $6)
		ON CONFLICT (library_id, slot_number) DO UPDATE SET
			barcode = EXCLUDED.barcode,
			tape_present = EXCLUDED.tape_present,
			tape_type = EXCLUDED.tape_type,
			last_updated_at = EXCLUDED.last_updated_at
	`
	h.db.ExecContext(ctx, query,
		slot.LibraryID, slot.SlotNumber, slot.Barcode, slot.TapePresent, slot.TapeType, slot.LastUpdatedAt,
	)
}

