package tape_vtl

import (
	"context"
	"database/sql"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/atlasos/calypso/internal/common/database"
	"github.com/atlasos/calypso/internal/common/logger"
)

// Service handles virtual tape library (MHVTL) operations
type Service struct {
	db     *database.DB
	logger *logger.Logger
}

// NewService creates a new VTL service
func NewService(db *database.DB, log *logger.Logger) *Service {
	return &Service{
		db:     db,
		logger: log,
	}
}

// VirtualTapeLibrary represents a virtual tape library
type VirtualTapeLibrary struct {
	ID               string    `json:"id"`
	Name             string    `json:"name"`
	Description      string    `json:"description"`
	MHVTLibraryID    int       `json:"mhvtl_library_id"`
	BackingStorePath string    `json:"backing_store_path"`
	Vendor           string    `json:"vendor,omitempty"`
	SlotCount        int       `json:"slot_count"`
	DriveCount       int       `json:"drive_count"`
	IsActive         bool      `json:"is_active"`
	CreatedAt        time.Time `json:"created_at"`
	UpdatedAt        time.Time `json:"updated_at"`
	CreatedBy        string    `json:"created_by"`
}

// VirtualTapeDrive represents a virtual tape drive
type VirtualTapeDrive struct {
	ID            string    `json:"id"`
	LibraryID     string    `json:"library_id"`
	DriveNumber   int       `json:"drive_number"`
	DevicePath    *string   `json:"device_path,omitempty"`
	StablePath    *string   `json:"stable_path,omitempty"`
	Status        string    `json:"status"`
	CurrentTapeID string    `json:"current_tape_id,omitempty"`
	IsActive      bool      `json:"is_active"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
}

// VirtualTape represents a virtual tape
type VirtualTape struct {
	ID            string    `json:"id"`
	LibraryID     string    `json:"library_id"`
	Barcode       string    `json:"barcode"`
	SlotNumber    int       `json:"slot_number"`
	ImageFilePath string    `json:"image_file_path"`
	SizeBytes     int64     `json:"size_bytes"`
	UsedBytes     int64     `json:"used_bytes"`
	TapeType      string    `json:"tape_type"`
	Status        string    `json:"status"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
}

// CreateLibrary creates a new virtual tape library
func (s *Service) CreateLibrary(ctx context.Context, name, description, backingStorePath string, slotCount, driveCount int, createdBy string) (*VirtualTapeLibrary, error) {
	// Ensure backing store directory exists
	fullPath := filepath.Join(backingStorePath, name)
	if err := os.MkdirAll(fullPath, 0755); err != nil {
		return nil, fmt.Errorf("failed to create backing store directory: %w", err)
	}

	// Create tapes directory
	tapesPath := filepath.Join(fullPath, "tapes")
	if err := os.MkdirAll(tapesPath, 0755); err != nil {
		return nil, fmt.Errorf("failed to create tapes directory: %w", err)
	}

	// Generate MHVTL library ID (use next available ID)
	mhvtlID, err := s.getNextMHVTLID(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get next MHVTL ID: %w", err)
	}

	// Insert into database
	query := `
		INSERT INTO virtual_tape_libraries (
			name, description, mhvtl_library_id, backing_store_path,
			slot_count, drive_count, is_active, created_by
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id, created_at, updated_at
	`

	var lib VirtualTapeLibrary
	err = s.db.QueryRowContext(ctx, query,
		name, description, mhvtlID, fullPath,
		slotCount, driveCount, true, createdBy,
	).Scan(&lib.ID, &lib.CreatedAt, &lib.UpdatedAt)
	if err != nil {
		return nil, fmt.Errorf("failed to save library to database: %w", err)
	}

	lib.Name = name
	lib.Description = description
	lib.MHVTLibraryID = mhvtlID
	lib.BackingStorePath = fullPath
	lib.SlotCount = slotCount
	lib.DriveCount = driveCount
	lib.IsActive = true
	lib.CreatedBy = createdBy

	// Create virtual drives
	for i := 1; i <= driveCount; i++ {
		drive := VirtualTapeDrive{
			LibraryID:   lib.ID,
			DriveNumber: i,
			Status:      "idle",
			IsActive:    true,
		}
		if err := s.createDrive(ctx, &drive); err != nil {
			s.logger.Error("Failed to create drive", "drive_number", i, "error", err)
			// Continue creating other drives even if one fails
		}
	}

	// Create initial tapes in slots
	for i := 1; i <= slotCount; i++ {
		barcode := fmt.Sprintf("V%05d", i)
		tape := VirtualTape{
			LibraryID:     lib.ID,
			Barcode:       barcode,
			SlotNumber:    i,
			ImageFilePath: filepath.Join(tapesPath, fmt.Sprintf("%s.img", barcode)),
			SizeBytes:     800 * 1024 * 1024 * 1024, // 800 GB default (LTO-8)
			UsedBytes:     0,
			TapeType:      "LTO-8",
			Status:        "idle",
		}
		if err := s.createTape(ctx, &tape); err != nil {
			s.logger.Error("Failed to create tape", "slot", i, "error", err)
			// Continue creating other tapes even if one fails
		}
	}

	s.logger.Info("Virtual tape library created", "name", name, "id", lib.ID)
	return &lib, nil
}

