package tape_vtl

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"database/sql"

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

// MHVTLMonitor monitors mhvtl configuration files and syncs to database
type MHVTLMonitor struct {
	service    *Service
	logger     *logger.Logger
	configPath string
	interval   time.Duration
	stopCh     chan struct{}
}

// NewMHVTLMonitor creates a new MHVTL monitor service
func NewMHVTLMonitor(db *database.DB, log *logger.Logger, configPath string, interval time.Duration) *MHVTLMonitor {
	return &MHVTLMonitor{
		service:    NewService(db, log),
		logger:     log,
		configPath: configPath,
		interval:   interval,
		stopCh:     make(chan struct{}),
	}
}

// Start starts the MHVTL monitor background service
func (m *MHVTLMonitor) Start(ctx context.Context) {
	m.logger.Info("Starting MHVTL monitor service", "config_path", m.configPath, "interval", m.interval)
	ticker := time.NewTicker(m.interval)
	defer ticker.Stop()

	// Run initial sync immediately
	m.syncMHVTL(ctx)

	for {
		select {
		case <-ctx.Done():
			m.logger.Info("MHVTL monitor service stopped")
			return
		case <-m.stopCh:
			m.logger.Info("MHVTL monitor service stopped")
			return
		case <-ticker.C:
			m.syncMHVTL(ctx)
		}
	}
}

// Stop stops the MHVTL monitor service
func (m *MHVTLMonitor) Stop() {
	close(m.stopCh)
}

// syncMHVTL parses mhvtl configuration and syncs to database
func (m *MHVTLMonitor) syncMHVTL(ctx context.Context) {
	m.logger.Info("Running MHVTL configuration sync")

	deviceConfPath := filepath.Join(m.configPath, "device.conf")
	if _, err := os.Stat(deviceConfPath); os.IsNotExist(err) {
		m.logger.Warn("MHVTL device.conf not found", "path", deviceConfPath)
		return
	}

	// Parse device.conf to get libraries and drives
	libraries, drives, err := m.parseDeviceConf(ctx, deviceConfPath)
	if err != nil {
		m.logger.Error("Failed to parse device.conf", "error", err)
		return
	}

	m.logger.Info("Parsed MHVTL configuration", "libraries", len(libraries), "drives", len(drives))

	// Log parsed drives for debugging
	for _, drive := range drives {
		m.logger.Debug("Parsed drive", "drive_id", drive.DriveID, "library_id", drive.LibraryID, "slot", drive.Slot)
	}

	// Sync libraries to database
	for _, lib := range libraries {
		if err := m.syncLibrary(ctx, lib); err != nil {
			m.logger.Error("Failed to sync library", "library_id", lib.LibraryID, "error", err)
		}
	}

	// Sync drives to database
	for _, drive := range drives {
		if err := m.syncDrive(ctx, drive); err != nil {
			m.logger.Error("Failed to sync drive", "drive_id", drive.DriveID, "library_id", drive.LibraryID, "slot", drive.Slot, "error", err)
		} else {
			m.logger.Debug("Synced drive", "drive_id", drive.DriveID, "library_id", drive.LibraryID, "slot", drive.Slot)
		}
	}

	// Parse library_contents files to get tapes
	for _, lib := range libraries {
		contentsPath := filepath.Join(m.configPath, fmt.Sprintf("library_contents.%d", lib.LibraryID))
		if err := m.syncLibraryContents(ctx, lib.LibraryID, contentsPath); err != nil {
			m.logger.Warn("Failed to sync library contents", "library_id", lib.LibraryID, "error", err)
		}
	}

	m.logger.Info("MHVTL configuration sync completed")
}

// LibraryInfo represents a library from device.conf
type LibraryInfo struct {
	LibraryID     int
	Vendor        string
	Product       string
	SerialNumber  string
	HomeDirectory string
	Channel       string
	Target        string
	LUN           string
}

// DriveInfo represents a drive from device.conf
type DriveInfo struct {
	DriveID      int
	LibraryID    int
	Slot         int
	Vendor       string
	Product      string
	SerialNumber string
	Channel      string
	Target       string
	LUN          string
}

