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

// Package cli provides the daemon command.
package cli

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/spf13/cobra"
)

// DaemonStartOptions contains options for daemon start.
type DaemonStartOptions struct {
	Foreground bool
}

// newDaemonCommand creates the daemon command.
func (c *CLI) newDaemonCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "daemon",
		Short: "Control the backend daemon",
		Long: `Control the Private Archive backend daemon.

The daemon must be running to perform backup and restore operations.
It manages the Tor connection and communicates with the server.`,
		Example: `  # Start daemon
  privatebackup daemon start

  # Check status
  privatebackup daemon status

  # View logs
  privatebackup daemon logs --follow`,
	}

	// Add subcommands
	cmd.AddCommand(c.newDaemonStartCommand())
	cmd.AddCommand(c.newDaemonStopCommand())
	cmd.AddCommand(c.newDaemonRestartCommand())
	cmd.AddCommand(c.newDaemonStatusCommand())
	cmd.AddCommand(c.newDaemonLogsCommand())

	return cmd
}

// newDaemonStartCommand creates the daemon start subcommand.
func (c *CLI) newDaemonStartCommand() *cobra.Command {
	opts := &DaemonStartOptions{}

	cmd := &cobra.Command{
		Use:   "start",
		Short: "Start the backend daemon",
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runDaemonStart(opts)
		},
	}

	flags := cmd.Flags()
	flags.BoolVar(&opts.Foreground, "foreground", false, "Run in foreground (don't daemonize)")

	return cmd
}

// runDaemonStart starts the daemon.
func (c *CLI) runDaemonStart(opts *DaemonStartOptions) error {
	// Check if already running
	if c.isDaemonRunning() {
		c.renderer.Info("Backend daemon is already running")
		return nil
	}

	c.renderer.Info("Starting backend daemon...")

	if opts.Foreground {
		// Run in foreground - would exec the backend binary directly
		c.renderer.Info("Running in foreground mode")
		c.renderer.Info("Press Ctrl+C to stop")
		c.renderer.Newline()

		// In reality, we'd exec the backend binary here
		// For now, just indicate what would happen
		c.renderer.Info("(Backend would start here in foreground)")
		return nil
	}

	// Daemonize
	// In reality, we'd start the backend as a background process
	// and write its PID to a file

	// Find the backend binary
	backendBinary := c.findBackendBinary()
	if backendBinary == "" {
		return fmt.Errorf("backend binary not found. Please ensure privatebackup-backend is in your PATH")
	}

	// Start as daemon
	cmd := exec.Command(backendBinary, "serve")
	cmd.SysProcAttr = daemonProcAttr()

	// Redirect output to log file
	logDir := filepath.Join(c.configMgr.GetConfigDir(), "logs")
	if err := os.MkdirAll(logDir, 0700); err != nil {
		return fmt.Errorf("failed to create log directory: %w", err)
	}

	logFile, err := os.OpenFile(
		filepath.Join(logDir, "backend.log"),
		os.O_CREATE|os.O_WRONLY|os.O_APPEND,
		0600,
	)
	if err != nil {
		return fmt.Errorf("failed to open log file: %w", err)
	}

	cmd.Stdout = logFile
	cmd.Stderr = logFile

	if err := cmd.Start(); err != nil {
		logFile.Close()
		return fmt.Errorf("failed to start daemon: %w", err)
	}

	// Write PID file
	pidFile := filepath.Join(c.configMgr.GetConfigDir(), "daemon.pid")
	if err := os.WriteFile(pidFile, []byte(strconv.Itoa(cmd.Process.Pid)), 0600); err != nil {
		c.renderer.Warning("Failed to write PID file")
	}

	c.renderer.Success("Backend daemon started")
	c.renderer.KeyValue("PID", strconv.Itoa(cmd.Process.Pid))
	c.renderer.KeyValue("Log", filepath.Join(logDir, "backend.log"))

	return nil
}

// newDaemonStopCommand creates the daemon stop subcommand.
func (c *CLI) newDaemonStopCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "stop",
		Short: "Stop the backend daemon",
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runDaemonStop()
		},
	}

	return cmd
}

// runDaemonStop stops the daemon.
func (c *CLI) runDaemonStop() error {
	pid, err := c.getDaemonPID()
	if err != nil {
		c.renderer.Info("Backend daemon is not running")
		return nil
	}

	c.renderer.Info(fmt.Sprintf("Stopping backend daemon (PID %d)...", pid))

	// Send SIGTERM
	process, err := os.FindProcess(pid)
	if err != nil {
		return fmt.Errorf("failed to find process: %w", err)
	}

	if err := sendStopSignal(process); err != nil {
		return fmt.Errorf("failed to send signal: %w", err)
	}

	// Remove PID file
	pidFile := filepath.Join(c.configMgr.GetConfigDir(), "daemon.pid")
	os.Remove(pidFile)

	c.renderer.Success("Backend daemon stopped")

	return nil
}

