package scst

import (
	"context"
	"database/sql"
	"fmt"
	"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"`
	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"`
	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"`
	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"`
}

// 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, "scstadmin", "-add_target", target.IQN, "-driver", "iscsi")
	output, err := cmd.CombinedOutput()
	if err != nil {
		// Check if target already exists
		if strings.Contains(string(output), "already exists") {
			s.logger.Warn("Target already exists in SCST", "iqn", target.IQN)
		} else {
			return fmt.Errorf("failed to create SCST target: %s: %w", string(output), err)
		}
	}

	// 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, "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)
	return nil
}

// AddLUN adds a LUN to a target
func (s *Service) AddLUN(ctx context.Context, targetIQN, deviceName, devicePath string, lunNumber int, handlerType string) error {
	// Open device in SCST
	openCmd := exec.CommandContext(ctx, "scstadmin", "-open_dev", deviceName,
		"-handler", handlerType,
		"-attributes", fmt.Sprintf("filename=%s", devicePath))
	output, err := openCmd.CombinedOutput()
	if err != nil {
		if !strings.Contains(string(output), "already exists") {
			return fmt.Errorf("failed to open device in SCST: %s: %w", string(output), err)
		}
	}

	// Add LUN to target
	addCmd := exec.CommandContext(ctx, "scstadmin", "-add_lun", fmt.Sprintf("%d", lunNumber),
		"-target", targetIQN,
		"-driver", "iscsi",
		"-device", deviceName)
	output, err = addCmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to add LUN to target: %s: %w", string(output), err)
	}

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

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

	s.logger.Info("LUN added", "target", targetIQN, "lun", lunNumber, "device", deviceName)
	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, "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, "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
}

// ListTargets lists all SCST targets
func (s *Service) ListTargets(ctx context.Context) ([]Target, error) {
	query := `
		SELECT id, iqn, target_type, name, description, is_active,
		       single_initiator_only, created_at, updated_at, created_by
		FROM scst_targets
		ORDER BY 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
		err := rows.Scan(
			&target.ID, &target.IQN, &target.TargetType, &target.Name,
			&target.Description, &target.IsActive, &target.SingleInitiatorOnly,
			&target.CreatedAt, &target.UpdatedAt, &target.CreatedBy,
		)
		if err != nil {
			s.logger.Error("Failed to scan target", "error", err)
			continue
		}
		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
	err := s.db.QueryRowContext(ctx, query, id).Scan(
		&target.ID, &target.IQN, &target.TargetType, &target.Name,
		&target.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)
	}

	return &target, nil
}

// GetTargetLUNs retrieves all LUNs for a target
func (s *Service) GetTargetLUNs(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
		}
		luns = append(luns, lun)
	}

	return luns, rows.Err()
}

// WriteConfig writes SCST configuration to file
func (s *Service) WriteConfig(ctx context.Context, configPath string) error {
	cmd := exec.CommandContext(ctx, "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
}

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

	// Parse output (simplified - actual parsing would be more robust)
	handlers := []string{}
	lines := strings.Split(string(output), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line != "" && !strings.HasPrefix(line, "Handler") {
			handlers = append(handlers, line)
		}
	}

	return handlers, nil
}

