package scst

import (
	"context"
	"database/sql"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"

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

// Service handles SCST operations
type Service struct {
	db     *database.DB
	logger *logger.Logger
}

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

// Target represents an SCST iSCSI target
type Target struct {
	ID                  string    `json:"id"`
	IQN                 string    `json:"iqn"`
	Alias               string    `json:"alias,omitempty"` // Alias for frontend compatibility (uses name)
	TargetType          string    `json:"target_type"`     // 'disk', 'vtl', 'physical_tape'
	Name                string    `json:"name"`
	Description         string    `json:"description"`
	IsActive            bool      `json:"is_active"`
	SingleInitiatorOnly bool      `json:"single_initiator_only"`
	LUNCount            int       `json:"lun_count"` // Number of LUNs attached to this target
	CreatedAt           time.Time `json:"created_at"`
	UpdatedAt           time.Time `json:"updated_at"`
	CreatedBy           string    `json:"created_by"`
}

// LUN represents an SCST LUN mapping
type LUN struct {
	ID          string    `json:"id"`
	TargetID    string    `json:"target_id"`
	LUNNumber   int       `json:"lun_number"`
	DeviceName  string    `json:"device_name"`
	DevicePath  string    `json:"device_path"`
	HandlerType string    `json:"handler_type"`
	Handler     string    `json:"handler"`     // Alias for handler_type for frontend compatibility
	DeviceType  string    `json:"device_type"` // Derived from handler_type
	IsActive    bool      `json:"is_active"`   // True if LUN exists in SCST
	CreatedAt   time.Time `json:"created_at"`
}

// InitiatorGroup represents an SCST initiator group
type InitiatorGroup struct {
	ID         string      `json:"id"`
	TargetID   string      `json:"target_id"`
	GroupName  string      `json:"group_name"`
	Initiators []Initiator `json:"initiators"`
	CreatedAt  time.Time   `json:"created_at"`
}

// Initiator represents an iSCSI initiator
type Initiator struct {
	ID        string    `json:"id"`
	GroupID   string    `json:"group_id"`
	IQN       string    `json:"iqn"`
	IsActive  bool      `json:"is_active"`
	CreatedAt time.Time `json:"created_at"`
}

// Portal represents an iSCSI network portal (IP address and port)
type Portal struct {
	ID        string    `json:"id"`
	IPAddress string    `json:"ip_address"` // IPv4 or IPv6 address, or "0.0.0.0" for all interfaces
	Port      int       `json:"port"`       // Default 3260 for iSCSI
	IsActive  bool      `json:"is_active"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

// Extent represents an SCST device extent (opened device)
type Extent struct {
	HandlerType string `json:"handler_type"`
	DeviceName  string `json:"device_name"`
	DevicePath  string `json:"device_path"` // Path to the actual device/file
	IsInUse     bool   `json:"is_in_use"`   // True if device is used by any LUN
	LUNCount    int    `json:"lun_count"`   // Number of LUNs using this device
}

// CreateTarget creates a new SCST iSCSI target
func (s *Service) CreateTarget(ctx context.Context, target *Target) error {
	// Validate IQN format
	if !strings.HasPrefix(target.IQN, "iqn.") {
		return fmt.Errorf("invalid IQN format")
	}

	// Create target in SCST
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-add_target", target.IQN, "-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	outputStr := string(output)

	if err != nil {
		// Check if target already exists
		if strings.Contains(outputStr, "already exists") {
			s.logger.Warn("Target already exists in SCST", "iqn", target.IQN)
		} else {
			// Check for common SCST errors
			if strings.Contains(outputStr, "User space process is not connected") ||
				strings.Contains(outputStr, "iscsi-scstd") {
				return fmt.Errorf("iSCSI daemon (iscsi-scstd) is not running. Please start it with: systemctl start iscsi-scstd")
			}
			if strings.Contains(outputStr, "Failed to add target") {
				return fmt.Errorf("failed to add target to SCST: %s. Check dmesg for details", strings.TrimSpace(outputStr))
			}
			return fmt.Errorf("failed to create SCST target: %s: %w", outputStr, err)
		}
	}

	// Check for warnings in output even if command succeeded
	if strings.Contains(outputStr, "WARNING") || strings.Contains(outputStr, "Failed to add target") {
		s.logger.Warn("SCST warning during target creation", "iqn", target.IQN, "output", outputStr)
		// Check dmesg for more details
		dmesgCmd := exec.CommandContext(ctx, "dmesg", "-T", "-l", "err,warn")
		if dmesgOutput, dmesgErr := dmesgCmd.CombinedOutput(); dmesgErr == nil {
			recentErrors := string(dmesgOutput)
			if strings.Contains(recentErrors, "iscsi-scst") {
				return fmt.Errorf("iSCSI daemon (iscsi-scstd) is not running. Please start it with: systemctl start iscsi-scstd")
			}
		}
		return fmt.Errorf("target creation completed but SCST reported an error: %s. Check dmesg for details", strings.TrimSpace(outputStr))
	}

	// Insert into database
	query := `
		INSERT INTO scst_targets (
			iqn, target_type, name, description, is_active,
			single_initiator_only, created_by
		) VALUES ($1, $2, $3, $4, $5, $6, $7)
		RETURNING id, created_at, updated_at
	`

	err = s.db.QueryRowContext(ctx, query,
		target.IQN, target.TargetType, target.Name, target.Description,
		target.IsActive, target.SingleInitiatorOnly, target.CreatedBy,
	).Scan(&target.ID, &target.CreatedAt, &target.UpdatedAt)
	if err != nil {
		// Rollback: remove from SCST
		exec.CommandContext(ctx, "sudo", "scstadmin", "-remove_target", target.IQN, "-driver", "iscsi").Run()
		return fmt.Errorf("failed to save target to database: %w", err)
	}

	s.logger.Info("SCST target created", "iqn", target.IQN, "type", target.TargetType)

	// Apply active portals to the new target
	if err := s.applyPortalsToTarget(ctx, target.IQN); err != nil {
		s.logger.Warn("Failed to apply portals to new target", "iqn", target.IQN, "error", err)
		// Don't fail target creation if portal application fails
	}

	return nil
}

// AddLUN assigns an extent (device) to initiator groups as a LUN
// Note: The device/extent should already be created and opened in SCST
// This function only assigns the LUN to initiator groups, not to the target itself
func (s *Service) AddLUN(ctx context.Context, targetIQN, deviceName, devicePath string, lunNumber int, handlerType string) error {
	// Get target ID
	var targetID string
	err := s.db.QueryRowContext(ctx, "SELECT id FROM scst_targets WHERE iqn = $1", targetIQN).Scan(&targetID)
	if err != nil {
		return fmt.Errorf("failed to get target ID: %w", err)
	}

	// Get all initiator groups for this target
	groups, err := s.GetTargetInitiatorGroups(ctx, targetID)
	if err != nil {
		return fmt.Errorf("failed to get initiator groups: %w", err)
	}

	if len(groups) == 0 {
		return fmt.Errorf("no initiator groups found for target %s. Please create an initiator group first", targetIQN)
	}

	// Assign LUN to each group
	// Command format: scstadmin -add_lun <lun_number> -driver iscsi -target <target_iqn> -group <group_name> -device <device_name>
	for _, group := range groups {
		assignCmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-add_lun", fmt.Sprintf("%d", lunNumber),
			"-driver", "iscsi",
			"-target", targetIQN,
			"-group", group.GroupName,
			"-device", deviceName)
		output, err := assignCmd.CombinedOutput()
		outputStr := string(output)

		// Log the command output for debugging
		s.logger.Info("Assigning LUN to initiator group",
			"target", targetIQN,
			"group", group.GroupName,
			"lun", lunNumber,
			"device", deviceName,
			"command", fmt.Sprintf("scstadmin -add_lun %d -driver iscsi -target %s -group %s -device %s", lunNumber, targetIQN, group.GroupName, deviceName),
			"output", outputStr)

		if err != nil {
			return fmt.Errorf("failed to assign LUN to initiator group %s: %s: %w", group.GroupName, outputStr, err)
		}

		s.logger.Info("LUN assigned to initiator group",
			"target", targetIQN,
			"group", group.GroupName,
			"lun", lunNumber,
			"device", deviceName)
	}

	// Insert into database
	_, err = s.db.ExecContext(ctx, `
		INSERT INTO scst_luns (target_id, lun_number, device_name, device_path, handler_type)
		VALUES ($1, $2, $3, $4, $5)
		ON CONFLICT (target_id, lun_number) DO UPDATE SET
			device_name = EXCLUDED.device_name,
			device_path = EXCLUDED.device_path,
			handler_type = EXCLUDED.handler_type
	`, targetID, lunNumber, deviceName, devicePath, handlerType)
	if err != nil {
		return fmt.Errorf("failed to save LUN to database: %w", err)
	}

	// Write config to persist changes
	configPath := "/etc/calypso/scst/generated.conf"
	if err := s.WriteConfig(ctx, configPath); err != nil {
		s.logger.Warn("Failed to write config after LUN assignment", "error", err)
		// Don't fail the assignment if config write fails
	}

	s.logger.Info("LUN assigned to initiator groups", "target", targetIQN, "lun", lunNumber, "device", deviceName)
	return nil
}

// RemoveLUN removes a LUN from a target
func (s *Service) RemoveLUN(ctx context.Context, targetIQN string, lunNumber int) error {
	// Remove LUN from SCST first
	// scstadmin -rem_lun may require interactive confirmation, so we pipe "y" to it
	remCmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-rem_lun", fmt.Sprintf("%d", lunNumber),
		"-driver", "iscsi",
		"-target", targetIQN, "-noprompt")
	output, err := remCmd.CombinedOutput()
	outputStr := string(output)

	// Log the command output for debugging
	s.logger.Info("Removing LUN from SCST", "target", targetIQN, "lun", lunNumber, "output", outputStr)

	if err != nil {
		// Check if LUN doesn't exist in SCST (not an error, but log it)
		if strings.Contains(outputStr, "not found") || strings.Contains(outputStr, "does not exist") {
			s.logger.Info("LUN not found in SCST, continuing with database deletion", "target", targetIQN, "lun", lunNumber)
		} else {
			// Real error - return error to prevent database deletion
			s.logger.Error("Failed to remove LUN from SCST", "target", targetIQN, "lun", lunNumber, "output", outputStr, "error", err)
			return fmt.Errorf("failed to remove LUN from SCST: %s: %w", outputStr, err)
		}
	} else {
		// Command succeeded - verify by checking output
		if strings.Contains(outputStr, "Removing") || strings.Contains(outputStr, "done") || strings.Contains(outputStr, "All done") {
			s.logger.Info("LUN removed from SCST successfully", "target", targetIQN, "lun", lunNumber, "output", outputStr)
		} else {
			// Output doesn't indicate success, but no error - log warning
			s.logger.Warn("LUN removal command completed but output unclear", "target", targetIQN, "lun", lunNumber, "output", outputStr)
		}
	}

	// Get target ID
	var targetID string
	err = s.db.QueryRowContext(ctx, "SELECT id FROM scst_targets WHERE iqn = $1", targetIQN).Scan(&targetID)
	if err != nil {
		if err == sql.ErrNoRows {
			return fmt.Errorf("target not found")
		}
		return fmt.Errorf("failed to get target ID: %w", err)
	}

	// Remove from database
	_, err = s.db.ExecContext(ctx, `
		DELETE FROM scst_luns 
		WHERE target_id = $1 AND lun_number = $2
	`, targetID, lunNumber)
	if err != nil {
		return fmt.Errorf("failed to remove LUN from database: %w", err)
	}

	// Write config to persist changes
	configPath := "/etc/calypso/scst/generated.conf"
	if err := s.WriteConfig(ctx, configPath); err != nil {
		s.logger.Warn("Failed to write config after LUN deletion", "error", err)
		// Don't fail the deletion if config write fails
	}

	s.logger.Info("LUN removed", "target", targetIQN, "lun", lunNumber)
	return nil
}

// AddInitiator adds an initiator to a target
func (s *Service) AddInitiator(ctx context.Context, targetIQN, initiatorIQN string) error {
	// Get target from database
	var targetID string
	var singleInitiatorOnly bool
	err := s.db.QueryRowContext(ctx,
		"SELECT id, single_initiator_only FROM scst_targets WHERE iqn = $1",
		targetIQN,
	).Scan(&targetID, &singleInitiatorOnly)
	if err != nil {
		return fmt.Errorf("target not found: %w", err)
	}

	// Check single initiator policy
	if singleInitiatorOnly {
		var existingCount int
		s.db.QueryRowContext(ctx,
			"SELECT COUNT(*) FROM scst_initiators WHERE group_id IN (SELECT id FROM scst_initiator_groups WHERE target_id = $1)",
			targetID,
		).Scan(&existingCount)
		if existingCount > 0 {
			return fmt.Errorf("target enforces single initiator only")
		}
	}

	// Get or create initiator group
	var groupID string
	groupName := targetIQN + "_acl"
	err = s.db.QueryRowContext(ctx,
		"SELECT id FROM scst_initiator_groups WHERE target_id = $1 AND group_name = $2",
		targetID, groupName,
	).Scan(&groupID)

	if err == sql.ErrNoRows {
		// Create group in SCST
		cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-add_group", groupName,
			"-target", targetIQN,
			"-driver", "iscsi")
		output, err := cmd.CombinedOutput()
		if err != nil {
			return fmt.Errorf("failed to create initiator group: %s: %w", string(output), err)
		}

		// Insert into database
		err = s.db.QueryRowContext(ctx,
			"INSERT INTO scst_initiator_groups (target_id, group_name) VALUES ($1, $2) RETURNING id",
			targetID, groupName,
		).Scan(&groupID)
		if err != nil {
			return fmt.Errorf("failed to save group to database: %w", err)
		}
	} else if err != nil {
		return fmt.Errorf("failed to get initiator group: %w", err)
	}

	// Add initiator to group in SCST
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-add_init", initiatorIQN,
		"-group", groupName,
		"-target", targetIQN,
		"-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to add initiator: %s: %w", string(output), err)
	}

	// Insert into database
	_, err = s.db.ExecContext(ctx, `
		INSERT INTO scst_initiators (group_id, iqn, is_active)
		VALUES ($1, $2, true)
		ON CONFLICT (group_id, iqn) DO UPDATE SET is_active = true
	`, groupID, initiatorIQN)
	if err != nil {
		return fmt.Errorf("failed to save initiator to database: %w", err)
	}

	s.logger.Info("Initiator added", "target", targetIQN, "initiator", initiatorIQN)
	return nil
}

// GetTargetInitiatorGroups retrieves all initiator groups for a target
func (s *Service) GetTargetInitiatorGroups(ctx context.Context, targetID string) ([]InitiatorGroup, error) {
	// Get all groups for this target
	query := `
		SELECT id, target_id, group_name, created_at
		FROM scst_initiator_groups
		WHERE target_id = $1
		ORDER BY group_name
	`

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

	var groups []InitiatorGroup
	for rows.Next() {
		var group InitiatorGroup
		err := rows.Scan(&group.ID, &group.TargetID, &group.GroupName, &group.CreatedAt)
		if err != nil {
			s.logger.Error("Failed to scan initiator group", "error", err)
			continue
		}

		// Get initiators for this group
		initiators, err := s.getGroupInitiators(ctx, group.ID)
		if err != nil {
			s.logger.Warn("Failed to get initiators for group", "group_id", group.ID, "error", err)
			group.Initiators = []Initiator{}
		} else {
			group.Initiators = initiators
		}

		groups = append(groups, group)
	}

	return groups, rows.Err()
}

// InitiatorWithTarget extends Initiator with target information
type InitiatorWithTarget struct {
	Initiator
	TargetID   string `json:"target_id"`
	TargetIQN  string `json:"target_iqn"`
	TargetName string `json:"target_name"`
	GroupName  string `json:"group_name"`
}

// ListAllInitiators lists all initiators across all targets
func (s *Service) ListAllInitiators(ctx context.Context) ([]InitiatorWithTarget, error) {
	query := `
		SELECT i.id, i.group_id, i.iqn, i.is_active, i.created_at,
		       ig.target_id, ig.group_name, t.iqn as target_iqn, t.name as target_name
		FROM scst_initiators i
		JOIN scst_initiator_groups ig ON i.group_id = ig.id
		JOIN scst_targets t ON ig.target_id = t.id
		ORDER BY t.iqn, ig.group_name, i.iqn
	`

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

	var initiators []InitiatorWithTarget
	for rows.Next() {
		var initiator InitiatorWithTarget
		err := rows.Scan(
			&initiator.ID, &initiator.GroupID, &initiator.IQN,
			&initiator.IsActive, &initiator.CreatedAt,
			&initiator.TargetID, &initiator.GroupName, &initiator.TargetIQN, &initiator.TargetName,
		)
		if err != nil {
			s.logger.Error("Failed to scan initiator", "error", err)
			continue
		}
		initiators = append(initiators, initiator)
	}

	return initiators, rows.Err()
}

// AddInitiatorToGroup adds an initiator to a specific group
func (s *Service) AddInitiatorToGroup(ctx context.Context, groupID, initiatorIQN string) error {
	// Get group info
	var groupName, targetIQN string
	var targetID string
	var singleInitiatorOnly bool
	err := s.db.QueryRowContext(ctx, `
		SELECT ig.group_name, t.iqn, t.id, t.single_initiator_only
		FROM scst_initiator_groups ig
		JOIN scst_targets t ON ig.target_id = t.id
		WHERE ig.id = $1
	`, groupID).Scan(&groupName, &targetIQN, &targetID, &singleInitiatorOnly)
	if err != nil {
		if err == sql.ErrNoRows {
			return fmt.Errorf("initiator group not found")
		}
		return fmt.Errorf("failed to get initiator group: %w", err)
	}

	// Check single initiator policy
	if singleInitiatorOnly {
		var existingCount int
		s.db.QueryRowContext(ctx,
			"SELECT COUNT(*) FROM scst_initiators WHERE group_id IN (SELECT id FROM scst_initiator_groups WHERE target_id = $1)",
			targetID,
		).Scan(&existingCount)
		if existingCount > 0 {
			return fmt.Errorf("target enforces single initiator only")
		}
	}

	// Check if initiator already exists in this group
	var existingID string
	err = s.db.QueryRowContext(ctx,
		"SELECT id FROM scst_initiators WHERE group_id = $1 AND iqn = $2",
		groupID, initiatorIQN,
	).Scan(&existingID)
	if err == nil {
		return fmt.Errorf("initiator '%s' already exists in this group", initiatorIQN)
	} else if err != sql.ErrNoRows {
		return fmt.Errorf("failed to check existing initiator: %w", err)
	}

	// Add initiator to group in SCST
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-add_init", initiatorIQN,
		"-group", groupName,
		"-target", targetIQN,
		"-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to add initiator to SCST: %s: %w", string(output), err)
	}

	// Insert into database
	_, err = s.db.ExecContext(ctx, `
		INSERT INTO scst_initiators (group_id, iqn, is_active)
		VALUES ($1, $2, true)
	`, groupID, initiatorIQN)
	if err != nil {
		// Try to remove from SCST if database insert fails
		exec.CommandContext(ctx, "sudo", "scstadmin", "-rem_init", initiatorIQN,
			"-group", groupName,
			"-target", targetIQN,
			"-driver", "iscsi").Run()
		return fmt.Errorf("failed to save initiator to database: %w", err)
	}

	s.logger.Info("Initiator added to group", "group", groupName, "initiator", initiatorIQN, "target", targetIQN)
	return nil
}

// RemoveInitiator removes an initiator from a target
func (s *Service) RemoveInitiator(ctx context.Context, initiatorID string) error {
	// Get initiator info
	var initiatorIQN, groupName, targetIQN string
	err := s.db.QueryRowContext(ctx, `
		SELECT i.iqn, ig.group_name, t.iqn
		FROM scst_initiators i
		JOIN scst_initiator_groups ig ON i.group_id = ig.id
		JOIN scst_targets t ON ig.target_id = t.id
		WHERE i.id = $1
	`, initiatorID).Scan(&initiatorIQN, &groupName, &targetIQN)
	if err != nil {
		if err == sql.ErrNoRows {
			return fmt.Errorf("initiator not found")
		}
		return fmt.Errorf("failed to get initiator: %w", err)
	}

	// Remove from SCST
	// Note: scstadmin uses -rem_init (not -remove_init)
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-rem_init", initiatorIQN,
		"-group", groupName,
		"-target", targetIQN,
		"-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	if err != nil {
		outputStr := string(output)
		if !strings.Contains(outputStr, "not found") {
			return fmt.Errorf("failed to remove initiator from SCST: %s: %w", outputStr, err)
		}
		// If not found in SCST, continue to remove from database
	}

	// Remove from database
	_, err = s.db.ExecContext(ctx, "DELETE FROM scst_initiators WHERE id = $1", initiatorID)
	if err != nil {
		return fmt.Errorf("failed to remove initiator from database: %w", err)
	}

	s.logger.Info("Initiator removed", "initiator", initiatorIQN, "target", targetIQN)
	return nil
}

// GetInitiator retrieves an initiator by ID with full details
func (s *Service) GetInitiator(ctx context.Context, initiatorID string) (*Initiator, error) {
	query := `
		SELECT i.id, i.group_id, i.iqn, i.is_active, i.created_at,
		       ig.target_id, ig.group_name, t.iqn as target_iqn, t.name as target_name
		FROM scst_initiators i
		JOIN scst_initiator_groups ig ON i.group_id = ig.id
		JOIN scst_targets t ON ig.target_id = t.id
		WHERE i.id = $1
	`

	var initiator Initiator
	var targetID, groupName, targetIQN, targetName string
	err := s.db.QueryRowContext(ctx, query, initiatorID).Scan(
		&initiator.ID, &initiator.GroupID, &initiator.IQN,
		&initiator.IsActive, &initiator.CreatedAt,
		&targetID, &groupName, &targetIQN, &targetName,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("initiator not found")
		}
		return nil, fmt.Errorf("failed to get initiator: %w", err)
	}

	return &initiator, nil
}

// getGroupInitiators retrieves all initiators for a group
func (s *Service) getGroupInitiators(ctx context.Context, groupID string) ([]Initiator, error) {
	query := `
		SELECT id, group_id, iqn, is_active, created_at
		FROM scst_initiators
		WHERE group_id = $1
		ORDER BY iqn
	`

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

	var initiators []Initiator
	for rows.Next() {
		var initiator Initiator
		err := rows.Scan(&initiator.ID, &initiator.GroupID, &initiator.IQN, &initiator.IsActive, &initiator.CreatedAt)
		if err != nil {
			s.logger.Error("Failed to scan initiator", "error", err)
			continue
		}
		initiators = append(initiators, initiator)
	}

	return initiators, rows.Err()
}

// CreateInitiatorGroup creates a new initiator group for a target
func (s *Service) CreateInitiatorGroup(ctx context.Context, targetID, groupName string) (*InitiatorGroup, error) {
	// Get target IQN
	var targetIQN string
	err := s.db.QueryRowContext(ctx, "SELECT iqn FROM scst_targets WHERE id = $1", targetID).Scan(&targetIQN)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("target not found")
		}
		return nil, fmt.Errorf("failed to get target: %w", err)
	}

	// Validate group name
	if groupName == "" {
		return nil, fmt.Errorf("group name cannot be empty")
	}

	// Check if group already exists
	var existingID string
	err = s.db.QueryRowContext(ctx,
		"SELECT id FROM scst_initiator_groups WHERE target_id = $1 AND group_name = $2",
		targetID, groupName,
	).Scan(&existingID)
	if err == nil {
		return nil, fmt.Errorf("initiator group with name '%s' already exists for this target", groupName)
	} else if err != sql.ErrNoRows {
		return nil, fmt.Errorf("failed to check existing group: %w", err)
	}

	// Create group in SCST
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-add_group", groupName,
		"-target", targetIQN,
		"-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed to create initiator group in SCST: %s: %w", string(output), err)
	}

	// Insert into database
	var groupID string
	err = s.db.QueryRowContext(ctx,
		"INSERT INTO scst_initiator_groups (target_id, group_name) VALUES ($1, $2) RETURNING id",
		targetID, groupName,
	).Scan(&groupID)
	if err != nil {
		// Try to remove from SCST if database insert fails
		// scstadmin -rem_group requires interactive confirmation, so we pipe "y" to it
		rollbackCmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-rem_group", groupName,
			"-target", targetIQN,
			"-driver", "iscsi", "-noprompt")
		rollbackCmd.Run()
		return nil, fmt.Errorf("failed to save group to database: %w", err)
	}

	group := &InitiatorGroup{
		ID:         groupID,
		TargetID:   targetID,
		GroupName:  groupName,
		Initiators: []Initiator{},
		CreatedAt:  time.Now(),
	}

	s.logger.Info("Initiator group created", "target_id", targetID, "group_name", groupName)
	return group, nil
}

// UpdateInitiatorGroup updates an initiator group (rename)
func (s *Service) UpdateInitiatorGroup(ctx context.Context, groupID, newGroupName string) (*InitiatorGroup, error) {
	// Get group info
	var targetIQN, oldGroupName string
	err := s.db.QueryRowContext(ctx, `
		SELECT ig.group_name, t.iqn
		FROM scst_initiator_groups ig
		JOIN scst_targets t ON ig.target_id = t.id
		WHERE ig.id = $1
	`, groupID).Scan(&oldGroupName, &targetIQN)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("initiator group not found")
		}
		return nil, fmt.Errorf("failed to get initiator group: %w", err)
	}

	// Validate new name
	if newGroupName == "" {
		return nil, fmt.Errorf("group name cannot be empty")
	}

	if newGroupName == oldGroupName {
		// No change, just return the group
		return s.GetInitiatorGroup(ctx, groupID)
	}

	// Check if new name already exists for this target
	var targetID string
	err = s.db.QueryRowContext(ctx, "SELECT target_id FROM scst_initiator_groups WHERE id = $1", groupID).Scan(&targetID)
	if err != nil {
		return nil, fmt.Errorf("failed to get target_id: %w", err)
	}

	var existingID string
	err = s.db.QueryRowContext(ctx,
		"SELECT id FROM scst_initiator_groups WHERE target_id = $1 AND group_name = $2 AND id != $3",
		targetID, newGroupName, groupID,
	).Scan(&existingID)
	if err == nil {
		return nil, fmt.Errorf("initiator group with name '%s' already exists for this target", newGroupName)
	} else if err != sql.ErrNoRows {
		return nil, fmt.Errorf("failed to check existing group: %w", err)
	}

	// Update in SCST (remove old, add new)
	// Note: SCST doesn't support rename directly, so we need to recreate
	// First, get all initiators in this group
	initiators, err := s.getGroupInitiators(ctx, groupID)
	if err != nil {
		return nil, fmt.Errorf("failed to get initiators: %w", err)
	}

	// Remove old group from SCST
	// scstadmin -rem_group requires interactive confirmation, so we pipe "y" to it
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-rem_group", oldGroupName,
		"-target", targetIQN,
		"-driver", "iscsi", "-noprompt")
	output, err := cmd.CombinedOutput()
	if err != nil {
		outputStr := string(output)
		if !strings.Contains(outputStr, "not found") && !strings.Contains(outputStr, "does not exist") {
			return nil, fmt.Errorf("failed to remove old group from SCST: %s: %w", outputStr, err)
		}
	}

	// Create new group in SCST
	cmd = exec.CommandContext(ctx, "sudo", "scstadmin", "-add_group", newGroupName,
		"-target", targetIQN,
		"-driver", "iscsi")
	output, err = cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed to create new group in SCST: %s: %w", string(output), err)
	}

	// Re-add all initiators to the new group
	for _, initiator := range initiators {
		cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-add_init", initiator.IQN,
			"-group", newGroupName,
			"-target", targetIQN,
			"-driver", "iscsi")
		output, err := cmd.CombinedOutput()
		if err != nil {
			s.logger.Warn("Failed to re-add initiator to renamed group",
				"initiator", initiator.IQN, "group", newGroupName, "error", string(output))
		}
	}

	// Update in database
	_, err = s.db.ExecContext(ctx,
		"UPDATE scst_initiator_groups SET group_name = $1 WHERE id = $2",
		newGroupName, groupID)
	if err != nil {
		// Try to restore old group in SCST
		exec.CommandContext(ctx, "sudo", "scstadmin", "-add_group", oldGroupName,
			"-target", targetIQN,
			"-driver", "iscsi").Run()
		return nil, fmt.Errorf("failed to update group in database: %w", err)
	}

	s.logger.Info("Initiator group updated", "group_id", groupID, "old_name", oldGroupName, "new_name", newGroupName)
	return s.GetInitiatorGroup(ctx, groupID)
}

// DeleteInitiatorGroup deletes an initiator group
func (s *Service) DeleteInitiatorGroup(ctx context.Context, groupID string) error {
	// Get group info
	var targetIQN, groupName string
	var initiatorCount int
	err := s.db.QueryRowContext(ctx, `
		SELECT ig.group_name, t.iqn, COUNT(i.id)
		FROM scst_initiator_groups ig
		JOIN scst_targets t ON ig.target_id = t.id
		LEFT JOIN scst_initiators i ON i.group_id = ig.id
		WHERE ig.id = $1
		GROUP BY ig.group_name, t.iqn
	`, groupID).Scan(&groupName, &targetIQN, &initiatorCount)
	if err != nil {
		if err == sql.ErrNoRows {
			return fmt.Errorf("initiator group not found")
		}
		return fmt.Errorf("failed to get initiator group: %w", err)
	}

	// Prevent deletion if group has initiators
	if initiatorCount > 0 {
		return fmt.Errorf("cannot delete initiator group: group contains %d initiator(s). Please remove all initiators first", initiatorCount)
	}

	// Remove from SCST first - this must succeed before deleting from database
	// scstadmin -rem_group requires interactive confirmation, so we pipe "y" to it
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-rem_group", groupName,
		"-target", targetIQN,
		"-driver", "iscsi", "-noprompt")
	output, err := cmd.CombinedOutput()
	outputStr := string(output)

	// Log the command output for debugging
	s.logger.Info("Removing group from SCST", "group", groupName, "target", targetIQN, "output", outputStr)

	if err != nil {
		// Check if group doesn't exist in SCST (not an error, but log it)
		if strings.Contains(outputStr, "not found") || strings.Contains(outputStr, "does not exist") {
			s.logger.Info("Group not found in SCST, continuing with database deletion", "group", groupName, "target", targetIQN)
		} else {
			// Real error - return error to prevent database deletion
			s.logger.Error("Failed to remove group from SCST", "group", groupName, "target", targetIQN, "output", outputStr, "error", err)
			return fmt.Errorf("failed to remove group from SCST: %s: %w", outputStr, err)
		}
	} else {
		// Command succeeded - verify by checking output
		if strings.Contains(outputStr, "Removing group") || strings.Contains(outputStr, "done") || strings.Contains(outputStr, "All done") {
			s.logger.Info("Group removed from SCST successfully", "group", groupName, "target", targetIQN, "output", outputStr)
		} else {
			// Output doesn't indicate success, but no error - log warning
			s.logger.Warn("Group removal command completed but output unclear", "group", groupName, "target", targetIQN, "output", outputStr)
		}
	}

	// Delete from database (cascade will handle initiators if any)
	_, err = s.db.ExecContext(ctx, "DELETE FROM scst_initiator_groups WHERE id = $1", groupID)
	if err != nil {
		return fmt.Errorf("failed to delete group from database: %w", err)
	}

	// Write config to persist changes
	configPath := "/etc/calypso/scst/generated.conf"
	if err := s.WriteConfig(ctx, configPath); err != nil {
		s.logger.Warn("Failed to write config after group deletion", "error", err)
		// Don't fail the deletion if config write fails
	}

	s.logger.Info("Initiator group deleted", "group_id", groupID, "group_name", groupName, "target", targetIQN)
	return nil
}

// GetInitiatorGroup retrieves a single initiator group by ID
func (s *Service) GetInitiatorGroup(ctx context.Context, groupID string) (*InitiatorGroup, error) {
	var group InitiatorGroup
	err := s.db.QueryRowContext(ctx, `
		SELECT id, target_id, group_name, created_at
		FROM scst_initiator_groups
		WHERE id = $1
	`, groupID).Scan(&group.ID, &group.TargetID, &group.GroupName, &group.CreatedAt)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("initiator group not found")
		}
		return nil, fmt.Errorf("failed to get initiator group: %w", err)
	}

	// Get initiators for this group
	initiators, err := s.getGroupInitiators(ctx, groupID)
	if err != nil {
		s.logger.Warn("Failed to get initiators for group", "group_id", groupID, "error", err)
		group.Initiators = []Initiator{}
	} else {
		group.Initiators = initiators
	}

	return &group, nil
}

// ListAllInitiatorGroups lists all initiator groups across all targets
func (s *Service) ListAllInitiatorGroups(ctx context.Context) ([]InitiatorGroup, error) {
	query := `
		SELECT id, target_id, group_name, created_at
		FROM scst_initiator_groups
		ORDER BY target_id, group_name
	`

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

	var groups []InitiatorGroup
	for rows.Next() {
		var group InitiatorGroup
		err := rows.Scan(&group.ID, &group.TargetID, &group.GroupName, &group.CreatedAt)
		if err != nil {
			s.logger.Error("Failed to scan initiator group", "error", err)
			continue
		}

		// Get initiators for this group
		initiators, err := s.getGroupInitiators(ctx, group.ID)
		if err != nil {
			s.logger.Warn("Failed to get initiators for group", "group_id", group.ID, "error", err)
			group.Initiators = []Initiator{}
		} else {
			group.Initiators = initiators
		}

		groups = append(groups, group)
	}

	return groups, rows.Err()
}

// ListTargets lists all SCST targets
func (s *Service) ListTargets(ctx context.Context) ([]Target, error) {
	query := `
		SELECT 
			t.id, t.iqn, t.target_type, t.name, t.description, t.is_active,
			t.single_initiator_only, t.created_at, t.updated_at, t.created_by,
			COALESCE(COUNT(l.id), 0) as lun_count
		FROM scst_targets t
		LEFT JOIN scst_luns l ON t.id = l.target_id
		GROUP BY t.id, t.iqn, t.target_type, t.name, t.description, t.is_active,
		         t.single_initiator_only, t.created_at, t.updated_at, t.created_by
		ORDER BY t.name
	`

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

	var targets []Target
	for rows.Next() {
		var target Target
		var description sql.NullString
		err := rows.Scan(
			&target.ID, &target.IQN, &target.TargetType, &target.Name,
			&description, &target.IsActive, &target.SingleInitiatorOnly,
			&target.CreatedAt, &target.UpdatedAt, &target.CreatedBy,
			&target.LUNCount,
		)
		if err != nil {
			s.logger.Error("Failed to scan target", "error", err)
			continue
		}
		if description.Valid {
			target.Description = description.String
		}
		// Set alias to name for frontend compatibility
		target.Alias = target.Name
		targets = append(targets, target)
	}

	return targets, rows.Err()
}

// GetTarget retrieves a target by ID
func (s *Service) GetTarget(ctx context.Context, id string) (*Target, error) {
	query := `
		SELECT id, iqn, target_type, name, description, is_active,
		       single_initiator_only, created_at, updated_at, created_by
		FROM scst_targets
		WHERE id = $1
	`

	var target Target
	var description sql.NullString
	err := s.db.QueryRowContext(ctx, query, id).Scan(
		&target.ID, &target.IQN, &target.TargetType, &target.Name,
		&description, &target.IsActive, &target.SingleInitiatorOnly,
		&target.CreatedAt, &target.UpdatedAt, &target.CreatedBy,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("target not found")
		}
		return nil, fmt.Errorf("failed to get target: %w", err)
	}

	if description.Valid {
		target.Description = description.String
	}

	// Sync enabled status from SCST
	enabled, err := s.getTargetEnabledStatus(ctx, target.IQN)
	if err == nil {
		// Update database if status differs
		if enabled != target.IsActive {
			_, err = s.db.ExecContext(ctx, "UPDATE scst_targets SET is_active = $1 WHERE id = $2", enabled, target.ID)
			if err != nil {
				s.logger.Warn("Failed to update target status", "error", err)
			} else {
				target.IsActive = enabled
			}
		}
	}

	return &target, nil
}

// getTargetEnabledStatus reads enabled status from SCST
func (s *Service) getTargetEnabledStatus(ctx context.Context, targetIQN string) (bool, error) {
	// Read SCST config to check if target is enabled
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-write_config", "/tmp/scst_target_check.conf")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return false, fmt.Errorf("failed to write config: %s: %w", string(output), err)
	}

	// Read config file
	configData, err := os.ReadFile("/tmp/scst_target_check.conf")
	if err != nil {
		return false, fmt.Errorf("failed to read config: %w", err)
	}

	// Check if target is enabled in config
	// Format: TARGET iqn.2025-12.id.atlas:lun01 { enabled 1 } or { enabled 0 }
	configStr := string(configData)
	targetSection := fmt.Sprintf("TARGET %s", targetIQN)
	if !strings.Contains(configStr, targetSection) {
		return false, nil
	}

	// Extract enabled status
	lines := strings.Split(configStr, "\n")
	inTargetSection := false
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.Contains(line, targetSection) {
			inTargetSection = true
			continue
		}
		if inTargetSection {
			if strings.Contains(line, "enabled 1") {
				return true, nil
			}
			if strings.Contains(line, "enabled 0") {
				return false, nil
			}
			if strings.HasPrefix(line, "TARGET") {
				// Next target, stop searching
				break
			}
		}
	}

	// Default to enabled if target exists but status not found
	return true, nil
}

// EnableTarget enables a target in SCST
func (s *Service) EnableTarget(ctx context.Context, targetIQN string) error {
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-enable_target", targetIQN, "-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	if err != nil {
		outputStr := string(output)
		if strings.Contains(outputStr, "already enabled") {
			return nil // Already enabled, no error
		}
		return fmt.Errorf("failed to enable target: %s: %w", outputStr, err)
	}

	// Update database
	var targetID string
	err = s.db.QueryRowContext(ctx, "SELECT id FROM scst_targets WHERE iqn = $1", targetIQN).Scan(&targetID)
	if err == nil {
		s.db.ExecContext(ctx, "UPDATE scst_targets SET is_active = true WHERE id = $1", targetID)
	}

	s.logger.Info("Target enabled", "iqn", targetIQN)
	return nil
}

// DisableTarget disables a target in SCST
func (s *Service) DisableTarget(ctx context.Context, targetIQN string) error {
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-disable_target", targetIQN, "-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	if err != nil {
		outputStr := string(output)
		if strings.Contains(outputStr, "already disabled") {
			return nil // Already disabled, no error
		}
		return fmt.Errorf("failed to disable target: %s: %w", outputStr, err)
	}

	// Update database
	var targetID string
	err = s.db.QueryRowContext(ctx, "SELECT id FROM scst_targets WHERE iqn = $1", targetIQN).Scan(&targetID)
	if err == nil {
		s.db.ExecContext(ctx, "UPDATE scst_targets SET is_active = false WHERE id = $1", targetID)
	}

	s.logger.Info("Target disabled", "iqn", targetIQN)
	return nil
}

// DeleteTarget deletes a target from SCST and database
func (s *Service) DeleteTarget(ctx context.Context, targetID string) error {
	// Get target IQN before deletion
	var targetIQN string
	err := s.db.QueryRowContext(ctx, "SELECT iqn FROM scst_targets WHERE id = $1", targetID).Scan(&targetIQN)
	if err != nil {
		if err == sql.ErrNoRows {
			return fmt.Errorf("target not found")
		}
		return fmt.Errorf("failed to get target: %w", err)
	}

	// Check if target has LUNs - warn but allow deletion
	var lunCount int
	s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM scst_luns WHERE target_id = $1", targetID).Scan(&lunCount)
	if lunCount > 0 {
		s.logger.Warn("Deleting target with LUNs", "target", targetIQN, "lun_count", lunCount)
	}

	// Check if target has initiators - warn but allow deletion
	var initiatorCount int
	s.db.QueryRowContext(ctx, `
		SELECT COUNT(*) FROM scst_initiators 
		WHERE group_id IN (SELECT id FROM scst_initiator_groups WHERE target_id = $1)
	`, targetID).Scan(&initiatorCount)
	if initiatorCount > 0 {
		s.logger.Warn("Deleting target with initiators", "target", targetIQN, "initiator_count", initiatorCount)
	}

	// Remove target from SCST first
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-remove_target", targetIQN, "-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	outputStr := string(output)

	if err != nil {
		// Check if target doesn't exist in SCST (not an error)
		if strings.Contains(outputStr, "not found") || strings.Contains(outputStr, "does not exist") {
			s.logger.Warn("Target not found in SCST, continuing with database deletion", "target", targetIQN)
		} else {
			// Log error but continue with database deletion
			s.logger.Warn("Failed to remove target from SCST", "target", targetIQN, "output", outputStr)
		}
	}

	// Delete from database (cascade will handle related records)
	_, err = s.db.ExecContext(ctx, "DELETE FROM scst_targets WHERE id = $1", targetID)
	if err != nil {
		return fmt.Errorf("failed to delete target from database: %w", err)
	}

	// Write config to persist changes
	configPath := "/etc/calypso/scst/generated.conf"
	if err := s.WriteConfig(ctx, configPath); err != nil {
		s.logger.Warn("Failed to write config after target deletion", "error", err)
		// Don't fail the deletion if config write fails
	}

	s.logger.Info("Target deleted", "iqn", targetIQN, "id", targetID)
	return nil
}

// GetTargetLUNs retrieves all LUNs for a target
// It reads from SCST first, then syncs with database
func (s *Service) GetTargetLUNs(ctx context.Context, targetID string) ([]LUN, error) {
	// Get target IQN
	var targetIQN string
	err := s.db.QueryRowContext(ctx, "SELECT iqn FROM scst_targets WHERE id = $1", targetID).Scan(&targetIQN)
	if err != nil {
		return nil, fmt.Errorf("failed to get target IQN: %w", err)
	}

	// Read LUNs from SCST
	scstLUNs, err := s.readLUNsFromSCST(ctx, targetIQN)
	if err != nil {
		s.logger.Warn("Failed to read LUNs from SCST, using database only", "error", err)
		// Fallback to database only
		return s.getLUNsFromDatabase(ctx, targetID)
	}

	// Sync SCST LUNs with database
	for _, scstLUN := range scstLUNs {
		// Get device info from SCST
		devicePath, handlerType, err := s.getDeviceInfo(ctx, scstLUN.DeviceName)
		if err != nil {
			s.logger.Warn("Failed to get device info", "device", scstLUN.DeviceName, "error", err)
			// Try to get from existing database record if available
			var existingPath, existingHandler string
			s.db.QueryRowContext(ctx,
				"SELECT device_path, handler_type FROM scst_luns WHERE target_id = $1 AND lun_number = $2",
				targetID, scstLUN.LUNNumber,
			).Scan(&existingPath, &existingHandler)
			if existingPath != "" {
				devicePath = existingPath
			}
			if existingHandler != "" {
				handlerType = existingHandler
			}
		}

		// Upsert into database
		_, err = s.db.ExecContext(ctx, `
			INSERT INTO scst_luns (target_id, lun_number, device_name, device_path, handler_type)
			VALUES ($1, $2, $3, $4, $5)
			ON CONFLICT (target_id, lun_number) DO UPDATE SET
				device_name = EXCLUDED.device_name,
				device_path = COALESCE(NULLIF(EXCLUDED.device_path, ''), scst_luns.device_path),
				handler_type = COALESCE(NULLIF(EXCLUDED.handler_type, ''), scst_luns.handler_type)
		`, targetID, scstLUN.LUNNumber, scstLUN.DeviceName, devicePath, handlerType)
		if err != nil {
			s.logger.Warn("Failed to sync LUN to database", "lun", scstLUN.LUNNumber, "error", err)
		}
	}

	// Return from database (now synced)
	return s.getLUNsFromDatabase(ctx, targetID)
}

// getLUNsFromDatabase retrieves LUNs from database only
func (s *Service) getLUNsFromDatabase(ctx context.Context, targetID string) ([]LUN, error) {
	query := `
		SELECT id, target_id, lun_number, device_name, device_path, handler_type, created_at
		FROM scst_luns
		WHERE target_id = $1
		ORDER BY lun_number
	`

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

	var luns []LUN
	for rows.Next() {
		var lun LUN
		err := rows.Scan(
			&lun.ID, &lun.TargetID, &lun.LUNNumber, &lun.DeviceName,
			&lun.DevicePath, &lun.HandlerType, &lun.CreatedAt,
		)
		if err != nil {
			s.logger.Error("Failed to scan LUN", "error", err)
			continue
		}

		// Set handler and device_type for frontend compatibility
		lun.Handler = lun.HandlerType
		// Map handler type to user-friendly device type label
		lun.DeviceType = s.getDeviceTypeLabel(lun.HandlerType)
		// LUN is active if it exists in database (we sync from SCST, so if it's here, it's active)
		lun.IsActive = true

		luns = append(luns, lun)
	}

	return luns, rows.Err()
}

// readLUNsFromSCST reads LUNs directly from SCST using scstadmin -list_group
func (s *Service) readLUNsFromSCST(ctx context.Context, targetIQN string) ([]LUN, error) {
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-list_group",
		"-target", targetIQN,
		"-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed to list group: %s: %w", string(output), err)
	}

	// Parse output
	// Format:
	// Driver: iscsi
	// Target: iqn.2025-12.id.atlas:lun01
	//
	// Assigned LUNs:
	//
	// 	LUN  Device
	// 	----------
	// 	1    LUN01

	lines := strings.Split(string(output), "\n")
	var luns []LUN
	inTargetLUNSection := false
	foundTarget := false

	for _, line := range lines {
		line = strings.TrimSpace(line)

		// Check if we found our target
		if strings.HasPrefix(line, "Target:") {
			targetLine := strings.TrimSpace(strings.TrimPrefix(line, "Target:"))
			if targetLine == targetIQN {
				foundTarget = true
				continue
			} else {
				// Different target, reset
				foundTarget = false
				inTargetLUNSection = false
				continue
			}
		}

		// Only process if we found our target
		if !foundTarget {
			continue
		}

		// Check if we're in the LUN section for this target
		if strings.Contains(line, "Assigned LUNs:") {
			inTargetLUNSection = true
			continue
		}

		// Stop if we hit a Group section (LUNs after this are group-specific)
		if strings.HasPrefix(line, "Group:") {
			break
		}

		// Skip separator line
		if strings.HasPrefix(line, "---") {
			continue
		}

		// Skip header line "LUN  Device"
		if strings.HasPrefix(line, "LUN") && strings.Contains(line, "Device") {
			continue
		}

		// Parse LUN lines (format: "0    pbs-test")
		if inTargetLUNSection && line != "" {
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				var lunNumber int
				if _, err := fmt.Sscanf(parts[0], "%d", &lunNumber); err == nil {
					deviceName := parts[1]
					luns = append(luns, LUN{
						LUNNumber:   lunNumber,
						DeviceName:  deviceName,
						DevicePath:  "", // Will be filled by getDeviceInfo
						HandlerType: "", // Will be filled by getDeviceInfo
					})
				}
			}
		}

		// Stop if we hit "All done"
		if strings.Contains(line, "All done") {
			break
		}
	}

	return luns, nil
}

// getDeviceInfo gets device path and handler type from SCST
// Since scstadmin doesn't provide easy access to device attributes,
// we try to get handler type from device list, and device path from database if available
func (s *Service) getDeviceInfo(ctx context.Context, deviceName string) (string, string, error) {
	// Find which handler this device belongs to
	listCmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-list_device")
	output, err := listCmd.Output()
	if err != nil {
		return "", "", fmt.Errorf("failed to list devices: %w", err)
	}

	// Parse output to find handler
	// Format:
	// Handler           Device
	// -----------------------
	// vdisk_fileio     LUN01
	lines := strings.Split(string(output), "\n")
	var handlerType string
	inHandlerSection := false

	for _, line := range lines {
		line = strings.TrimSpace(line)

		// Check if we're in the handler section
		if strings.Contains(line, "Handler") && strings.Contains(line, "Device") {
			inHandlerSection = true
			continue
		}

		// Skip separator
		if strings.HasPrefix(line, "---") {
			continue
		}

		// Parse handler and device lines
		if inHandlerSection && line != "" && !strings.Contains(line, "Collecting") && !strings.Contains(line, "All done") {
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				handler := parts[0]
				device := parts[1]
				if device == deviceName {
					handlerType = handler
					break
				}
			}
		}

		if strings.Contains(line, "All done") {
			break
		}
	}

	if handlerType == "" {
		return "", "", fmt.Errorf("handler not found for device %s", deviceName)
	}

	// Try to get device path from SCST config file
	devicePath := s.getDevicePathFromConfig(deviceName, handlerType)

	return devicePath, handlerType, nil
}

// getDevicePathFromConfig reads device path from SCST config file
func (s *Service) getDevicePathFromConfig(deviceName, handlerType string) string {
	// Write current config to temp file
	cmd := exec.Command("sudo", "scstadmin", "-write_config", "/tmp/scst_device_info.conf")
	if err := cmd.Run(); err != nil {
		s.logger.Warn("Failed to write SCST config", "error", err)
		return ""
	}

	// Read config file
	configData, err := os.ReadFile("/tmp/scst_device_info.conf")
	if err != nil {
		s.logger.Warn("Failed to read SCST config", "error", err)
		return ""
	}

	// Parse config to find device
	// Format:
	// DEVICE deviceName {
	//     filename /path/to/device
	// }
	lines := strings.Split(string(configData), "\n")
	inDeviceBlock := false
	var devicePath string

	for _, line := range lines {
		line = strings.TrimSpace(line)

		// Check if we're entering the device block
		if strings.Contains(line, "DEVICE") && strings.Contains(line, deviceName) {
			inDeviceBlock = true
			continue
		}

		// Check if we're leaving the device block
		if inDeviceBlock && strings.HasPrefix(line, "}") {
			break
		}

		// Look for filename in device block
		if inDeviceBlock && strings.Contains(line, "filename") {
			parts := strings.Fields(line)
			for j, part := range parts {
				if part == "filename" && j+1 < len(parts) {
					devicePath = parts[j+1]
					break
				}
			}
			if devicePath != "" {
				break
			}
		}
	}

	return devicePath
}

// ListExtents lists all device extents (opened devices) in SCST
func (s *Service) ListExtents(ctx context.Context) ([]Extent, error) {
	// List all devices from SCST
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-list_device")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to list devices: %w", err)
	}

	// Parse output
	// Format:
	// Handler           Device
	// -----------------------
	// vdisk_fileio     LUN01
	lines := strings.Split(string(output), "\n")
	var extents []Extent
	inHandlerSection := false

	for _, line := range lines {
		line = strings.TrimSpace(line)

		// Check if we're in the handler section
		if strings.Contains(line, "Handler") && strings.Contains(line, "Device") {
			inHandlerSection = true
			continue
		}

		// Skip separator
		if strings.HasPrefix(line, "---") {
			continue
		}

		// Skip header/footer lines
		if strings.Contains(line, "Collecting") || strings.Contains(line, "All done") {
			continue
		}

		// Parse handler and device lines
		if inHandlerSection && line != "" {
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				handlerType := parts[0]
				deviceName := parts[1]

				// Skip if device is "-" (no device opened for this handler)
				if deviceName == "-" {
					continue
				}

				// Get device path from config or database
				devicePath := s.getDevicePathFromConfig(deviceName, handlerType)
				if devicePath == "" {
					// Try to get from database
					var dbPath string
					s.db.QueryRowContext(ctx,
						"SELECT device_path FROM scst_luns WHERE device_name = $1 LIMIT 1",
						deviceName,
					).Scan(&dbPath)
					if dbPath != "" {
						devicePath = dbPath
					}
				}

				// Count how many LUNs use this device
				var lunCount int
				s.db.QueryRowContext(ctx,
					"SELECT COUNT(*) FROM scst_luns WHERE device_name = $1",
					deviceName,
				).Scan(&lunCount)

				extents = append(extents, Extent{
					HandlerType: handlerType,
					DeviceName:  deviceName,
					DevicePath:  devicePath,
					IsInUse:     lunCount > 0,
					LUNCount:    lunCount,
				})
			}
		}
	}

	return extents, nil
}

// CreateExtent opens a device in SCST (creates an extent)
func (s *Service) CreateExtent(ctx context.Context, deviceName, devicePath, handlerType string) error {
	// Validate handler type
	handlers, err := s.DetectHandlers(ctx)
	if err != nil {
		return fmt.Errorf("failed to detect handlers: %w", err)
	}

	handlerValid := false
	for _, h := range handlers {
		if h.Name == handlerType {
			handlerValid = true
			break
		}
	}

	if !handlerValid {
		return fmt.Errorf("invalid handler type: %s", handlerType)
	}

	// Open device in SCST
	openCmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-open_dev", deviceName,
		"-handler", handlerType,
		"-attributes", fmt.Sprintf("filename=%s", devicePath))
	output, err := openCmd.CombinedOutput()
	if err != nil {
		outputStr := string(output)
		if strings.Contains(outputStr, "already exists") {
			return fmt.Errorf("device %s already exists", deviceName)
		}
		return fmt.Errorf("failed to open device: %s: %w", outputStr, err)
	}

	s.logger.Info("Extent created", "device", deviceName, "handler", handlerType, "path", devicePath)
	return nil
}

// getDeviceHandlerType gets the handler type for a device by querying SCST
func (s *Service) getDeviceHandlerType(ctx context.Context, deviceName string) (string, error) {
	// First, try to get from database if device was used by a LUN
	var handlerType string
	err := s.db.QueryRowContext(ctx,
		"SELECT handler_type FROM scst_luns WHERE device_name = $1 LIMIT 1",
		deviceName,
	).Scan(&handlerType)
	if err == nil && handlerType != "" {
		return handlerType, nil
	}

	// If not in database, query from SCST
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-list_device")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("failed to list devices: %w", err)
	}

	// Parse output to find handler type for this device
	lines := strings.Split(string(output), "\n")
	inHandlerSection := false
	for _, line := range lines {
		line = strings.TrimSpace(line)

		// Check if we're in a handler section
		if strings.HasPrefix(line, "HANDLER") {
			inHandlerSection = true
			continue
		}

		// Skip separator
		if strings.HasPrefix(line, "---") {
			continue
		}

		// Skip header/footer lines
		if strings.Contains(line, "Collecting") || strings.Contains(line, "All done") {
			continue
		}

		// Parse handler and device lines
		if inHandlerSection && line != "" {
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				parsedHandlerType := parts[0]
				parsedDeviceName := parts[1]

				// Skip if device is "-" (no device opened for this handler)
				if parsedDeviceName == "-" {
					continue
				}

				// Found the device, return its handler type
				if parsedDeviceName == deviceName {
					return parsedHandlerType, nil
				}
			}
		}
	}

	return "", fmt.Errorf("device %s not found in SCST", deviceName)
}

// DeleteExtent closes a device in SCST (removes an extent)
func (s *Service) DeleteExtent(ctx context.Context, deviceName string) error {
	// Check if device is in use by any LUN
	var lunCount int
	err := s.db.QueryRowContext(ctx,
		"SELECT COUNT(*) FROM scst_luns WHERE device_name = $1",
		deviceName,
	).Scan(&lunCount)
	if err == nil && lunCount > 0 {
		return fmt.Errorf("device %s is in use by %d LUN(s). Remove LUNs first", deviceName, lunCount)
	}

	// Get handler type for this device
	handlerType, err := s.getDeviceHandlerType(ctx, deviceName)
	if err != nil {
		// If device not found, check if it's already closed
		if strings.Contains(err.Error(), "not found") {
			s.logger.Info("Device not found in SCST, may already be closed", "device", deviceName)
			return nil // Idempotent - device already closed
		}
		return fmt.Errorf("failed to get handler type: %w", err)
	}

	// Close device in SCST with handler type
	// Command format: scstadmin -close_dev <device> -handler <handler>
	closeCmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-close_dev", deviceName,
		"-handler", handlerType)
	output, err := closeCmd.CombinedOutput()
	outputStr := string(output)

	// Log the command output for debugging
	s.logger.Info("Closing device in SCST", "device", deviceName, "handler", handlerType, "command", fmt.Sprintf("scstadmin -close_dev %s -handler %s", deviceName, handlerType), "output", outputStr)

	if err != nil {
		// Check if device doesn't exist in SCST (not an error, but log it)
		if strings.Contains(outputStr, "not found") || strings.Contains(outputStr, "does not exist") {
			s.logger.Info("Device not found in SCST, may already be closed", "device", deviceName)
			return nil // Idempotent - device already closed
		}
		return fmt.Errorf("failed to close device: %s: %w", outputStr, err)
	}

	// Always verify device is actually closed by checking if it still exists
	// This ensures the command really succeeded even if output is unclear
	verifyCmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-list_device")
	verifyOutput, verifyErr := verifyCmd.CombinedOutput()
	if verifyErr == nil {
		verifyOutputStr := string(verifyOutput)
		// Parse output to check if device still exists
		lines := strings.Split(verifyOutputStr, "\n")
		inHandlerSection := false
		deviceStillExists := false

		for _, line := range lines {
			line = strings.TrimSpace(line)

			// Check if we're in a handler section
			if strings.HasPrefix(line, "HANDLER") {
				inHandlerSection = true
				continue
			}

			// Skip separator
			if strings.HasPrefix(line, "---") {
				continue
			}

			// Skip header/footer lines
			if strings.Contains(line, "Collecting") || strings.Contains(line, "All done") {
				continue
			}

			// Parse handler and device lines
			if inHandlerSection && line != "" {
				parts := strings.Fields(line)
				if len(parts) >= 2 {
					parsedDeviceName := parts[1]
					// Skip if device is "-" (no device opened for this handler)
					if parsedDeviceName == "-" {
						continue
					}
					// Check if this is the device we're trying to close
					if parsedDeviceName == deviceName {
						deviceStillExists = true
						break
					}
				}
			}
		}

		if deviceStillExists {
			// Device still exists - command failed
			return fmt.Errorf("device %s still exists after close command. Command output: %s", deviceName, outputStr)
		}
		s.logger.Info("Device verified as closed", "device", deviceName)
	} else {
		// If verification fails, log warning but don't fail (command may have succeeded)
		s.logger.Warn("Failed to verify device closure", "device", deviceName, "error", verifyErr)
	}

	// Write config to persist changes
	configPath := "/etc/calypso/scst/generated.conf"
	if err := s.WriteConfig(ctx, configPath); err != nil {
		s.logger.Warn("Failed to write config after extent deletion", "error", err)
		// Don't fail the deletion if config write fails
	}

	s.logger.Info("Extent deleted successfully", "device", deviceName, "handler", handlerType)
	return nil
}

// getDeviceTypeLabel returns a user-friendly label for device type based on handler type
func (s *Service) getDeviceTypeLabel(handlerType string) string {
	deviceTypeMap := map[string]string{
		"vdisk_fileio":  "ZFS Volume",
		"vdisk_blockio": "Block Device",
		"vdisk_nullio":  "Null Device",
		"vcdrom":        "CDROM",
		"dev_cdrom":     "CDROM",
		"dev_disk":      "Physical Disk",
		"dev_disk_perf": "Physical Disk (Performance)",
	}

	if label, ok := deviceTypeMap[handlerType]; ok {
		return label
	}

	// Default: return handler type as-is
	return handlerType
}

// WriteConfig writes SCST configuration to file
func (s *Service) WriteConfig(ctx context.Context, configPath string) error {
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-write_config", configPath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to write SCST config: %s: %w", string(output), err)
	}

	s.logger.Info("SCST configuration written", "path", configPath)
	return nil
}

// ReadConfigFile reads the SCST configuration file content
func (s *Service) ReadConfigFile(ctx context.Context, configPath string) (string, error) {
	// First, write current config to temp file to get the actual config
	tempPath := "/tmp/scst_config_read.conf"
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-write_config", tempPath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("failed to write SCST config: %s: %w", string(output), err)
	}

	// Read the config file
	configData, err := os.ReadFile(tempPath)
	if err != nil {
		return "", fmt.Errorf("failed to read config file: %w", err)
	}

	return string(configData), nil
}

// WriteConfigFile writes content to SCST configuration file
func (s *Service) WriteConfigFile(ctx context.Context, configPath string, content string) error {
	// Write content to temp file first
	tempPath := "/tmp/scst_config_write.conf"
	if err := os.WriteFile(tempPath, []byte(content), 0644); err != nil {
		return fmt.Errorf("failed to write temp config file: %w", err)
	}

	// Use scstadmin to load the config (this validates and applies it)
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-config", tempPath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to load SCST config: %s: %w", string(output), err)
	}

	// Write to the actual config path using sudo
	if configPath != tempPath {
		// Use sudo cp to copy temp file to actual config path
		cpCmd := exec.CommandContext(ctx, "sudo", "cp", tempPath, configPath)
		cpOutput, cpErr := cpCmd.CombinedOutput()
		if cpErr != nil {
			return fmt.Errorf("failed to copy config file: %s: %w", string(cpOutput), cpErr)
		}
		// Set proper permissions
		chmodCmd := exec.CommandContext(ctx, "sudo", "chmod", "644", configPath)
		if chmodErr := chmodCmd.Run(); chmodErr != nil {
			s.logger.Warn("Failed to set config file permissions", "error", chmodErr)
		}
	}

	s.logger.Info("SCST configuration file written", "path", configPath)
	return nil
}

// HandlerInfo represents SCST handler information
type HandlerInfo struct {
	Name        string `json:"name"`
	Label       string `json:"label"` // Simple, user-friendly label
	Description string `json:"description,omitempty"`
}

// DetectHandlers detects available SCST handlers
func (s *Service) DetectHandlers(ctx context.Context) ([]HandlerInfo, error) {
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-list_handler")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to list handlers: %w", err)
	}

	// Parse output - skip header lines and separator
	handlers := []HandlerInfo{}
	lines := strings.Split(string(output), "\n")
	skipHeader := true

	for _, line := range lines {
		line = strings.TrimSpace(line)

		// Skip empty lines
		if line == "" {
			continue
		}

		// Skip header line "Handler"
		if strings.HasPrefix(line, "Handler") {
			skipHeader = false
			continue
		}

		// Skip separator line "-------------"
		if strings.HasPrefix(line, "---") {
			continue
		}

		// Skip lines before header (like "Collecting current configuration: done.")
		if skipHeader {
			continue
		}

		// Skip footer lines
		if strings.Contains(line, "All done") || strings.Contains(line, "Collecting") {
			continue
		}

		// Map handler names to labels and descriptions
		label, description := s.getHandlerInfo(line)
		handlers = append(handlers, HandlerInfo{
			Name:        line,
			Label:       label,
			Description: description,
		})
	}

	return handlers, nil
}

// getHandlerInfo returns a simple label and description for a handler
func (s *Service) getHandlerInfo(handlerName string) (string, string) {
	handlerInfo := map[string]struct {
		label       string
		description string
	}{
		"dev_disk": {
			label:       "Physical Disk",
			description: "Physical disk handler",
		},
		"dev_disk_perf": {
			label:       "Physical Disk (Performance)",
			description: "Physical disk handler with performance optimizations",
		},
		"dev_cdrom": {
			label:       "CDROM",
			description: "CD/DVD-ROM handler",
		},
		"vdisk_blockio": {
			label:       "Block Device",
			description: "Virtual disk block I/O handler (for block devices)",
		},
		"vdisk_fileio": {
			label:       "Volume",
			description: "Virtual disk file I/O handler (for ZFS volumes and files)",
		},
		"vdisk_nullio": {
			label:       "Null Device",
			description: "Null I/O handler (for testing)",
		},
		"vcdrom": {
			label:       "CDROM",
			description: "Virtual CD-ROM handler",
		},
	}

	if info, ok := handlerInfo[handlerName]; ok {
		return info.label, info.description
	}

	// Default: use handler name as label
	return handlerName, ""
}

// ListPortals lists all iSCSI portals
func (s *Service) ListPortals(ctx context.Context) ([]Portal, error) {
	query := `
		SELECT id, ip_address, port, is_active, created_at, updated_at
		FROM scst_portals
		ORDER BY ip_address, port
	`

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

	var portals []Portal
	for rows.Next() {
		var portal Portal
		err := rows.Scan(
			&portal.ID, &portal.IPAddress, &portal.Port,
			&portal.IsActive, &portal.CreatedAt, &portal.UpdatedAt,
		)
		if err != nil {
			s.logger.Error("Failed to scan portal", "error", err)
			continue
		}
		portals = append(portals, portal)
	}

	return portals, rows.Err()
}

// CreatePortal creates a new iSCSI portal
func (s *Service) CreatePortal(ctx context.Context, portal *Portal) error {
	// Validate IP address format (basic validation)
	if portal.IPAddress == "" {
		return fmt.Errorf("IP address is required")
	}

	// Validate port range
	if portal.Port < 1 || portal.Port > 65535 {
		return fmt.Errorf("port must be between 1 and 65535")
	}

	// Default port to 3260 if not specified
	if portal.Port == 0 {
		portal.Port = 3260
	}

	// Insert into database
	query := `
		INSERT INTO scst_portals (ip_address, port, is_active)
		VALUES ($1, $2, $3)
		RETURNING id, created_at, updated_at
	`

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

	s.logger.Info("Portal created", "ip", portal.IPAddress, "port", portal.Port)

	// Apply portal to all existing targets if active
	if portal.IsActive {
		if err := s.applyPortalToAllTargets(ctx, portal.IPAddress, portal.Port); err != nil {
			s.logger.Warn("Failed to apply portal to targets", "ip", portal.IPAddress, "port", portal.Port, "error", err)
			// Don't fail portal creation if application fails
		}
	}

	return nil
}

// UpdatePortal updates an existing portal
func (s *Service) UpdatePortal(ctx context.Context, id string, portal *Portal) error {
	// Validate port range
	if portal.Port < 1 || portal.Port > 65535 {
		return fmt.Errorf("port must be between 1 and 65535")
	}

	// Get old portal data before update
	var oldIPAddress string
	var oldPort int
	var oldIsActive bool
	err := s.db.QueryRowContext(ctx, "SELECT ip_address, port, is_active FROM scst_portals WHERE id = $1", id).Scan(&oldIPAddress, &oldPort, &oldIsActive)
	if err != nil {
		if err == sql.ErrNoRows {
			return fmt.Errorf("portal not found")
		}
		return fmt.Errorf("failed to get portal info: %w", err)
	}

	// Update in database
	query := `
		UPDATE scst_portals
		SET ip_address = $1, port = $2, is_active = $3, updated_at = NOW()
		WHERE id = $4
		RETURNING updated_at
	`

	err = s.db.QueryRowContext(ctx, query,
		portal.IPAddress, portal.Port, portal.IsActive, id,
	).Scan(&portal.UpdatedAt)
	if err != nil {
		return fmt.Errorf("failed to update portal: %w", err)
	}

	s.logger.Info("Portal updated", "id", id, "ip", portal.IPAddress, "port", portal.Port)

	// Handle SCST updates
	if portal.IsActive {
		// Portal is active - apply to all targets
		// Remove old portal if IP or port changed
		if oldIPAddress != portal.IPAddress || oldPort != portal.Port {
			if err := s.removePortalFromAllTargets(ctx, oldIPAddress, oldPort); err != nil {
				s.logger.Warn("Failed to remove old portal from targets", "ip", oldIPAddress, "port", oldPort, "error", err)
			}
		}
		// Apply new portal to all targets
		if err := s.applyPortalToAllTargets(ctx, portal.IPAddress, portal.Port); err != nil {
			s.logger.Warn("Failed to apply portal to targets", "ip", portal.IPAddress, "port", portal.Port, "error", err)
		}
	} else {
		// Portal is inactive - remove from all targets
		// Only remove if it was previously active
		if oldIsActive {
			if err := s.removePortalFromAllTargets(ctx, oldIPAddress, oldPort); err != nil {
				s.logger.Warn("Failed to remove portal from targets", "ip", oldIPAddress, "port", oldPort, "error", err)
			}
		}
	}

	return nil
}

// DeletePortal deletes a portal
func (s *Service) DeletePortal(ctx context.Context, id string) error {
	// Get portal info before deletion to remove from SCST
	var ipAddress string
	var port int
	err := s.db.QueryRowContext(ctx, "SELECT ip_address, port FROM scst_portals WHERE id = $1", id).Scan(&ipAddress, &port)
	if err != nil {
		if err == sql.ErrNoRows {
			return fmt.Errorf("portal not found")
		}
		return fmt.Errorf("failed to get portal info: %w", err)
	}

	// Remove portal from all targets before deleting from database
	if err := s.removePortalFromAllTargets(ctx, ipAddress, port); err != nil {
		s.logger.Warn("Failed to remove portal from targets", "ip", ipAddress, "port", port, "error", err)
		// Continue with deletion even if removal fails
	}

	// Delete from database
	result, err := s.db.ExecContext(ctx, "DELETE FROM scst_portals WHERE id = $1", id)
	if err != nil {
		return fmt.Errorf("failed to delete portal: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("portal not found")
	}

	s.logger.Info("Portal deleted", "id", id)
	return nil
}

// GetPortal retrieves a portal by ID
func (s *Service) GetPortal(ctx context.Context, id string) (*Portal, error) {
	query := `
		SELECT id, ip_address, port, is_active, created_at, updated_at
		FROM scst_portals
		WHERE id = $1
	`

	var portal Portal
	err := s.db.QueryRowContext(ctx, query, id).Scan(
		&portal.ID, &portal.IPAddress, &portal.Port,
		&portal.IsActive, &portal.CreatedAt, &portal.UpdatedAt,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("portal not found")
		}
		return nil, fmt.Errorf("failed to get portal: %w", err)
	}

	return &portal, nil
}

// applyPortalsToTarget applies all active portals to a specific target
func (s *Service) applyPortalsToTarget(ctx context.Context, targetIQN string) error {
	// Get all active portals
	query := `
		SELECT ip_address, port
		FROM scst_portals
		WHERE is_active = true
		ORDER BY ip_address, port
	`
	rows, err := s.db.QueryContext(ctx, query)
	if err != nil {
		return fmt.Errorf("failed to query portals: %w", err)
	}
	defer rows.Close()

	var portals []struct {
		IPAddress string
		Port      int
	}
	for rows.Next() {
		var portal struct {
			IPAddress string
			Port      int
		}
		if err := rows.Scan(&portal.IPAddress, &portal.Port); err != nil {
			s.logger.Warn("Failed to scan portal", "error", err)
			continue
		}
		portals = append(portals, portal)
	}

	// Apply each portal to the target
	for _, portal := range portals {
		if err := s.addPortalToTarget(ctx, targetIQN, portal.IPAddress, portal.Port); err != nil {
			s.logger.Warn("Failed to add portal to target", "target", targetIQN, "ip", portal.IPAddress, "port", portal.Port, "error", err)
			// Continue with other portals even if one fails
		}
	}

	return nil
}

// applyPortalToAllTargets applies a portal to all existing targets
func (s *Service) applyPortalToAllTargets(ctx context.Context, ipAddress string, port int) error {
	// Ensure the iSCSI target driver is enabled
	enableCmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-set_drv_attr", "iscsi", "-attributes", "enabled=1")
	if output, err := enableCmd.CombinedOutput(); err != nil {
		s.logger.Warn("Could not enable iscsi target driver", "error", string(output))
		// Don't fail here, as it might already be enabled or another issue might be present.
	}

	// Get all iSCSI targets
	query := `
		SELECT iqn
		FROM scst_targets
		WHERE target_type IN ('disk', 'vtl', 'physical_tape')
		ORDER BY iqn
	`
	rows, err := s.db.QueryContext(ctx, query)
	if err != nil {
		return fmt.Errorf("failed to query targets: %w", err)
	}
	defer rows.Close()

	var targets []string
	for rows.Next() {
		var iqn string
		if err := rows.Scan(&iqn); err != nil {
			s.logger.Warn("Failed to scan target", "error", err)
			continue
		}
		targets = append(targets, iqn)
	}

	// Apply portal to each target
	for _, targetIQN := range targets {
		if err := s.addPortalToTarget(ctx, targetIQN, ipAddress, port); err != nil {
			s.logger.Warn("Failed to add portal to target", "target", targetIQN, "ip", ipAddress, "port", port, "error", err)
			// Continue with other targets even if one fails
		}
	}

	return nil
}

// removePortalFromAllTargets removes a portal from all existing targets
func (s *Service) removePortalFromAllTargets(ctx context.Context, ipAddress string, port int) error {
	// Get all iSCSI targets
	query := `
		SELECT iqn
		FROM scst_targets
		WHERE target_type IN ('disk', 'vtl', 'physical_tape')
		ORDER BY iqn
	`
	rows, err := s.db.QueryContext(ctx, query)
	if err != nil {
		return fmt.Errorf("failed to query targets: %w", err)
	}
	defer rows.Close()

	var targets []string
	for rows.Next() {
		var iqn string
		if err := rows.Scan(&iqn); err != nil {
			s.logger.Warn("Failed to scan target", "error", err)
			continue
		}
		targets = append(targets, iqn)
	}

	// Remove portal from each target
	for _, targetIQN := range targets {
		if err := s.removePortalFromTarget(ctx, targetIQN, ipAddress, port); err != nil {
			s.logger.Warn("Failed to remove portal from target", "target", targetIQN, "ip", ipAddress, "port", port, "error", err)
			// Continue with other targets even if one fails
		}
	}

	return nil
}

// addPortalToTarget adds a portal to a specific target in SCST
func (s *Service) addPortalToTarget(ctx context.Context, targetIQN, ipAddress string, port int) error {
	// Format: IP:PORT
	portalAddr := fmt.Sprintf("%s:%d", ipAddress, port)

	// Use scstadmin to add portal to target
	// Note: SCST uses different syntax depending on version
	// Try the standard syntax first: -add_portal <IP:PORT> -target <IQN> -driver iscsi
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-add_portal", portalAddr,
		"-target", targetIQN,
		"-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	outputStr := string(output)

	if err != nil {
		// Check if portal already exists (not an error)
		if strings.Contains(outputStr, "already exists") || strings.Contains(outputStr, "already added") {
			s.logger.Debug("Portal already exists on target", "target", targetIQN, "portal", portalAddr)
			return nil
		}

		// Some SCST versions use different syntax or portal might be configured differently
		// Log warning but don't fail - portal configuration might be handled by iscsi-scstd
		s.logger.Warn("Failed to add portal to target (may be handled by iscsi-scstd)", "target", targetIQN, "portal", portalAddr, "output", outputStr)
		return fmt.Errorf("failed to add portal: %s: %w", outputStr, err)
	}

	s.logger.Info("Portal added to target", "target", targetIQN, "portal", portalAddr)
	return nil
}

// removePortalFromTarget removes a portal from a specific target in SCST
func (s *Service) removePortalFromTarget(ctx context.Context, targetIQN, ipAddress string, port int) error {
	// Format: IP:PORT
	portalAddr := fmt.Sprintf("%s:%d", ipAddress, port)

	// Use scstadmin to remove portal from target
	cmd := exec.CommandContext(ctx, "sudo", "scstadmin", "-remove_portal", portalAddr,
		"-target", targetIQN,
		"-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	outputStr := string(output)

	if err != nil {
		// Check if portal doesn't exist (not an error)
		if strings.Contains(outputStr, "not found") || strings.Contains(outputStr, "does not exist") {
			s.logger.Debug("Portal not found on target", "target", targetIQN, "portal", portalAddr)
			return nil
		}

		// Log warning but don't fail
		s.logger.Warn("Failed to remove portal from target", "target", targetIQN, "portal", portalAddr, "output", outputStr)
		return fmt.Errorf("failed to remove portal: %s: %w", outputStr, err)
	}

	s.logger.Info("Portal removed from target", "target", targetIQN, "portal", portalAddr)
	return nil
}