// getNextMHVTLID gets the next available MHVTL library ID
func (s *Service) getNextMHVTLID(ctx context.Context) (int, error) {
	var maxID sql.NullInt64
	err := s.db.QueryRowContext(ctx,
		"SELECT MAX(mhvtl_library_id) FROM virtual_tape_libraries",
	).Scan(&maxID)

	if err != nil && err != sql.ErrNoRows {
		return 0, err
	}

	if maxID.Valid {
		return int(maxID.Int64) + 1, nil
	}

	return 1, nil
}

// createDrive creates a virtual tape drive
func (s *Service) createDrive(ctx context.Context, drive *VirtualTapeDrive) error {
	query := `
		INSERT INTO virtual_tape_drives (
			library_id, drive_number, status, is_active
		) VALUES ($1, $2, $3, $4)
		RETURNING id, created_at, updated_at
	`

	err := s.db.QueryRowContext(ctx, query,
		drive.LibraryID, drive.DriveNumber, drive.Status, drive.IsActive,
	).Scan(&drive.ID, &drive.CreatedAt, &drive.UpdatedAt)
	if err != nil {
		return fmt.Errorf("failed to create drive: %w", err)
	}

	return nil
}

// createTape creates a virtual tape
func (s *Service) createTape(ctx context.Context, tape *VirtualTape) error {
	// Create empty tape image file
	file, err := os.Create(tape.ImageFilePath)
	if err != nil {
		return fmt.Errorf("failed to create tape image: %w", err)
	}
	file.Close()

	query := `
		INSERT INTO virtual_tapes (
			library_id, barcode, slot_number, image_file_path,
			size_bytes, used_bytes, tape_type, status
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id, created_at, updated_at
	`

	err = s.db.QueryRowContext(ctx, query,
		tape.LibraryID, tape.Barcode, tape.SlotNumber, tape.ImageFilePath,
		tape.SizeBytes, tape.UsedBytes, tape.TapeType, tape.Status,
	).Scan(&tape.ID, &tape.CreatedAt, &tape.UpdatedAt)
	if err != nil {
		return fmt.Errorf("failed to create tape: %w", err)
	}

	return nil
}

// ListLibraries lists all virtual tape libraries
func (s *Service) ListLibraries(ctx context.Context) ([]VirtualTapeLibrary, error) {
	query := `
		SELECT id, name, description, mhvtl_library_id, backing_store_path,
		       COALESCE(vendor, '') as vendor,
		       slot_count, drive_count, is_active, created_at, updated_at, created_by
		FROM virtual_tape_libraries
		ORDER BY name
	`

	s.logger.Info("Executing query to list libraries")
	rows, err := s.db.QueryContext(ctx, query)
	if err != nil {
		s.logger.Error("Failed to query libraries", "error", err)
		return nil, fmt.Errorf("failed to list libraries: %w", err)
	}
	s.logger.Info("Query executed successfully, got rows")
	defer rows.Close()

	libraries := make([]VirtualTapeLibrary, 0) // Initialize as empty slice, not nil
	s.logger.Info("Starting to scan library rows", "query", query)
	rowCount := 0
	for rows.Next() {
		rowCount++
		var lib VirtualTapeLibrary
		var description sql.NullString
		var createdBy sql.NullString
		err := rows.Scan(
			&lib.ID, &lib.Name, &description, &lib.MHVTLibraryID, &lib.BackingStorePath,
			&lib.Vendor,
			&lib.SlotCount, &lib.DriveCount, &lib.IsActive,
			&lib.CreatedAt, &lib.UpdatedAt, &createdBy,
		)
		if err != nil {
			s.logger.Error("Failed to scan library", "error", err, "row", rowCount)
			continue
		}
		if description.Valid {
			lib.Description = description.String
		}
		if createdBy.Valid {
			lib.CreatedBy = createdBy.String
		}
		libraries = append(libraries, lib)
		s.logger.Info("Added library to list", "library_id", lib.ID, "name", lib.Name, "mhvtl_id", lib.MHVTLibraryID)
	}
	s.logger.Info("Finished scanning library rows", "total_rows", rowCount, "libraries_added", len(libraries))

	if err := rows.Err(); err != nil {
		s.logger.Error("Error iterating library rows", "error", err)
		return nil, fmt.Errorf("error iterating library rows: %w", err)
	}

	s.logger.Info("Listed virtual tape libraries", "count", len(libraries), "is_nil", libraries == nil)
	// Ensure we return an empty slice, not nil
	if libraries == nil {
		s.logger.Warn("Libraries is nil in service, converting to empty array")
		libraries = []VirtualTapeLibrary{}
	}
	s.logger.Info("Returning from service", "count", len(libraries), "is_nil", libraries == nil)
	return libraries, nil
}

