package tape_physical

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

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

// Service handles physical tape library operations
type Service struct {
	db     *database.DB
	logger *logger.Logger
}

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

// TapeLibrary represents a physical tape library
type TapeLibrary struct {
	ID              string    `json:"id"`
	Name            string    `json:"name"`
	SerialNumber    string    `json:"serial_number"`
	Vendor          string    `json:"vendor"`
	Model           string    `json:"model"`
	ChangerDevicePath string  `json:"changer_device_path"`
	ChangerStablePath string  `json:"changer_stable_path"`
	SlotCount       int       `json:"slot_count"`
	DriveCount      int       `json:"drive_count"`
	IsActive        bool      `json:"is_active"`
	DiscoveredAt    time.Time `json:"discovered_at"`
	LastInventoryAt *time.Time `json:"last_inventory_at"`
	CreatedAt       time.Time `json:"created_at"`
	UpdatedAt       time.Time `json:"updated_at"`
}

// TapeDrive represents a physical tape drive
type TapeDrive struct {
	ID               string    `json:"id"`
	LibraryID        string    `json:"library_id"`
	DriveNumber      int       `json:"drive_number"`
	DevicePath       string    `json:"device_path"`
	StablePath       string    `json:"stable_path"`
	Vendor            string    `json:"vendor"`
	Model             string    `json:"model"`
	SerialNumber      string    `json:"serial_number"`
	DriveType         string    `json:"drive_type"`
	Status            string    `json:"status"`
	CurrentTapeBarcode string   `json:"current_tape_barcode"`
	IsActive          bool      `json:"is_active"`
	CreatedAt         time.Time `json:"created_at"`
	UpdatedAt         time.Time `json:"updated_at"`
}

// TapeSlot represents a tape slot in the library
type TapeSlot struct {
	ID           string     `json:"id"`
	LibraryID    string     `json:"library_id"`
	SlotNumber   int        `json:"slot_number"`
	Barcode      string     `json:"barcode"`
	TapePresent  bool       `json:"tape_present"`
	TapeType     string     `json:"tape_type"`
	LastUpdatedAt time.Time `json:"last_updated_at"`
}

// DiscoverLibraries discovers physical tape libraries on the system
func (s *Service) DiscoverLibraries(ctx context.Context) ([]TapeLibrary, error) {
	// Use lsscsi to find tape changers
	cmd := exec.CommandContext(ctx, "lsscsi", "-g")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to run lsscsi: %w", err)
	}

	var libraries []TapeLibrary
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")

	for _, line := range lines {
		if line == "" {
			continue
		}

		// Parse lsscsi output: [0:0:0:0] disk ATA ... /dev/sda /dev/sg0
		parts := strings.Fields(line)
		if len(parts) < 4 {
			continue
		}

		deviceType := parts[2]
		devicePath := ""
		sgPath := ""

		// Extract device paths
		for i := 3; i < len(parts); i++ {
			if strings.HasPrefix(parts[i], "/dev/") {
				if strings.HasPrefix(parts[i], "/dev/sg") {
					sgPath = parts[i]
				} else if strings.HasPrefix(parts[i], "/dev/sch") || strings.HasPrefix(parts[i], "/dev/st") {
					devicePath = parts[i]
				}
			}
		}

		// Check for medium changer (tape library)
		if deviceType == "mediumx" || deviceType == "changer" {
			// Get changer information via sg_inq
			changerInfo, err := s.getChangerInfo(ctx, sgPath)
			if err != nil {
				s.logger.Warn("Failed to get changer info", "device", sgPath, "error", err)
				continue
			}

			lib := TapeLibrary{
				Name:              fmt.Sprintf("Library-%s", changerInfo["serial"]),
				SerialNumber:      changerInfo["serial"],
				Vendor:            changerInfo["vendor"],
				Model:             changerInfo["model"],
				ChangerDevicePath: devicePath,
				ChangerStablePath: sgPath,
				IsActive:          true,
				DiscoveredAt:      time.Now(),
			}

			// Get slot and drive count via mtx
			if slotCount, driveCount, err := s.getLibraryCounts(ctx, devicePath); err == nil {
				lib.SlotCount = slotCount
				lib.DriveCount = driveCount
			}

			libraries = append(libraries, lib)
		}
	}

	return libraries, nil
}

