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

// Package cli provides the snapshots command.
package cli

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"time"

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

// SnapshotsOptions contains options for the snapshots command.
type SnapshotsOptions struct {
	Tag     string
	Path    string
	Host    string
	Latest  int
	GroupBy string
}

// newSnapshotsCommand creates the snapshots command.
func (c *CLI) newSnapshotsCommand() *cobra.Command {
	opts := &SnapshotsOptions{}

	cmd := &cobra.Command{
		Use:   "snapshots",
		Short: "List and manage snapshots",
		Long: `List and manage backup snapshots.

A snapshot represents a point-in-time backup of your files.
Use subcommands to view details or compare snapshots.`,
		Example: `  # List all snapshots
  privatebackup snapshots

  # List with filters
  privatebackup snapshots --tag work --latest 5

  # Show snapshot details
  privatebackup snapshots show abc123de

  # Compare two snapshots
  privatebackup snapshots diff abc123de def456gh`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runSnapshotsList(opts)
		},
	}

	flags := cmd.Flags()
	flags.StringVar(&opts.Tag, "tag", "", "Filter by tag")
	flags.StringVar(&opts.Path, "path", "", "Filter by backup path")
	flags.StringVar(&opts.Host, "host", "", "Filter by hostname")
	flags.IntVar(&opts.Latest, "latest", 0, "Show only latest n snapshots")
	flags.StringVar(&opts.GroupBy, "group-by", "", "Group snapshots (host, tag, path)")

	// Add subcommands
	cmd.AddCommand(c.newSnapshotsShowCommand())
	cmd.AddCommand(c.newSnapshotsDiffCommand())

	return cmd
}

// runSnapshotsList lists all snapshots.
func (c *CLI) runSnapshotsList(opts *SnapshotsOptions) error {
	// 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)
	}

	ctx, cancel := c.GetContext()
	defer cancel()

	snapshots, err := c.client.ListSnapshots(ctx)
	if err != nil {
		return fmt.Errorf("failed to list snapshots: %w", err)
	}

	// Apply filters
	filtered := filterSnapshots(snapshots, opts)

	// Sort by time (newest first)
	sort.Slice(filtered, func(i, j int) bool {
		return filtered[i].Time.After(filtered[j].Time)
	})

	// Apply --latest limit
	if opts.Latest > 0 && len(filtered) > opts.Latest {
		filtered = filtered[:opts.Latest]
	}

	// Output
	c.renderer.Output(filtered, func() {
		if len(filtered) == 0 {
			c.renderer.Info("No snapshots found")
			c.renderer.Newline()
			c.renderer.Info("Create your first backup with: privatebackup backup <path>")
			return
		}

		// Build table using TableBuilder
		builder := output.NewTableBuilder(c.renderer)

		// Adjust columns based on terminal width
		if c.terminal.IsNarrow(100) {
			builder.Headers("ID", "DATE", "TAGS")
			for _, s := range filtered {
				builder.Row(
					truncateID(s.ID),
					formatShortDate(s.Time),
					formatTags(s.Tags),
				)
			}
		} else {
			builder.Headers("ID", "DATE", "HOST", "TAGS", "PATHS")
			for _, s := range filtered {
				builder.Row(
					truncateID(s.ID),
					formatDate(s.Time),
					s.Hostname,
					formatTags(s.Tags),
					formatPaths(s.Paths),
				)
			}
		}

		builder.Render()

		// Summary
		c.renderer.Newline()
		c.renderer.Printf("%d snapshot(s)\n", len(filtered))
	})

	return nil
}

// filterSnapshots applies filters to snapshots.
func filterSnapshots(snapshots []client.SnapshotResponse, opts *SnapshotsOptions) []client.SnapshotResponse {
	var filtered []client.SnapshotResponse

	for _, s := range snapshots {
		// Filter by tag
		if opts.Tag != "" {
			if _, ok := s.Tags[opts.Tag]; !ok {
				continue
			}
		}

		// Filter by path
		if opts.Path != "" {
			found := false
			for _, p := range s.Paths {
				if strings.Contains(p, opts.Path) {
					found = true
					break
				}
			}
			if !found {
				continue
			}
		}

		// Filter by host
		if opts.Host != "" && s.Hostname != opts.Host {
			continue
		}

		filtered = append(filtered, s)
	}

	return filtered
}

// newSnapshotsShowCommand creates the snapshots show subcommand.
func (c *CLI) newSnapshotsShowCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "show <snapshot>",
		Short: "Show details of a specific snapshot",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runSnapshotsShow(args[0])
		},
	}

	return cmd
}