// GetLibrary retrieves a library by ID
func (s *Service) GetLibrary(ctx context.Context, id string) (*VirtualTapeLibrary, error) {
	query := `
		SELECT id, name, description, mhvtl_library_id, backing_store_path,
		       COALESCE(vendor, '') as vendor,
		       slot_count, drive_count, is_active, created_at, updated_at, created_by
		FROM virtual_tape_libraries
		WHERE id = $1
	`

	var lib VirtualTapeLibrary
	var description sql.NullString
	var createdBy sql.NullString
	err := s.db.QueryRowContext(ctx, query, id).Scan(
		&lib.ID, &lib.Name, &description, &lib.MHVTLibraryID, &lib.BackingStorePath,
		&lib.Vendor,
		&lib.SlotCount, &lib.DriveCount, &lib.IsActive,
		&lib.CreatedAt, &lib.UpdatedAt, &createdBy,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("library not found")
		}
		return nil, fmt.Errorf("failed to get library: %w", err)
	}

	if description.Valid {
		lib.Description = description.String
	}
	if createdBy.Valid {
		lib.CreatedBy = createdBy.String
	}

	return &lib, nil
}

// GetLibraryDrives retrieves drives for a library
func (s *Service) GetLibraryDrives(ctx context.Context, libraryID string) ([]VirtualTapeDrive, error) {
	query := `
		SELECT id, library_id, drive_number, device_path, stable_path,
		       status, current_tape_id, is_active, created_at, updated_at
		FROM virtual_tape_drives
		WHERE library_id = $1
		ORDER BY drive_number
	`

	rows, err := s.db.QueryContext(ctx, query, libraryID)
	if err != nil {
		return nil, fmt.Errorf("failed to get drives: %w", err)
	}
	defer rows.Close()

	var drives []VirtualTapeDrive
	for rows.Next() {
		var drive VirtualTapeDrive
		var tapeID, devicePath, stablePath sql.NullString
		err := rows.Scan(
			&drive.ID, &drive.LibraryID, &drive.DriveNumber,
			&devicePath, &stablePath,
			&drive.Status, &tapeID, &drive.IsActive,
			&drive.CreatedAt, &drive.UpdatedAt,
		)
		if err != nil {
			s.logger.Error("Failed to scan drive", "error", err)
			continue
		}
		if devicePath.Valid {
			drive.DevicePath = &devicePath.String
		}
		if stablePath.Valid {
			drive.StablePath = &stablePath.String
		}
		if tapeID.Valid {
			drive.CurrentTapeID = tapeID.String
		}
		drives = append(drives, drive)
	}

	return drives, rows.Err()
}

// GetLibraryTapes retrieves tapes for a library
func (s *Service) GetLibraryTapes(ctx context.Context, libraryID string) ([]VirtualTape, error) {
	query := `
		SELECT id, library_id, barcode, slot_number, image_file_path,
		       size_bytes, used_bytes, tape_type, status, created_at, updated_at
		FROM virtual_tapes
		WHERE library_id = $1
		ORDER BY slot_number
	`

	rows, err := s.db.QueryContext(ctx, query, libraryID)
	if err != nil {
		return nil, fmt.Errorf("failed to get tapes: %w", err)
	}
	defer rows.Close()

	var tapes []VirtualTape
	for rows.Next() {
		var tape VirtualTape
		err := rows.Scan(
			&tape.ID, &tape.LibraryID, &tape.Barcode, &tape.SlotNumber,
			&tape.ImageFilePath, &tape.SizeBytes, &tape.UsedBytes,
			&tape.TapeType, &tape.Status, &tape.CreatedAt, &tape.UpdatedAt,
		)
		if err != nil {
			s.logger.Error("Failed to scan tape", "error", err)
			continue
		}
		tapes = append(tapes, tape)
	}

	return tapes, rows.Err()
}

// CreateTape creates a new virtual tape
func (s *Service) CreateTape(ctx context.Context, libraryID, barcode string, slotNumber int, tapeType string, sizeBytes int64) (*VirtualTape, error) {
	// Get library to find backing store path
	lib, err := s.GetLibrary(ctx, libraryID)
	if err != nil {
		return nil, err
	}

	// Create tape image file
	tapesPath := filepath.Join(lib.BackingStorePath, "tapes")
	imagePath := filepath.Join(tapesPath, fmt.Sprintf("%s.img", barcode))

	file, err := os.Create(imagePath)
	if err != nil {
		return nil, fmt.Errorf("failed to create tape image: %w", err)
	}
	file.Close()

	tape := VirtualTape{
		LibraryID:     libraryID,
		Barcode:       barcode,
		SlotNumber:    slotNumber,
		ImageFilePath: imagePath,
		SizeBytes:     sizeBytes,
		UsedBytes:     0,
		TapeType:      tapeType,
		Status:        "idle",
	}

	return s.createTapeRecord(ctx, &tape)
}