// newDaemonRestartCommand creates the daemon restart subcommand.
func (c *CLI) newDaemonRestartCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "restart",
		Short: "Restart the backend daemon",
		RunE: func(cmd *cobra.Command, args []string) error {
			if err := c.runDaemonStop(); err != nil {
				// Ignore stop errors - daemon may not be running
			}
			return c.runDaemonStart(&DaemonStartOptions{})
		},
	}

	return cmd
}

// newDaemonStatusCommand creates the daemon status subcommand.
func (c *CLI) newDaemonStatusCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "status",
		Short: "Show daemon status",
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runDaemonStatus()
		},
	}

	return cmd
}

// runDaemonStatus shows daemon status.
func (c *CLI) runDaemonStatus() error {
	c.renderer.Section("Backend Daemon")
	c.renderer.Newline()

	pid, err := c.getDaemonPID()
	if err != nil || !c.isProcessRunning(pid) {
		c.renderer.KeyValue("Status", c.renderer.Colors().Error("Not running"))
		c.renderer.Newline()
		c.renderer.Info("Start with: privatebackup daemon start")
		return nil
	}

	c.renderer.KeyValue("Status", c.renderer.Colors().Success("Running"))
	c.renderer.KeyValue("PID", strconv.Itoa(pid))

	// Get more info from backend API
	ctx, cancel := c.GetContext()
	defer cancel()

	status, err := c.client.Status(ctx)
	if err == nil {
		c.renderer.KeyValue("Version", status.Version)
		c.renderer.KeyValue("Uptime", status.Uptime)
		c.renderer.KeyValue("API", c.client.BaseURL())
	}

	c.renderer.Newline()

	// Get Tor status
	torStatus, err := c.client.TorStatus(ctx)
	if err == nil {
		c.renderer.Section("Connections")
		if torStatus.Connected {
			c.renderer.KeyValue("Tor", c.renderer.Colors().Success("Connected"))
		} else {
			c.renderer.KeyValue("Tor", c.renderer.Colors().Error("Disconnected"))
		}

		serverStatus, err := c.client.ServerStatus(ctx)
		if err == nil && serverStatus.Connected {
			c.renderer.KeyValue("Server", c.renderer.Colors().Success("Reachable"))
		} else {
			c.renderer.KeyValue("Server", c.renderer.Colors().Error("Unreachable"))
		}
	}

	return nil
}

// newDaemonLogsCommand creates the daemon logs subcommand.
func (c *CLI) newDaemonLogsCommand() *cobra.Command {
	var follow bool

	cmd := &cobra.Command{
		Use:   "logs",
		Short: "Show daemon logs",
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runDaemonLogs(follow)
		},
	}

	flags := cmd.Flags()
	flags.BoolVarP(&follow, "follow", "f", false, "Follow log output")

	return cmd
}

// runDaemonLogs shows daemon logs.
func (c *CLI) runDaemonLogs(follow bool) error {
	logFile := filepath.Join(c.configMgr.GetConfigDir(), "logs", "backend.log")

	if _, err := os.Stat(logFile); os.IsNotExist(err) {
		c.renderer.Info("No log file found")
		c.renderer.Info("The daemon may not have been started yet.")
		return nil
	}

	if follow {
		// Tail with follow
		cmd := exec.Command("tail", "-f", logFile)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		return cmd.Run()
	}

	// Show last 50 lines
	file, err := os.Open(logFile)
	if err != nil {
		return fmt.Errorf("failed to open log file: %w", err)
	}
	defer file.Close()

	// Read last N lines
	lines := make([]string, 0, 50)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
		if len(lines) > 50 {
			lines = lines[1:]
		}
	}

	for _, line := range lines {
		fmt.Println(line)
	}

	return nil
}

// Helper functions

// findBackendBinary finds the backend binary.
func (c *CLI) findBackendBinary() string {
	// Look for privatebackup-backend in common locations
	candidates := []string{
		"privatebackup-backend",
		"/usr/local/bin/privatebackup-backend",
		"/usr/bin/privatebackup-backend",
	}

	for _, candidate := range candidates {
		if path, err := exec.LookPath(candidate); err == nil {
			return path
		}
	}

	return ""
}

// isDaemonRunning checks if the daemon is running.
func (c *CLI) isDaemonRunning() bool {
	pid, err := c.getDaemonPID()
	if err != nil {
		return false
	}
	return c.isProcessRunning(pid)
}

// getDaemonPID reads the daemon PID from the PID file.
func (c *CLI) getDaemonPID() (int, error) {
	pidFile := filepath.Join(c.configMgr.GetConfigDir(), "daemon.pid")
	data, err := os.ReadFile(pidFile)
	if err != nil {
		return 0, err
	}

	pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
	if err != nil {
		return 0, err
	}

	return pid, nil
}

// isProcessRunning checks if a process with the given PID is running.
func (c *CLI) isProcessRunning(pid int) bool {
	process, err := os.FindProcess(pid)
	if err != nil {
		return false
	}

	// Check if process exists
	err = checkProcessAlive(process)
	return err == nil
}