// runSnapshotsShow shows details of a specific snapshot.
func (c *CLI) runSnapshotsShow(snapshotID string) error {
	// Check backend connection
	if err := c.checkBackendConnection(); err != nil {
		return err
	}

	ctx, cancel := c.GetContext()
	defer cancel()

	snapshot, err := c.client.GetSnapshot(ctx, snapshotID)
	if err != nil {
		return fmt.Errorf("failed to get snapshot: %w", err)
	}

	// Output
	c.renderer.Output(snapshot, func() {
		c.renderer.Heading(fmt.Sprintf("Snapshot %s", truncateID(snapshot.ID)))
		c.renderer.Newline()

		c.renderer.KeyValue("Date", formatDate(snapshot.Time))
		c.renderer.KeyValue("Host", snapshot.Hostname)
		c.renderer.KeyValue("Tags", formatTags(snapshot.Tags))
		c.renderer.KeyValue("Paths", formatPaths(snapshot.Paths))
		c.renderer.KeyValue("Size", formatBytes(snapshot.Size))
		c.renderer.KeyValue("Storage", "Hot (instant restore)") // Would need real check

		c.renderer.Newline()
		c.renderer.Section("Statistics")
		// In a real implementation, we'd show file counts, etc.
		c.renderer.KeyValue("Total size", formatBytes(snapshot.Size))
	})

	return nil
}

// newSnapshotsDiffCommand creates the snapshots diff subcommand.
func (c *CLI) newSnapshotsDiffCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "diff <snapshot1> <snapshot2>",
		Short: "Show differences between snapshots",
		Args:  cobra.ExactArgs(2),
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runSnapshotsDiff(args[0], args[1])
		},
	}

	return cmd
}

// runSnapshotsDiff shows differences between two snapshots.
func (c *CLI) runSnapshotsDiff(snap1, snap2 string) error {
	// Check backend connection
	if err := c.checkBackendConnection(); err != nil {
		return err
	}

	ctx, cancel := c.GetContext()
	defer cancel()

	// Get both snapshots
	snapshot1, err := c.client.GetSnapshot(ctx, snap1)
	if err != nil {
		return fmt.Errorf("failed to get snapshot %s: %w", snap1, err)
	}

	snapshot2, err := c.client.GetSnapshot(ctx, snap2)
	if err != nil {
		return fmt.Errorf("failed to get snapshot %s: %w", snap2, err)
	}

	// Output
	c.renderer.Heading(fmt.Sprintf("Diff: %s → %s", truncateID(snap1), truncateID(snap2)))
	c.renderer.Newline()

	c.renderer.KeyValue("Snapshot 1", fmt.Sprintf("%s (%s)", truncateID(snapshot1.ID), formatDate(snapshot1.Time)))
	c.renderer.KeyValue("Snapshot 2", fmt.Sprintf("%s (%s)", truncateID(snapshot2.ID), formatDate(snapshot2.Time)))
	c.renderer.Newline()

	// In a real implementation, we'd call a diff endpoint and show actual differences
	c.renderer.Info("Detailed diff requires backend support")

	return nil
}

// Helper functions

func truncateID(id string) string {
	if len(id) > 8 {
		return id[:8]
	}
	return id
}

func formatDate(t time.Time) string {
	return t.Format("2006-01-02 15:04:05")
}

func formatShortDate(t time.Time) string {
	return t.Format("Jan 02 15:04")
}

func formatTags(tags map[string]string) string {
	if len(tags) == 0 {
		return "-"
	}
	names := make([]string, 0, len(tags))
	for k := range tags {
		names = append(names, k)
	}
	sort.Strings(names)
	return strings.Join(names, ", ")
}

func formatPaths(paths []string) string {
	if len(paths) == 0 {
		return "-"
	}
	if len(paths) == 1 {
		return shortenPath(paths[0])
	}
	return fmt.Sprintf("%s (+%d)", shortenPath(paths[0]), len(paths)-1)
}

func shortenPath(path string) string {
	// Shorten home directory
	if home, err := userHomeDir(); err == nil {
		if strings.HasPrefix(path, home) {
			return "~" + path[len(home):]
		}
	}
	// Truncate long paths
	if len(path) > 30 {
		return "..." + path[len(path)-27:]
	}
	return path
}

func userHomeDir() (string, error) {
	return os.UserHomeDir()
}