// getChangerInfo retrieves changer information via sg_inq
func (s *Service) getChangerInfo(ctx context.Context, sgPath string) (map[string]string, error) {
	cmd := exec.CommandContext(ctx, "sg_inq", "-i", sgPath)
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to run sg_inq: %w", err)
	}

	info := make(map[string]string)
	lines := strings.Split(string(output), "\n")

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "Vendor identification:") {
			info["vendor"] = strings.TrimSpace(strings.TrimPrefix(line, "Vendor identification:"))
		} else if strings.HasPrefix(line, "Product identification:") {
			info["model"] = strings.TrimSpace(strings.TrimPrefix(line, "Product identification:"))
		} else if strings.HasPrefix(line, "Unit serial number:") {
			info["serial"] = strings.TrimSpace(strings.TrimPrefix(line, "Unit serial number:"))
		}
	}

	return info, nil
}

// getLibraryCounts gets slot and drive count via mtx
func (s *Service) getLibraryCounts(ctx context.Context, changerPath string) (slots, drives int, err error) {
	// Use mtx status to get slot count
	cmd := exec.CommandContext(ctx, "mtx", "-f", changerPath, "status")
	output, err := cmd.Output()
	if err != nil {
		return 0, 0, err
	}

	lines := strings.Split(string(output), "\n")
	for _, line := range lines {
		if strings.Contains(line, "Storage Element") {
			// Parse: Storage Element 1:Full (Storage Element 1:Full)
			parts := strings.Fields(line)
			for _, part := range parts {
				if strings.HasPrefix(part, "Element") {
					// Extract number
					numStr := strings.TrimPrefix(part, "Element")
					if num, err := strconv.Atoi(numStr); err == nil {
						if num > slots {
							slots = num
						}
					}
				}
			}
		} else if strings.Contains(line, "Data Transfer Element") {
			drives++
		}
	}

	return slots, drives, nil
}

// DiscoverDrives discovers tape drives for a library
func (s *Service) DiscoverDrives(ctx context.Context, libraryID, changerPath string) ([]TapeDrive, error) {
	// Use lsscsi to find tape drives
	cmd := exec.CommandContext(ctx, "lsscsi", "-g")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to run lsscsi: %w", err)
	}

	var drives []TapeDrive
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	driveNum := 1

	for _, line := range lines {
		if line == "" {
			continue
		}

		parts := strings.Fields(line)
		if len(parts) < 4 {
			continue
		}

		deviceType := parts[2]
		devicePath := ""
		sgPath := ""

		for i := 3; i < len(parts); i++ {
			if strings.HasPrefix(parts[i], "/dev/") {
				if strings.HasPrefix(parts[i], "/dev/sg") {
					sgPath = parts[i]
				} else if strings.HasPrefix(parts[i], "/dev/st") || strings.HasPrefix(parts[i], "/dev/nst") {
					devicePath = parts[i]
				}
			}
		}

		// Check for tape drive
		if deviceType == "tape" && devicePath != "" {
			driveInfo, err := s.getDriveInfo(ctx, sgPath)
			if err != nil {
				s.logger.Warn("Failed to get drive info", "device", sgPath, "error", err)
				continue
			}

			drive := TapeDrive{
				LibraryID:   libraryID,
				DriveNumber: driveNum,
				DevicePath:  devicePath,
				StablePath:  sgPath,
				Vendor:      driveInfo["vendor"],
				Model:       driveInfo["model"],
				SerialNumber: driveInfo["serial"],
				DriveType:   driveInfo["type"],
				Status:      "idle",
				IsActive:    true,
			}

			drives = append(drives, drive)
			driveNum++
		}
	}

	return drives, nil
}

// getDriveInfo retrieves drive information via sg_inq
func (s *Service) getDriveInfo(ctx context.Context, sgPath string) (map[string]string, error) {
	cmd := exec.CommandContext(ctx, "sg_inq", "-i", sgPath)
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to run sg_inq: %w", err)
	}

	info := make(map[string]string)
	lines := strings.Split(string(output), "\n")

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "Vendor identification:") {
			info["vendor"] = strings.TrimSpace(strings.TrimPrefix(line, "Vendor identification:"))
		} else if strings.HasPrefix(line, "Product identification:") {
			info["model"] = strings.TrimSpace(strings.TrimPrefix(line, "Product identification:"))
			// Try to extract drive type from model (e.g., "LTO-8")
			if strings.Contains(strings.ToUpper(info["model"]), "LTO-8") {
				info["type"] = "LTO-8"
			} else if strings.Contains(strings.ToUpper(info["model"]), "LTO-9") {
				info["type"] = "LTO-9"
			} else {
				info["type"] = "Unknown"
			}
		} else if strings.HasPrefix(line, "Unit serial number:") {
			info["serial"] = strings.TrimSpace(strings.TrimPrefix(line, "Unit serial number:"))
		}
	}

	return info, nil
}