// parseDeviceConf parses mhvtl device.conf file
func (m *MHVTLMonitor) parseDeviceConf(ctx context.Context, path string) ([]LibraryInfo, []DriveInfo, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to open device.conf: %w", err)
	}
	defer file.Close()

	var libraries []LibraryInfo
	var drives []DriveInfo

	scanner := bufio.NewScanner(file)
	var currentLibrary *LibraryInfo
	var currentDrive *DriveInfo

	libraryRegex := regexp.MustCompile(`^Library:\s+(\d+)\s+CHANNEL:\s+(\S+)\s+TARGET:\s+(\S+)\s+LUN:\s+(\S+)`)
	driveRegex := regexp.MustCompile(`^Drive:\s+(\d+)\s+CHANNEL:\s+(\S+)\s+TARGET:\s+(\S+)\s+LUN:\s+(\S+)`)
	libraryIDRegex := regexp.MustCompile(`Library ID:\s+(\d+)\s+Slot:\s+(\d+)`)

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())

		// Skip comments and empty lines
		if strings.HasPrefix(line, "#") || line == "" {
			continue
		}

		// Check for Library entry
		if matches := libraryRegex.FindStringSubmatch(line); matches != nil {
			if currentLibrary != nil {
				libraries = append(libraries, *currentLibrary)
			}
			libID, _ := strconv.Atoi(matches[1])
			currentLibrary = &LibraryInfo{
				LibraryID: libID,
				Channel:   matches[2],
				Target:    matches[3],
				LUN:       matches[4],
			}
			currentDrive = nil
			continue
		}

		// Check for Drive entry
		if matches := driveRegex.FindStringSubmatch(line); matches != nil {
			if currentDrive != nil {
				drives = append(drives, *currentDrive)
			}
			driveID, _ := strconv.Atoi(matches[1])
			currentDrive = &DriveInfo{
				DriveID: driveID,
				Channel: matches[2],
				Target:  matches[3],
				LUN:     matches[4],
			}
			// Library ID and Slot might be on the same line or next line
			if matches := libraryIDRegex.FindStringSubmatch(line); matches != nil {
				libID, _ := strconv.Atoi(matches[1])
				slot, _ := strconv.Atoi(matches[2])
				currentDrive.LibraryID = libID
				currentDrive.Slot = slot
			}
			continue
		}

		// Parse library fields (only if we're in a library section and not in a drive section)
		if currentLibrary != nil && currentDrive == nil {
			// Handle both "Vendor identification:" and " Vendor identification:" (with leading space)
			if strings.Contains(line, "Vendor identification:") {
				parts := strings.Split(line, "Vendor identification:")
				if len(parts) > 1 {
					currentLibrary.Vendor = strings.TrimSpace(parts[1])
					m.logger.Debug("Parsed vendor", "vendor", currentLibrary.Vendor, "library_id", currentLibrary.LibraryID)
				}
			} else if strings.Contains(line, "Product identification:") {
				parts := strings.Split(line, "Product identification:")
				if len(parts) > 1 {
					currentLibrary.Product = strings.TrimSpace(parts[1])
					m.logger.Info("Parsed library product", "product", currentLibrary.Product, "library_id", currentLibrary.LibraryID)
				}
			} else if strings.Contains(line, "Unit serial number:") {
				parts := strings.Split(line, "Unit serial number:")
				if len(parts) > 1 {
					currentLibrary.SerialNumber = strings.TrimSpace(parts[1])
				}
			} else if strings.Contains(line, "Home directory:") {
				parts := strings.Split(line, "Home directory:")
				if len(parts) > 1 {
					currentLibrary.HomeDirectory = strings.TrimSpace(parts[1])
				}
			}
		}

		// Parse drive fields
		if currentDrive != nil {
			// Check for Library ID and Slot first (can be on separate line)
			if strings.Contains(line, "Library ID:") && strings.Contains(line, "Slot:") {
				matches := libraryIDRegex.FindStringSubmatch(line)
				if matches != nil {
					libID, _ := strconv.Atoi(matches[1])
					slot, _ := strconv.Atoi(matches[2])
					currentDrive.LibraryID = libID
					currentDrive.Slot = slot
					m.logger.Debug("Parsed drive Library ID and Slot", "drive_id", currentDrive.DriveID, "library_id", libID, "slot", slot)
					continue
				}
			}
			// Handle both "Vendor identification:" and " Vendor identification:" (with leading space)
			if strings.Contains(line, "Vendor identification:") {
				parts := strings.Split(line, "Vendor identification:")
				if len(parts) > 1 {
					currentDrive.Vendor = strings.TrimSpace(parts[1])
				}
			} else if strings.Contains(line, "Product identification:") {
				parts := strings.Split(line, "Product identification:")
				if len(parts) > 1 {
					currentDrive.Product = strings.TrimSpace(parts[1])
				}
			} else if strings.Contains(line, "Unit serial number:") {
				parts := strings.Split(line, "Unit serial number:")
				if len(parts) > 1 {
					currentDrive.SerialNumber = strings.TrimSpace(parts[1])
				}
			}
		}
	}

	// Add last library and drive
	if currentLibrary != nil {
		libraries = append(libraries, *currentLibrary)
	}
	if currentDrive != nil {
		drives = append(drives, *currentDrive)
	}

	if err := scanner.Err(); err != nil {
		return nil, nil, fmt.Errorf("error reading device.conf: %w", err)
	}

	return libraries, drives, nil
}

