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

// Package cli provides the jobs command.
package cli

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

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

// newJobsCommand creates the jobs command.
func (c *CLI) newJobsCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "jobs",
		Short: "Manage background jobs",
		Long: `List and manage background jobs.

Background jobs include backups and restores.
Use subcommands to view details, cancel jobs, or wait for completion.`,
		Example: `  # List jobs
  privatebackup jobs

  # Show job details
  privatebackup jobs show job-a1b2c3d4

  # Cancel job
  privatebackup jobs cancel job-a1b2c3d4

  # Wait for job
  privatebackup jobs wait job-a1b2c3d4`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runJobsList()
		},
	}

	// Add subcommands
	cmd.AddCommand(c.newJobsShowCommand())
	cmd.AddCommand(c.newJobsCancelCommand())
	cmd.AddCommand(c.newJobsWaitCommand())

	return cmd
}

// runJobsList lists all jobs.
func (c *CLI) runJobsList() 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()

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

	c.renderer.Output(jobs, func() {
		if len(jobs) == 0 {
			c.renderer.Info("No active jobs")
			return
		}

		// Build table using TableBuilder
		builder := output.NewTableBuilder(c.renderer)
		builder.Headers("ID", "TYPE", "STATUS", "PROGRESS", "ETA")

		var running, completed int
		for _, job := range jobs {
			eta := "--"
			if job.Status == "running" || job.Status == "in-progress" {
				running++
				// Calculate ETA from metadata if available
			} else if job.Status == "completed" {
				completed++
			}

			builder.Row(
				truncateID(job.ID),
				job.Type,
				formatJobStatus(job.Status),
				fmt.Sprintf("%d%%", job.Progress),
				eta,
			)
		}

		builder.Render()

		c.renderer.Newline()
		c.renderer.Printf("%d running, %d completed\n", running, completed)
	})

	return nil
}

// formatJobStatus formats job status with color.
func formatJobStatus(status string) string {
	switch status {
	case "running", "in-progress":
		return "running"
	case "completed":
		return "completed"
	case "failed":
		return "failed"
	case "cancelled":
		return "cancelled"
	case "waiting":
		return "waiting"
	default:
		return status
	}
}

// newJobsShowCommand creates the jobs show subcommand.
func (c *CLI) newJobsShowCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "show <job-id>",
		Short: "Show job details",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runJobsShow(args[0])
		},
	}

	return cmd
}

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

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

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

	c.renderer.Output(job, func() {
		c.renderer.Heading(fmt.Sprintf("Job %s", truncateID(job.ID)))
		c.renderer.Newline()

		c.renderer.KeyValue("Type", job.Type)
		c.renderer.KeyValue("Status", formatJobStatus(job.Status))
		c.renderer.KeyValue("Progress", fmt.Sprintf("%d%%", job.Progress))
		c.renderer.KeyValue("Started", formatDate(job.StartedAt))

		if !job.CompletedAt.IsZero() {
			c.renderer.KeyValue("Completed", formatDate(job.CompletedAt))
		}

		if job.Error != "" {
			c.renderer.Newline()
			c.renderer.Error(job.Error)
		}
	})

	return nil
}

// newJobsCancelCommand creates the jobs cancel subcommand.
func (c *CLI) newJobsCancelCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "cancel <job-id>",
		Short: "Cancel a running job",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runJobsCancel(args[0])
		},
	}

	return cmd
}

// runJobsCancel cancels a job.
func (c *CLI) runJobsCancel(jobID string) error {
	// Check backend connection
	if err := c.checkBackendConnection(); err != nil {
		return err
	}

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

	// Confirm cancellation
	confirmed, err := c.prompter.Confirm(fmt.Sprintf("Cancel job %s?", truncateID(jobID)), false)
	if err != nil {
		return err
	}
	if !confirmed {
		c.renderer.Info("Cancellation aborted")
		return nil
	}

	err = c.client.CancelJob(ctx, jobID)
	if err != nil {
		return fmt.Errorf("failed to cancel job: %w", err)
	}

	c.renderer.Success(fmt.Sprintf("Job %s cancelled", truncateID(jobID)))

	return nil
}

// newJobsWaitCommand creates the jobs wait subcommand.
func (c *CLI) newJobsWaitCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "wait <job-id>",
		Short: "Wait for job to complete",
		Long:  `Wait for a job to complete, showing progress.`,
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.runJobsWait(args[0])
		},
	}

	return cmd
}

// runJobsWait waits for a job to complete.
func (c *CLI) runJobsWait(jobID string) error {
	// Check backend connection
	if err := c.checkBackendConnection(); err != nil {
		return err
	}

	ctx, cancel := context.WithTimeout(context.Background(), 24*time.Hour)
	defer cancel()

	// Get initial job status
	job, err := c.client.GetJob(ctx, jobID)
	if err != nil {
		return fmt.Errorf("failed to get job: %w", err)
	}

	if job.Status == "completed" {
		c.renderer.Success(fmt.Sprintf("Job %s already completed", truncateID(jobID)))
		return nil
	}

	if job.Status == "failed" || job.Status == "cancelled" {
		return fmt.Errorf("job %s %s", truncateID(jobID), job.Status)
	}

	c.renderer.Info(fmt.Sprintf("Waiting for job %s...", truncateID(jobID)))
	c.renderer.Newline()

	// Stream progress
	sseClient := client.NewSSEClient(c.client)

	var progressBar *progressDisplay
	if c.terminal.CanShowProgress() && !c.options.Quiet {
		progressBar = newProgressDisplay(c.renderer, "Processing")
	}

	handler := &jobWaitHandler{
		renderer:    c.renderer,
		progressBar: progressBar,
		jobType:     job.Type,
	}

	sseClient.OnEvent(handler.handleEvent)
	sseClient.OnError(func(err error) {
		handler.failed = true
		handler.errorMsg = err.Error()
	})

	err = sseClient.Stream(ctx, fmt.Sprintf("/api/v1/jobs/%s/progress", jobID))

	if progressBar != nil {
		progressBar.done()
	}

	if err != nil {
		return fmt.Errorf("failed to stream progress: %w", err)
	}

	c.renderer.Newline()
	if handler.failed {
		c.renderer.Error(fmt.Sprintf("Job %s failed", truncateID(jobID)))
		if handler.errorMsg != "" {
			c.renderer.Errorf("  %s", handler.errorMsg)
		}
		return fmt.Errorf("job failed: %s", handler.errorMsg)
	}

	c.renderer.Success(fmt.Sprintf("Job %s completed!", truncateID(jobID)))

	return nil
}

// jobWaitHandler handles job progress events.
type jobWaitHandler struct {
	renderer    *output.Renderer
	progressBar *progressDisplay
	jobType     string
	failed      bool
	errorMsg    string
}

func (h *jobWaitHandler) handleEvent(event client.Event) {
	switch event.Event {
	case "progress":
		// Parse JSON data to extract progress
		var data struct {
			Progress int `json:"progress"`
		}
		if err := json.Unmarshal([]byte(event.Data), &data); err == nil {
			if h.progressBar != nil {
				h.progressBar.update(data.Progress, "")
			}
		}
	case "complete":
		// Job completed
	case "error":
		h.failed = true
		var data struct {
			Error string `json:"error"`
		}
		if err := json.Unmarshal([]byte(event.Data), &data); err == nil {
			h.errorMsg = data.Error
		}
	}
}