// createTapeRecord creates a tape record in the database
func (s *Service) createTapeRecord(ctx context.Context, tape *VirtualTape) (*VirtualTape, error) {
	query := `
		INSERT INTO virtual_tapes (
			library_id, barcode, slot_number, image_file_path,
			size_bytes, used_bytes, tape_type, status
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id, created_at, updated_at
	`

	err := s.db.QueryRowContext(ctx, query,
		tape.LibraryID, tape.Barcode, tape.SlotNumber, tape.ImageFilePath,
		tape.SizeBytes, tape.UsedBytes, tape.TapeType, tape.Status,
	).Scan(&tape.ID, &tape.CreatedAt, &tape.UpdatedAt)
	if err != nil {
		return nil, fmt.Errorf("failed to create tape record: %w", err)
	}

	return tape, nil
}

// LoadTape loads a tape from slot to drive
func (s *Service) LoadTape(ctx context.Context, libraryID string, slotNumber, driveNumber int) error {
	// Get tape from slot
	var tapeID, barcode string
	err := s.db.QueryRowContext(ctx,
		"SELECT id, barcode FROM virtual_tapes WHERE library_id = $1 AND slot_number = $2",
		libraryID, slotNumber,
	).Scan(&tapeID, &barcode)
	if err != nil {
		return fmt.Errorf("tape not found in slot: %w", err)
	}

	// Update tape status
	_, err = s.db.ExecContext(ctx,
		"UPDATE virtual_tapes SET status = 'in_drive', updated_at = NOW() WHERE id = $1",
		tapeID,
	)
	if err != nil {
		return fmt.Errorf("failed to update tape status: %w", err)
	}

	// Update drive status
	_, err = s.db.ExecContext(ctx,
		"UPDATE virtual_tape_drives SET status = 'ready', current_tape_id = $1, updated_at = NOW() WHERE library_id = $2 AND drive_number = $3",
		tapeID, libraryID, driveNumber,
	)
	if err != nil {
		return fmt.Errorf("failed to update drive status: %w", err)
	}

	s.logger.Info("Virtual tape loaded", "library_id", libraryID, "slot", slotNumber, "drive", driveNumber, "barcode", barcode)
	return nil
}

// UnloadTape unloads a tape from drive to slot
func (s *Service) UnloadTape(ctx context.Context, libraryID string, driveNumber, slotNumber int) error {
	// Get current tape in drive
	var tapeID string
	err := s.db.QueryRowContext(ctx,
		"SELECT current_tape_id FROM virtual_tape_drives WHERE library_id = $1 AND drive_number = $2",
		libraryID, driveNumber,
	).Scan(&tapeID)
	if err != nil {
		return fmt.Errorf("no tape in drive: %w", err)
	}

	// Update tape status and slot
	_, err = s.db.ExecContext(ctx,
		"UPDATE virtual_tapes SET status = 'idle', slot_number = $1, updated_at = NOW() WHERE id = $2",
		slotNumber, tapeID,
	)
	if err != nil {
		return fmt.Errorf("failed to update tape: %w", err)
	}

	// Update drive status
	_, err = s.db.ExecContext(ctx,
		"UPDATE virtual_tape_drives SET status = 'idle', current_tape_id = NULL, updated_at = NOW() WHERE library_id = $1 AND drive_number = $2",
		libraryID, driveNumber,
	)
	if err != nil {
		return fmt.Errorf("failed to update drive: %w", err)
	}

	s.logger.Info("Virtual tape unloaded", "library_id", libraryID, "drive", driveNumber, "slot", slotNumber)
	return nil
}

// DeleteLibrary deletes a virtual tape library
func (s *Service) DeleteLibrary(ctx context.Context, id string) error {
	lib, err := s.GetLibrary(ctx, id)
	if err != nil {
		return err
	}

	if lib.IsActive {
		return fmt.Errorf("cannot delete active library")
	}

	// Delete from database (cascade will handle drives and tapes)
	_, err = s.db.ExecContext(ctx, "DELETE FROM virtual_tape_libraries WHERE id = $1", id)
	if err != nil {
		return fmt.Errorf("failed to delete library: %w", err)
	}

	// Optionally remove backing store (commented out for safety)
	// os.RemoveAll(lib.BackingStorePath)

	s.logger.Info("Virtual tape library deleted", "id", id, "name", lib.Name)
	return nil
}