// syncLibrary syncs a library to database
func (m *MHVTLMonitor) syncLibrary(ctx context.Context, libInfo LibraryInfo) error {
	// Check if library exists by mhvtl_library_id
	var existingID string
	err := m.service.db.QueryRowContext(ctx,
		"SELECT id FROM virtual_tape_libraries WHERE mhvtl_library_id = $1",
		libInfo.LibraryID,
	).Scan(&existingID)

	m.logger.Debug("Syncing library", "library_id", libInfo.LibraryID, "vendor", libInfo.Vendor, "product", libInfo.Product)

	// Use product identification for library name (without library ID)
	libraryName := fmt.Sprintf("VTL-%d", libInfo.LibraryID)
	if libInfo.Product != "" {
		// Use only product name, without library ID
		libraryName = libInfo.Product
		m.logger.Info("Using product for library name", "product", libInfo.Product, "library_id", libInfo.LibraryID, "name", libraryName)
	} else if libInfo.Vendor != "" {
		libraryName = libInfo.Vendor
		m.logger.Info("Using vendor for library name (product not available)", "vendor", libInfo.Vendor, "library_id", libInfo.LibraryID)
	}

	if err == sql.ErrNoRows {
		// Create new library
		// Get backing store path from mhvtl.conf
		backingStorePath := "/opt/mhvtl"
		if libInfo.HomeDirectory != "" {
			backingStorePath = libInfo.HomeDirectory
		}

		// Count slots and drives from library_contents file
		contentsPath := filepath.Join(m.configPath, fmt.Sprintf("library_contents.%d", libInfo.LibraryID))
		slotCount, driveCount := m.countSlotsAndDrives(contentsPath)

		_, err = m.service.db.ExecContext(ctx, `
			INSERT INTO virtual_tape_libraries (
				name, description, mhvtl_library_id, backing_store_path,
				vendor, slot_count, drive_count, is_active
			) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		`, libraryName, fmt.Sprintf("MHVTL Library %d (%s)", libInfo.LibraryID, libInfo.Product),
			libInfo.LibraryID, backingStorePath, libInfo.Vendor, slotCount, driveCount, true)
		if err != nil {
			return fmt.Errorf("failed to insert library: %w", err)
		}
		m.logger.Info("Created virtual library from MHVTL", "library_id", libInfo.LibraryID, "name", libraryName)
	} else if err == nil {
		// Update existing library - also update name if product is available
		updateName := libraryName
		// If product exists and current name doesn't match, update it
		if libInfo.Product != "" {
			var currentName string
			err := m.service.db.QueryRowContext(ctx,
				"SELECT name FROM virtual_tape_libraries WHERE id = $1", existingID,
			).Scan(&currentName)
			if err == nil {
				// Use only product name, without library ID
				expectedName := libInfo.Product
				if currentName != expectedName {
					updateName = expectedName
					m.logger.Info("Updating library name", "old", currentName, "new", updateName, "product", libInfo.Product)
				}
			}
		}

		m.logger.Info("Updating existing library", "library_id", libInfo.LibraryID, "product", libInfo.Product, "vendor", libInfo.Vendor, "old_name", libraryName, "new_name", updateName)
		_, err = m.service.db.ExecContext(ctx, `
			UPDATE virtual_tape_libraries SET
				name = $1, description = $2, backing_store_path = $3,
				vendor = $4, is_active = $5, updated_at = NOW()
			WHERE id = $6
		`, updateName, fmt.Sprintf("MHVTL Library %d (%s)", libInfo.LibraryID, libInfo.Product),
			libInfo.HomeDirectory, libInfo.Vendor, true, existingID)
		if err != nil {
			return fmt.Errorf("failed to update library: %w", err)
		}
		m.logger.Debug("Updated virtual library from MHVTL", "library_id", libInfo.LibraryID)
	} else {
		return fmt.Errorf("failed to check library existence: %w", err)
	}

	return nil
}

