package storage

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

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

// LVMService handles LVM operations
type LVMService struct {
	db     *database.DB
	logger *logger.Logger
}

// NewLVMService creates a new LVM service
func NewLVMService(db *database.DB, log *logger.Logger) *LVMService {
	return &LVMService{
		db:     db,
		logger: log,
	}
}

// VolumeGroup represents an LVM volume group
type VolumeGroup struct {
	ID              string   `json:"id"`
	Name            string   `json:"name"`
	SizeBytes       int64    `json:"size_bytes"`
	FreeBytes       int64    `json:"free_bytes"`
	PhysicalVolumes []string `json:"physical_volumes"`
	CreatedAt       time.Time `json:"created_at"`
	UpdatedAt       time.Time `json:"updated_at"`
}

// Repository represents a disk repository (logical volume)
type Repository struct {
	ID                    string    `json:"id"`
	Name                  string    `json:"name"`
	Description           string    `json:"description"`
	VolumeGroup           string    `json:"volume_group"`
	LogicalVolume         string    `json:"logical_volume"`
	SizeBytes             int64     `json:"size_bytes"`
	UsedBytes             int64     `json:"used_bytes"`
	FilesystemType        string    `json:"filesystem_type"`
	MountPoint            string    `json:"mount_point"`
	IsActive              bool      `json:"is_active"`
	WarningThresholdPercent int    `json:"warning_threshold_percent"`
	CriticalThresholdPercent int   `json:"critical_threshold_percent"`
	CreatedAt             time.Time `json:"created_at"`
	UpdatedAt             time.Time `json:"updated_at"`
	CreatedBy             string    `json:"created_by"`
}

// ListVolumeGroups lists all volume groups
func (s *LVMService) ListVolumeGroups(ctx context.Context) ([]VolumeGroup, error) {
	cmd := exec.CommandContext(ctx, "vgs", "--units=b", "--noheadings", "--nosuffix", "-o", "vg_name,vg_size,vg_free,pv_name")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to list volume groups: %w", err)
	}

	vgMap := make(map[string]*VolumeGroup)
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")

	for _, line := range lines {
		if line == "" {
			continue
		}
		fields := strings.Fields(line)
		if len(fields) < 3 {
			continue
		}

		vgName := fields[0]
		vgSize, _ := strconv.ParseInt(fields[1], 10, 64)
		vgFree, _ := strconv.ParseInt(fields[2], 10, 64)
		pvName := ""
		if len(fields) > 3 {
			pvName = fields[3]
		}

		if vg, exists := vgMap[vgName]; exists {
			if pvName != "" {
				vg.PhysicalVolumes = append(vg.PhysicalVolumes, pvName)
			}
		} else {
			vgMap[vgName] = &VolumeGroup{
				Name:            vgName,
				SizeBytes:       vgSize,
				FreeBytes:       vgFree,
				PhysicalVolumes: []string{},
			}
			if pvName != "" {
				vgMap[vgName].PhysicalVolumes = append(vgMap[vgName].PhysicalVolumes, pvName)
			}
		}
	}

	var vgs []VolumeGroup
	for _, vg := range vgMap {
		vgs = append(vgs, *vg)
	}

	return vgs, nil
}

// CreateRepository creates a new repository (logical volume)
func (s *LVMService) CreateRepository(ctx context.Context, name, vgName string, sizeBytes int64, createdBy string) (*Repository, error) {
	// Generate logical volume name
	lvName := "calypso-" + name

	// Create logical volume
	cmd := exec.CommandContext(ctx, "lvcreate", "-L", fmt.Sprintf("%dB", sizeBytes), "-n", lvName, vgName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed to create logical volume: %s: %w", string(output), err)
	}

	// Get device path
	devicePath := fmt.Sprintf("/dev/%s/%s", vgName, lvName)

	// Create filesystem (XFS)
	cmd = exec.CommandContext(ctx, "mkfs.xfs", "-f", devicePath)
	output, err = cmd.CombinedOutput()
	if err != nil {
		// Cleanup: remove LV if filesystem creation fails
		exec.CommandContext(ctx, "lvremove", "-f", fmt.Sprintf("%s/%s", vgName, lvName)).Run()
		return nil, fmt.Errorf("failed to create filesystem: %s: %w", string(output), err)
	}

	// Insert into database
	query := `
		INSERT INTO disk_repositories (
			name, volume_group, logical_volume, size_bytes, used_bytes,
			filesystem_type, is_active, created_by
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id, created_at, updated_at
	`

	var repo Repository
	err = s.db.QueryRowContext(ctx, query,
		name, vgName, lvName, sizeBytes, 0, "xfs", true, createdBy,
	).Scan(&repo.ID, &repo.CreatedAt, &repo.UpdatedAt)
	if err != nil {
		// Cleanup: remove LV if database insert fails
		exec.CommandContext(ctx, "lvremove", "-f", fmt.Sprintf("%s/%s", vgName, lvName)).Run()
		return nil, fmt.Errorf("failed to save repository to database: %w", err)
	}

	repo.Name = name
	repo.VolumeGroup = vgName
	repo.LogicalVolume = lvName
	repo.SizeBytes = sizeBytes
	repo.UsedBytes = 0
	repo.FilesystemType = "xfs"
	repo.IsActive = true
	repo.WarningThresholdPercent = 80
	repo.CriticalThresholdPercent = 90
	repo.CreatedBy = createdBy

	s.logger.Info("Repository created", "name", name, "size_bytes", sizeBytes)
	return &repo, nil
}