// PerformInventory performs a slot inventory of the library
func (s *Service) PerformInventory(ctx context.Context, libraryID, changerPath string) ([]TapeSlot, error) {
	// Use mtx to get inventory
	cmd := exec.CommandContext(ctx, "mtx", "-f", changerPath, "status")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to run mtx status: %w", err)
	}

	var slots []TapeSlot
	lines := strings.Split(string(output), "\n")

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.Contains(line, "Storage Element") && strings.Contains(line, ":") {
			// Parse: Storage Element 1:Full (Storage Element 1:Full) [Storage Changer Serial Number]
			parts := strings.Fields(line)
			slotNum := 0
			barcode := ""
			tapePresent := false

			for i, part := range parts {
				if part == "Element" && i+1 < len(parts) {
					// Next part should be the number
					if num, err := strconv.Atoi(strings.TrimSuffix(parts[i+1], ":")); err == nil {
						slotNum = num
					}
				}
				if part == "Full" {
					tapePresent = true
				}
				// Try to extract barcode from brackets
				if strings.HasPrefix(part, "[") && strings.HasSuffix(part, "]") {
					barcode = strings.Trim(part, "[]")
				}
			}

			if slotNum > 0 {
				slot := TapeSlot{
					LibraryID:     libraryID,
					SlotNumber:    slotNum,
					Barcode:       barcode,
					TapePresent:   tapePresent,
					LastUpdatedAt: time.Now(),
				}
				slots = append(slots, slot)
			}
		}
	}

	return slots, nil
}

// LoadTape loads a tape from a slot into a drive
func (s *Service) LoadTape(ctx context.Context, libraryID, changerPath string, slotNumber, driveNumber int) error {
	// Use mtx to load tape
	cmd := exec.CommandContext(ctx, "mtx", "-f", changerPath, "load", strconv.Itoa(slotNumber), strconv.Itoa(driveNumber))
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to load tape: %s: %w", string(output), err)
	}

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

// UnloadTape unloads a tape from a drive to a slot
func (s *Service) UnloadTape(ctx context.Context, libraryID, changerPath string, driveNumber, slotNumber int) error {
	// Use mtx to unload tape
	cmd := exec.CommandContext(ctx, "mtx", "-f", changerPath, "unload", strconv.Itoa(slotNumber), strconv.Itoa(driveNumber))
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to unload tape: %s: %w", string(output), err)
	}

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

// SyncLibraryToDatabase syncs discovered library to database
func (s *Service) SyncLibraryToDatabase(ctx context.Context, library *TapeLibrary) error {
	// Check if library exists
	var existingID string
	err := s.db.QueryRowContext(ctx,
		"SELECT id FROM physical_tape_libraries WHERE serial_number = $1",
		library.SerialNumber,
	).Scan(&existingID)

	if err == sql.ErrNoRows {
		// Insert new library
		query := `
			INSERT INTO physical_tape_libraries (
				name, serial_number, vendor, model,
				changer_device_path, changer_stable_path,
				slot_count, drive_count, is_active, discovered_at
			) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
			RETURNING id, created_at, updated_at
		`
		err = s.db.QueryRowContext(ctx, query,
			library.Name, library.SerialNumber, library.Vendor, library.Model,
			library.ChangerDevicePath, library.ChangerStablePath,
			library.SlotCount, library.DriveCount, library.IsActive, library.DiscoveredAt,
		).Scan(&library.ID, &library.CreatedAt, &library.UpdatedAt)
		if err != nil {
			return fmt.Errorf("failed to insert library: %w", err)
		}
	} else if err == nil {
		// Update existing library
		query := `
			UPDATE physical_tape_libraries SET
				name = $1, vendor = $2, model = $3,
				changer_device_path = $4, changer_stable_path = $5,
				slot_count = $6, drive_count = $7,
				updated_at = NOW()
			WHERE id = $8
		`
		_, err = s.db.ExecContext(ctx, query,
			library.Name, library.Vendor, library.Model,
			library.ChangerDevicePath, library.ChangerStablePath,
			library.SlotCount, library.DriveCount, existingID,
		)
		if err != nil {
			return fmt.Errorf("failed to update library: %w", err)
		}
		library.ID = existingID
	} else {
		return fmt.Errorf("failed to check library existence: %w", err)
	}

	return nil
}