// syncDrive syncs a drive to database
func (m *MHVTLMonitor) syncDrive(ctx context.Context, driveInfo DriveInfo) error {
	// Get library ID from mhvtl_library_id
	var libraryID string
	err := m.service.db.QueryRowContext(ctx,
		"SELECT id FROM virtual_tape_libraries WHERE mhvtl_library_id = $1",
		driveInfo.LibraryID,
	).Scan(&libraryID)
	if err != nil {
		return fmt.Errorf("library not found for drive: %w", err)
	}

	// Calculate drive number from slot (drives are typically in slots 1, 2, 3, etc.)
	driveNumber := driveInfo.Slot

	// Check if drive exists
	var existingID string
	err = m.service.db.QueryRowContext(ctx,
		"SELECT id FROM virtual_tape_drives WHERE library_id = $1 AND drive_number = $2",
		libraryID, driveNumber,
	).Scan(&existingID)

	// Get device path (typically /dev/stX or /dev/nstX)
	devicePath := fmt.Sprintf("/dev/st%d", driveInfo.DriveID-10) // Drive 11 -> st1, Drive 12 -> st2, etc.
	stablePath := fmt.Sprintf("/dev/tape/by-id/scsi-%s", driveInfo.SerialNumber)

	if err == sql.ErrNoRows {
		// Create new drive
		_, err = m.service.db.ExecContext(ctx, `
			INSERT INTO virtual_tape_drives (
				library_id, drive_number, device_path, stable_path, status, is_active
			) VALUES ($1, $2, $3, $4, $5, $6)
		`, libraryID, driveNumber, devicePath, stablePath, "idle", true)
		if err != nil {
			return fmt.Errorf("failed to insert drive: %w", err)
		}
		m.logger.Info("Created virtual drive from MHVTL", "drive_id", driveInfo.DriveID, "library_id", driveInfo.LibraryID)
	} else if err == nil {
		// Update existing drive
		_, err = m.service.db.ExecContext(ctx, `
			UPDATE virtual_tape_drives SET
				device_path = $1, stable_path = $2, is_active = $3, updated_at = NOW()
			WHERE id = $4
		`, devicePath, stablePath, true, existingID)
		if err != nil {
			return fmt.Errorf("failed to update drive: %w", err)
		}
		m.logger.Debug("Updated virtual drive from MHVTL", "drive_id", driveInfo.DriveID)
	} else {
		return fmt.Errorf("failed to check drive existence: %w", err)
	}

	return nil
}