// GetRepository retrieves a repository by ID
func (s *LVMService) GetRepository(ctx context.Context, id string) (*Repository, error) {
	query := `
		SELECT id, name, description, volume_group, logical_volume,
		       size_bytes, used_bytes, filesystem_type, mount_point,
		       is_active, warning_threshold_percent, critical_threshold_percent,
		       created_at, updated_at, created_by
		FROM disk_repositories
		WHERE id = $1
	`

	var repo Repository
	err := s.db.QueryRowContext(ctx, query, id).Scan(
		&repo.ID, &repo.Name, &repo.Description, &repo.VolumeGroup,
		&repo.LogicalVolume, &repo.SizeBytes, &repo.UsedBytes,
		&repo.FilesystemType, &repo.MountPoint, &repo.IsActive,
		&repo.WarningThresholdPercent, &repo.CriticalThresholdPercent,
		&repo.CreatedAt, &repo.UpdatedAt, &repo.CreatedBy,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("repository not found")
		}
		return nil, fmt.Errorf("failed to get repository: %w", err)
	}

	// Update used bytes from actual filesystem
	s.updateRepositoryUsage(ctx, &repo)

	return &repo, nil
}

// ListRepositories lists all repositories
func (s *LVMService) ListRepositories(ctx context.Context) ([]Repository, error) {
	query := `
		SELECT id, name, description, volume_group, logical_volume,
		       size_bytes, used_bytes, filesystem_type, mount_point,
		       is_active, warning_threshold_percent, critical_threshold_percent,
		       created_at, updated_at, created_by
		FROM disk_repositories
		ORDER BY name
	`

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

	var repos []Repository
	for rows.Next() {
		var repo Repository
		err := rows.Scan(
			&repo.ID, &repo.Name, &repo.Description, &repo.VolumeGroup,
			&repo.LogicalVolume, &repo.SizeBytes, &repo.UsedBytes,
			&repo.FilesystemType, &repo.MountPoint, &repo.IsActive,
			&repo.WarningThresholdPercent, &repo.CriticalThresholdPercent,
			&repo.CreatedAt, &repo.UpdatedAt, &repo.CreatedBy,
		)
		if err != nil {
			s.logger.Error("Failed to scan repository", "error", err)
			continue
		}

		// Update used bytes from actual filesystem
		s.updateRepositoryUsage(ctx, &repo)
		repos = append(repos, repo)
	}

	return repos, rows.Err()
}

// updateRepositoryUsage updates repository usage from filesystem
func (s *LVMService) updateRepositoryUsage(ctx context.Context, repo *Repository) {
	// Use df to get filesystem usage (if mounted)
	// For now, use lvs to get actual size
	cmd := exec.CommandContext(ctx, "lvs", "--units=b", "--noheadings", "--nosuffix", "-o", "lv_size,data_percent", fmt.Sprintf("%s/%s", repo.VolumeGroup, repo.LogicalVolume))
	output, err := cmd.Output()
	if err == nil {
		fields := strings.Fields(string(output))
		if len(fields) >= 1 {
			if size, err := strconv.ParseInt(fields[0], 10, 64); err == nil {
				repo.SizeBytes = size
			}
		}
	}

	// Update in database
	s.db.ExecContext(ctx, `
		UPDATE disk_repositories SET used_bytes = $1, updated_at = NOW() WHERE id = $2
	`, repo.UsedBytes, repo.ID)
}

// DeleteRepository deletes a repository
func (s *LVMService) DeleteRepository(ctx context.Context, id string) error {
	repo, err := s.GetRepository(ctx, id)
	if err != nil {
		return err
	}

	if repo.IsActive {
		return fmt.Errorf("cannot delete active repository")
	}

	// Remove logical volume
	cmd := exec.CommandContext(ctx, "lvremove", "-f", fmt.Sprintf("%s/%s", repo.VolumeGroup, repo.LogicalVolume))
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to remove logical volume: %s: %w", string(output), err)
	}

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

	s.logger.Info("Repository deleted", "id", id, "name", repo.Name)
	return nil
}

