// SPDX-License-Identifier: GPL-3.0-or-later

// Package cli provides the restore command.
package cli

import (
	"context"
	"fmt"
	"time"

	"github.com/privatebackup/client-frontend-unix/internal/client"
	"github.com/privatebackup/client-frontend-unix/internal/output"
	"github.com/spf13/cobra"
)

// RestoreOptions contains options for the restore command.
type RestoreOptions struct {
	Target        string
	Include       []string
	Exclude       []string
	Verify        bool
	Overwrite     bool
	NoOwnership   bool
	NoPermissions bool
}

// newRestoreCommand creates the restore command.
func (c *CLI) newRestoreCommand() *cobra.Command {
	opts := &RestoreOptions{}

	cmd := &cobra.Command{
		Use:   "restore <snapshot> [paths...]",
		Short: "Restore files from a snapshot",
		Long: `Restore files from a backup snapshot to the specified target directory.

By default, restores all files; use paths to restore specific items.`,
		Example: `  # Restore latest snapshot to current directory
  privatebackup restore latest

  # Restore to specific directory
  privatebackup restore abc123de --target ~/restored

  # Restore specific file
  privatebackup restore latest ~/documents/important.pdf

  # Restore with pattern matching
  privatebackup restore latest --include "*.pdf" --target ~/pdfs

  # Restore and verify
  privatebackup restore latest --target ~/restored --verify`,
		Args: cobra.MinimumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runRestore(opts, args)
		},
	}

	flags := cmd.Flags()
	flags.StringVarP(&opts.Target, "target", "t", ".", "Target directory for restore")
	flags.StringSliceVarP(&opts.Include, "include", "i", nil, "Only restore files matching pattern")
	flags.StringSliceVarP(&opts.Exclude, "exclude", "e", nil, "Exclude files matching pattern")
	flags.BoolVar(&opts.Verify, "verify", false, "Verify restored files after restore")
	flags.BoolVar(&opts.Overwrite, "overwrite", false, "Overwrite existing files (default: skip)")
	flags.BoolVar(&opts.NoOwnership, "no-ownership", false, "Don't restore file ownership")
	flags.BoolVar(&opts.NoPermissions, "no-permissions", false, "Don't restore file permissions")

	return cmd
}

// runRestore executes the restore command.
func (c *CLI) runRestore(opts *RestoreOptions, args []string) error {
	snapshotID := args[0]
	paths := args[1:]

	// Check backend connection
	if err := c.checkBackendConnection(); err != nil {
		if IsBackendNotRunning(err) {
			c.renderer.Error("Cannot connect to backend daemon")
			c.renderer.Info("Start it with: privatebackup daemon start")
			return err
		}
		return fmt.Errorf("backend error: %w", err)
	}

	// Expand target path
	targetPath := expandPath(opts.Target)

	// Build restore request
	req := &client.RestoreRequest{
		SnapshotID: snapshotID,
		Target:     targetPath,
		Paths:      paths,
	}

	// Start restore
	ctx, cancel := context.WithTimeout(context.Background(), 24*time.Hour) // Long timeout for restores
	defer cancel()

	c.renderer.Info(fmt.Sprintf("Restoring from snapshot %s", snapshotID))
	c.renderer.Newline()

	status, err := c.client.StartRestore(ctx, req)
	if err != nil {
		return fmt.Errorf("failed to start restore: %w", err)
	}

	return c.streamRestoreProgress(ctx, status.JobID)
}

// streamRestoreProgress polls restore progress via GetJob.
func (c *CLI) streamRestoreProgress(ctx context.Context, jobID string) error {
	// Poll job status until complete
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()

	handler := &restoreProgressState{
		renderer:  c.renderer,
		startTime: time.Now(),
	}

	// Create progress bar if we have a TTY
	var progressBar *progressDisplay
	if c.terminal.CanShowProgress() && !c.options.Quiet {
		progressBar = newProgressDisplay(c.renderer, "Restoring")
		handler.progressBar = progressBar
	}

	for {
		select {
		case <-ctx.Done():
			if progressBar != nil {
				progressBar.done()
			}
			return ctx.Err()
		case <-ticker.C:
			job, err := c.client.GetJob(ctx, jobID)
			if err != nil {
				if progressBar != nil {
					progressBar.done()
				}
				return fmt.Errorf("failed to get job status: %w", err)
			}

			// Extract bytes/files from metadata if present
			var bytesProcessed, filesProcessed int64
			if job.Metadata != nil {
				if v, ok := job.Metadata["bytes_restored"].(float64); ok {
					bytesProcessed = int64(v)
				}
				if v, ok := job.Metadata["files_restored"].(float64); ok {
					filesProcessed = int64(v)
				}
			}

			// Update progress display
			event := client.ProgressEvent{
				JobID:          job.ID,
				Status:         job.Status,
				Progress:       job.Progress,
				BytesProcessed: bytesProcessed,
				FilesProcessed: filesProcessed,
			}
			handler.handleProgress(event)

			// Check if job is complete
			switch job.Status {
			case "completed":
				handler.handleComplete(event)
				if progressBar != nil {
					progressBar.done()
				}
				// Show success output
				c.renderer.Newline()
				c.renderer.Success("Restore complete!")
				c.renderer.Newline()
				c.renderer.KeyValue("Files restored", fmt.Sprintf("%d", handler.filesRestored))
				c.renderer.KeyValue("Size", formatBytes(handler.bytesRestored))
				c.renderer.KeyValue("Duration", time.Since(handler.startTime).Round(time.Second).String())
				return nil
			case "failed":
				if progressBar != nil {
					progressBar.done()
				}
				handler.failed = true
				handler.errorMsg = job.Error
				return fmt.Errorf("restore failed: %s", job.Error)
			case "cancelled":
				if progressBar != nil {
					progressBar.done()
				}
				return fmt.Errorf("restore cancelled")
			}
		}
	}
}

// restoreProgressState holds restore progress state.
type restoreProgressState struct {
	renderer      *output.Renderer
	progressBar   *progressDisplay
	startTime     time.Time
	filesRestored int64
	bytesRestored int64
	failed        bool
	errorMsg      string
}

func (h *restoreProgressState) handleProgress(event client.ProgressEvent) {
	if h.progressBar != nil {
		status := ""
		if event.Rate > 0 {
			status = fmt.Sprintf("%.1f MB/s", event.Rate/1024/1024)
		}
		h.progressBar.update(event.Progress, status)
	}
	h.filesRestored = event.FilesProcessed
	h.bytesRestored = event.BytesProcessed
}

func (h *restoreProgressState) handleComplete(event client.ProgressEvent) {
	h.filesRestored = event.FilesProcessed
	h.bytesRestored = event.BytesProcessed
}

func (h *restoreProgressState) handleError(err error) {
	h.failed = true
	h.errorMsg = err.Error()
}