// syncLibraryContents syncs tapes from library_contents file
func (m *MHVTLMonitor) syncLibraryContents(ctx context.Context, libraryID int, contentsPath string) error {
	// Get library ID from database
	var dbLibraryID string
	err := m.service.db.QueryRowContext(ctx,
		"SELECT id FROM virtual_tape_libraries WHERE mhvtl_library_id = $1",
		libraryID,
	).Scan(&dbLibraryID)
	if err != nil {
		return fmt.Errorf("library not found: %w", err)
	}

	// Get backing store path
	var backingStorePath string
	err = m.service.db.QueryRowContext(ctx,
		"SELECT backing_store_path FROM virtual_tape_libraries WHERE id = $1",
		dbLibraryID,
	).Scan(&backingStorePath)
	if err != nil {
		return fmt.Errorf("failed to get backing store path: %w", err)
	}

	file, err := os.Open(contentsPath)
	if err != nil {
		return fmt.Errorf("failed to open library_contents file: %w", err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	slotRegex := regexp.MustCompile(`^Slot\s+(\d+):\s+(.+)`)

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())

		// Skip comments and empty lines
		if strings.HasPrefix(line, "#") || line == "" {
			continue
		}

		matches := slotRegex.FindStringSubmatch(line)
		if matches != nil {
			slotNumber, _ := strconv.Atoi(matches[1])
			barcode := strings.TrimSpace(matches[2])

			if barcode == "" || barcode == "?" {
				continue // Empty slot
			}

			// Determine tape type from barcode suffix
			tapeType := "LTO-8" // Default
			if len(barcode) >= 2 {
				suffix := barcode[len(barcode)-2:]
				switch suffix {
				case "L1":
					tapeType = "LTO-1"
				case "L2":
					tapeType = "LTO-2"
				case "L3":
					tapeType = "LTO-3"
				case "L4":
					tapeType = "LTO-4"
				case "L5":
					tapeType = "LTO-5"
				case "L6":
					tapeType = "LTO-6"
				case "L7":
					tapeType = "LTO-7"
				case "L8":
					tapeType = "LTO-8"
				case "L9":
					tapeType = "LTO-9"
				}
			}

			// Check if tape exists
			var existingID string
			err := m.service.db.QueryRowContext(ctx,
				"SELECT id FROM virtual_tapes WHERE library_id = $1 AND barcode = $2",
				dbLibraryID, barcode,
			).Scan(&existingID)

			imagePath := filepath.Join(backingStorePath, "tapes", fmt.Sprintf("%s.img", barcode))
			defaultSize := int64(15 * 1024 * 1024 * 1024 * 1024) // 15 TB default for LTO-8

			if err == sql.ErrNoRows {
				// Create new tape
				_, err = m.service.db.ExecContext(ctx, `
					INSERT INTO virtual_tapes (
						library_id, barcode, slot_number, image_file_path,
						size_bytes, used_bytes, tape_type, status
					) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
				`, dbLibraryID, barcode, slotNumber, imagePath, defaultSize, 0, tapeType, "idle")
				if err != nil {
					m.logger.Warn("Failed to insert tape", "barcode", barcode, "error", err)
				} else {
					m.logger.Debug("Created virtual tape from MHVTL", "barcode", barcode, "slot", slotNumber)
				}
			} else if err == nil {
				// Update existing tape slot
				_, err = m.service.db.ExecContext(ctx, `
					UPDATE virtual_tapes SET
						slot_number = $1, tape_type = $2, updated_at = NOW()
					WHERE id = $3
				`, slotNumber, tapeType, existingID)
				if err != nil {
					m.logger.Warn("Failed to update tape", "barcode", barcode, "error", err)
				}
			}
		}
	}

	return scanner.Err()
}

// countSlotsAndDrives counts slots and drives from library_contents file
func (m *MHVTLMonitor) countSlotsAndDrives(contentsPath string) (slotCount, driveCount int) {
	file, err := os.Open(contentsPath)
	if err != nil {
		return 10, 2 // Default values
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	slotRegex := regexp.MustCompile(`^Slot\s+(\d+):`)
	driveRegex := regexp.MustCompile(`^Drive\s+(\d+):`)

	maxSlot := 0
	driveCount = 0

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "#") || line == "" {
			continue
		}

		if matches := slotRegex.FindStringSubmatch(line); matches != nil {
			slot, _ := strconv.Atoi(matches[1])
			if slot > maxSlot {
				maxSlot = slot
			}
		}
		if matches := driveRegex.FindStringSubmatch(line); matches != nil {
			driveCount++
		}
	}

	slotCount = maxSlot
	if slotCount == 0 {
		slotCount = 10 // Default
	}
	if driveCount == 0 {
		driveCount = 2 // Default
	}

	return slotCount, driveCount
}
