package storage

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

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

// DiskService handles disk discovery and management
type DiskService struct {
	db     *database.DB
	logger *logger.Logger
}

// NewDiskService creates a new disk service
func NewDiskService(db *database.DB, log *logger.Logger) *DiskService {
	return &DiskService{
		db:     db,
		logger: log,
	}
}

// PhysicalDisk represents a physical disk
type PhysicalDisk struct {
	ID           string                 `json:"id"`
	DevicePath   string                 `json:"device_path"`
	Vendor       string                 `json:"vendor"`
	Model        string                 `json:"model"`
	SerialNumber string                 `json:"serial_number"`
	SizeBytes    int64                  `json:"size_bytes"`
	SectorSize   int                    `json:"sector_size"`
	IsSSD        bool                   `json:"is_ssd"`
	HealthStatus string                 `json:"health_status"`
	HealthDetails map[string]interface{} `json:"health_details"`
	IsUsed       bool                   `json:"is_used"`
	CreatedAt    time.Time              `json:"created_at"`
	UpdatedAt    time.Time              `json:"updated_at"`
}

// DiscoverDisks discovers physical disks on the system
func (s *DiskService) DiscoverDisks(ctx context.Context) ([]PhysicalDisk, error) {
	// Use lsblk to discover block devices
	cmd := exec.CommandContext(ctx, "lsblk", "-b", "-o", "NAME,SIZE,TYPE", "-J")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to run lsblk: %w", err)
	}

	var lsblkOutput struct {
		BlockDevices []struct {
			Name string      `json:"name"`
			Size interface{} `json:"size"` // Can be string or number
			Type string      `json:"type"`
		} `json:"blockdevices"`
	}

	if err := json.Unmarshal(output, &lsblkOutput); err != nil {
		return nil, fmt.Errorf("failed to parse lsblk output: %w", err)
	}

	var disks []PhysicalDisk
	for _, device := range lsblkOutput.BlockDevices {
		// Only process disk devices (not partitions)
		if device.Type != "disk" {
			continue
		}

		devicePath := "/dev/" + device.Name
		disk, err := s.getDiskInfo(ctx, devicePath)
		if err != nil {
			s.logger.Warn("Failed to get disk info", "device", devicePath, "error", err)
			continue
		}

		// Parse size (can be string or number)
		var sizeBytes int64
		switch v := device.Size.(type) {
		case string:
			if size, err := strconv.ParseInt(v, 10, 64); err == nil {
				sizeBytes = size
			}
		case float64:
			sizeBytes = int64(v)
		case int64:
			sizeBytes = v
		case int:
			sizeBytes = int64(v)
		}
		disk.SizeBytes = sizeBytes

		disks = append(disks, *disk)
	}

	return disks, nil
}

// getDiskInfo retrieves detailed information about a disk
func (s *DiskService) getDiskInfo(ctx context.Context, devicePath string) (*PhysicalDisk, error) {
	disk := &PhysicalDisk{
		DevicePath:   devicePath,
		HealthStatus: "unknown",
		HealthDetails: make(map[string]interface{}),
	}

	// Get disk information using udevadm
	cmd := exec.CommandContext(ctx, "udevadm", "info", "--query=property", "--name="+devicePath)
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to get udev info: %w", err)
	}

	props := parseUdevProperties(string(output))
	disk.Vendor = props["ID_VENDOR"]
	disk.Model = props["ID_MODEL"]
	disk.SerialNumber = props["ID_SERIAL_SHORT"]
	
	if props["ID_ATA_ROTATION_RATE"] == "0" {
		disk.IsSSD = true
	}

	// Get sector size
	if sectorSize, err := strconv.Atoi(props["ID_SECTOR_SIZE"]); err == nil {
		disk.SectorSize = sectorSize
	}

	// Check if disk is in use (part of a volume group)
	disk.IsUsed = s.isDiskInUse(ctx, devicePath)

	// Get health status (simplified - would use smartctl in production)
	disk.HealthStatus = "healthy" // Placeholder

	return disk, nil
}

// parseUdevProperties parses udevadm output
func parseUdevProperties(output string) map[string]string {
	props := make(map[string]string)
	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.SplitN(line, "=", 2)
		if len(parts) == 2 {
			props[parts[0]] = parts[1]
		}
	}
	return props
}

// isDiskInUse checks if a disk is part of a volume group
func (s *DiskService) isDiskInUse(ctx context.Context, devicePath string) bool {
	cmd := exec.CommandContext(ctx, "pvdisplay", devicePath)
	err := cmd.Run()
	return err == nil
}

// SyncDisksToDatabase syncs discovered disks to the database
func (s *DiskService) SyncDisksToDatabase(ctx context.Context) error {
	disks, err := s.DiscoverDisks(ctx)
	if err != nil {
		return fmt.Errorf("failed to discover disks: %w", err)
	}

	for _, disk := range disks {
		// Check if disk exists
		var existingID string
		err := s.db.QueryRowContext(ctx,
			"SELECT id FROM physical_disks WHERE device_path = $1",
			disk.DevicePath,
		).Scan(&existingID)

		healthDetailsJSON, _ := json.Marshal(disk.HealthDetails)

		if err == sql.ErrNoRows {
			// Insert new disk
			_, err = s.db.ExecContext(ctx, `
				INSERT INTO physical_disks (
					device_path, vendor, model, serial_number, size_bytes,
					sector_size, is_ssd, health_status, health_details, is_used
				) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
			`, disk.DevicePath, disk.Vendor, disk.Model, disk.SerialNumber,
				disk.SizeBytes, disk.SectorSize, disk.IsSSD,
				disk.HealthStatus, healthDetailsJSON, disk.IsUsed)
			if err != nil {
				s.logger.Error("Failed to insert disk", "device", disk.DevicePath, "error", err)
			}
		} else if err == nil {
			// Update existing disk
			_, err = s.db.ExecContext(ctx, `
				UPDATE physical_disks SET
					vendor = $1, model = $2, serial_number = $3,
					size_bytes = $4, sector_size = $5, is_ssd = $6,
					health_status = $7, health_details = $8, is_used = $9,
					updated_at = NOW()
				WHERE id = $10
			`, disk.Vendor, disk.Model, disk.SerialNumber,
				disk.SizeBytes, disk.SectorSize, disk.IsSSD,
				disk.HealthStatus, healthDetailsJSON, disk.IsUsed, existingID)
			if err != nil {
				s.logger.Error("Failed to update disk", "device", disk.DevicePath, "error", err)
			}
		}
	}

	return nil
}

