package storage

import (
	"context"
	"fmt"
	"strings"
	"time"

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

// SnapshotService handles ZFS snapshot operations
type SnapshotService struct {
	db     *database.DB
	logger *logger.Logger
}

// NewSnapshotService creates a new snapshot service
func NewSnapshotService(db *database.DB, log *logger.Logger) *SnapshotService {
	return &SnapshotService{
		db:     db,
		logger: log,
	}
}

// Snapshot represents a ZFS snapshot
type Snapshot struct {
	ID           string    `json:"id"`
	Name         string    `json:"name"`          // Full snapshot name (e.g., "pool/dataset@snapshot-name")
	Dataset      string    `json:"dataset"`       // Dataset name (e.g., "pool/dataset")
	SnapshotName string    `json:"snapshot_name"` // Snapshot name only (e.g., "snapshot-name")
	Created      time.Time `json:"created"`
	Referenced   int64     `json:"referenced"` // Size in bytes
	Used         int64     `json:"used"`       // Used space in bytes
	IsLatest     bool      `json:"is_latest"`  // Whether this is the latest snapshot for the dataset
}

// ListSnapshots lists all snapshots, optionally filtered by dataset
func (s *SnapshotService) ListSnapshots(ctx context.Context, datasetFilter string) ([]*Snapshot, error) {
	// Build zfs list command
	args := []string{"list", "-t", "snapshot", "-H", "-o", "name,creation,referenced,used"}
	if datasetFilter != "" {
		// List snapshots for specific dataset
		args = append(args, datasetFilter)
	} else {
		// List all snapshots
		args = append(args, "-r")
	}

	cmd := zfsCommand(ctx, args...)
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to list snapshots: %w", err)
	}

	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	var snapshots []*Snapshot

	// Track latest snapshot per dataset
	latestSnapshots := make(map[string]*Snapshot)

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

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

		fullName := fields[0]
		creationStr := fields[1]
		referencedStr := fields[2]
		usedStr := fields[3]

		// Parse snapshot name (format: pool/dataset@snapshot-name)
		parts := strings.Split(fullName, "@")
		if len(parts) != 2 {
			continue
		}

		dataset := parts[0]
		snapshotName := parts[1]

		// Parse creation time (Unix timestamp)
		var creationTime time.Time
		if timestamp, err := parseZFSUnixTimestamp(creationStr); err == nil {
			creationTime = timestamp
		} else {
			// Fallback to current time if parsing fails
			creationTime = time.Now()
		}

		// Parse sizes
		referenced := parseSnapshotSize(referencedStr)
		used := parseSnapshotSize(usedStr)

		snapshot := &Snapshot{
			ID:           fullName,
			Name:         fullName,
			Dataset:      dataset,
			SnapshotName: snapshotName,
			Created:      creationTime,
			Referenced:   referenced,
			Used:         used,
			IsLatest:     false,
		}

		snapshots = append(snapshots, snapshot)

		// Track latest snapshot per dataset
		if latest, exists := latestSnapshots[dataset]; !exists || snapshot.Created.After(latest.Created) {
			latestSnapshots[dataset] = snapshot
		}
	}

	// Mark latest snapshots
	for _, snapshot := range snapshots {
		if latest, exists := latestSnapshots[snapshot.Dataset]; exists && latest.ID == snapshot.ID {
			snapshot.IsLatest = true
		}
	}

	return snapshots, nil
}

// CreateSnapshot creates a new snapshot
func (s *SnapshotService) CreateSnapshot(ctx context.Context, dataset, snapshotName string, recursive bool) error {
	// Validate dataset exists
	cmd := zfsCommand(ctx, "list", "-H", "-o", "name", dataset)
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("dataset %s does not exist: %w", dataset, err)
	}

	// Build snapshot name
	fullSnapshotName := fmt.Sprintf("%s@%s", dataset, snapshotName)

	// Build command
	args := []string{"snapshot"}
	if recursive {
		args = append(args, "-r")
	}
	args = append(args, fullSnapshotName)

	cmd = zfsCommand(ctx, args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to create snapshot: %s: %w", string(output), err)
	}

	s.logger.Info("Snapshot created", "snapshot", fullSnapshotName, "dataset", dataset, "recursive", recursive)
	return nil
}

// DeleteSnapshot deletes a snapshot
func (s *SnapshotService) DeleteSnapshot(ctx context.Context, snapshotName string, recursive bool) error {
	// Build command
	args := []string{"destroy"}
	if recursive {
		args = append(args, "-r")
	}
	args = append(args, snapshotName)

	cmd := zfsCommand(ctx, args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to delete snapshot: %s: %w", string(output), err)
	}

	s.logger.Info("Snapshot deleted", "snapshot", snapshotName, "recursive", recursive)
	return nil
}

// RollbackSnapshot rolls back a dataset to a snapshot
func (s *SnapshotService) RollbackSnapshot(ctx context.Context, snapshotName string, force bool) error {
	// Build command
	args := []string{"rollback"}
	if force {
		args = append(args, "-r", "-f")
	} else {
		args = append(args, "-r")
	}
	args = append(args, snapshotName)

	cmd := zfsCommand(ctx, args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to rollback snapshot: %s: %w", string(output), err)
	}

	s.logger.Info("Snapshot rollback completed", "snapshot", snapshotName, "force", force)
	return nil
}

// CloneSnapshot clones a snapshot to a new dataset
func (s *SnapshotService) CloneSnapshot(ctx context.Context, snapshotName, cloneName string) error {
	// Build command
	args := []string{"clone", snapshotName, cloneName}

	cmd := zfsCommand(ctx, args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to clone snapshot: %s: %w", string(output), err)
	}

	s.logger.Info("Snapshot cloned", "snapshot", snapshotName, "clone", cloneName)
	return nil
}

// parseZFSUnixTimestamp parses ZFS Unix timestamp string
func parseZFSUnixTimestamp(timestampStr string) (time.Time, error) {
	// ZFS returns Unix timestamp as string
	timestamp, err := time.Parse("20060102150405", timestampStr)
	if err != nil {
		// Try parsing as Unix timestamp integer
		var unixTime int64
		if _, err := fmt.Sscanf(timestampStr, "%d", &unixTime); err == nil {
			return time.Unix(unixTime, 0), nil
		}
		return time.Time{}, err
	}
	return timestamp, nil
}

// parseZFSSize parses ZFS size string (e.g., "1.2M", "4.5G", "850K")
// Note: This function is also defined in zfs.go, but we need it here for snapshot parsing
func parseSnapshotSize(sizeStr string) int64 {
	if sizeStr == "-" || sizeStr == "" {
		return 0
	}

	// Remove any whitespace
	sizeStr = strings.TrimSpace(sizeStr)

	// Parse size with unit
	var size float64
	var unit string
	if _, err := fmt.Sscanf(sizeStr, "%f%s", &size, &unit); err != nil {
		return 0
	}

	// Convert to bytes
	unit = strings.ToUpper(unit)
	switch unit {
	case "K", "KB":
		return int64(size * 1024)
	case "M", "MB":
		return int64(size * 1024 * 1024)
	case "G", "GB":
		return int64(size * 1024 * 1024 * 1024)
	case "T", "TB":
		return int64(size * 1024 * 1024 * 1024 * 1024)
	default:
		// Assume bytes if no unit
		return int64(size)
	}
}
