// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package task

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

	"git.secluded.site/go-lunatask"
	"git.secluded.site/lune/internal/client"
	"git.secluded.site/lune/internal/completion"
	"git.secluded.site/lune/internal/config"
	"git.secluded.site/lune/internal/ui"
	"git.secluded.site/lune/internal/validate"
	"github.com/charmbracelet/lipgloss"
	"github.com/charmbracelet/lipgloss/table"
	"github.com/spf13/cobra"
)

// ErrUnknownArea indicates the specified area key was not found in config.
var ErrUnknownArea = errors.New("unknown area key")

// ListCmd lists tasks. Exported for potential use by shortcuts.
var ListCmd = &cobra.Command{
	Use:   "list",
	Short: "List tasks",
	Long: `List tasks from Lunatask.

By default, shows incomplete tasks and tasks completed today.
Use --all to show entire task history.

Note: Due to end-to-end encryption, task names and notes
are not available through the API. Only metadata is shown.`,
	RunE: runList,
}

func init() {
	ListCmd.Flags().StringP("area", "a", "", "Filter by area key")
	ListCmd.Flags().StringP("status", "s", "", "Filter by status")
	ListCmd.Flags().Bool("all", false, "Show all tasks including completed")
	ListCmd.Flags().Bool("json", false, "Output as JSON")

	_ = ListCmd.RegisterFlagCompletionFunc("area", completion.Areas)
	_ = ListCmd.RegisterFlagCompletionFunc("status", completion.TaskStatuses)
}

func runList(cmd *cobra.Command, _ []string) error {
	apiClient, err := client.New()
	if err != nil {
		return err
	}

	tasks, err := ui.Spin("Fetching tasks…", func() ([]lunatask.Task, error) {
		return apiClient.ListTasks(cmd.Context(), nil)
	})
	if err != nil {
		return err
	}

	areaID, err := resolveAreaFilter(cmd)
	if err != nil {
		return err
	}

	statusFilter, err := resolveStatusFilter(cmd)
	if err != nil {
		return err
	}

	showAll := mustGetBoolFlag(cmd, "all")
	tasks = applyFilters(tasks, areaID, statusFilter, showAll)

	if len(tasks) == 0 {
		fmt.Fprintln(cmd.OutOrStdout(), "No tasks found")

		return nil
	}

	if mustGetBoolFlag(cmd, "json") {
		return outputJSON(cmd, tasks)
	}

	return outputTable(cmd, tasks)
}

// mustGetStringFlag returns the string flag value. Panics if flag doesn't exist
// (indicates a programming error—flags are defined in init).
func mustGetStringFlag(cmd *cobra.Command, name string) string {
	f := cmd.Flags().Lookup(name)
	if f == nil {
		panic("flag not defined: " + name)
	}

	return f.Value.String()
}

// mustGetBoolFlag returns the bool flag value. Panics if flag doesn't exist.
func mustGetBoolFlag(cmd *cobra.Command, name string) bool {
	f := cmd.Flags().Lookup(name)
	if f == nil {
		panic("flag not defined: " + name)
	}

	return f.Value.String() == "true"
}

func resolveAreaFilter(cmd *cobra.Command) (string, error) {
	areaKey := mustGetStringFlag(cmd, "area")

	cfg, err := config.Load()
	if err != nil {
		// Config not required if no area flag and we just skip default
		if errors.Is(err, config.ErrNotFound) {
			if areaKey != "" {
				return "", err
			}

			return "", nil
		}

		return "", err
	}

	// Use default area if no explicit flag
	if areaKey == "" {
		areaKey = cfg.Defaults.Area
	}

	if areaKey == "" {
		return "", nil
	}

	area := cfg.AreaByKey(areaKey)
	if area == nil {
		return "", fmt.Errorf("%w: %s", ErrUnknownArea, areaKey)
	}

	return area.ID, nil
}

func resolveStatusFilter(cmd *cobra.Command) (string, error) {
	status := mustGetStringFlag(cmd, "status")
	if status == "" {
		return "", nil
	}

	s, err := validate.TaskStatus(status)
	if err != nil {
		return "", err
	}

	return string(s), nil
}

func applyFilters(tasks []lunatask.Task, areaID, statusFilter string, showAll bool) []lunatask.Task {
	opts := &lunatask.TaskFilterOptions{
		IncludeCompleted: showAll,
		Today:            time.Now(),
	}

	if areaID != "" {
		opts.AreaID = &areaID
	}

	if statusFilter != "" {
		s := lunatask.TaskStatus(statusFilter)
		opts.Status = &s
	}

	return lunatask.FilterTasks(tasks, opts)
}

func outputJSON(cmd *cobra.Command, tasks []lunatask.Task) error {
	enc := json.NewEncoder(cmd.OutOrStdout())
	enc.SetIndent("", "  ")

	if err := enc.Encode(tasks); err != nil {
		return fmt.Errorf("encoding JSON: %w", err)
	}

	return nil
}

func outputTable(cmd *cobra.Command, tasks []lunatask.Task) error {
	rows := make([][]string, 0, len(tasks))

	for _, task := range tasks {
		status := "-"
		if task.Status != nil {
			status = string(*task.Status)
		}

		scheduled := "-"
		if task.ScheduledOn != nil {
			scheduled = ui.FormatDate(task.ScheduledOn.Time)
		}

		created := ui.FormatDate(task.CreatedAt)

		rows = append(rows, []string{task.ID, status, scheduled, created})
	}

	tbl := table.New().
		Headers("ID", "STATUS", "SCHEDULED", "CREATED").
		Rows(rows...).
		StyleFunc(func(row, col int) lipgloss.Style {
			if row == table.HeaderRow {
				return ui.TableHeaderStyle()
			}

			return lipgloss.NewStyle()
		}).
		Border(ui.TableBorder())

	fmt.Fprintln(cmd.OutOrStdout(), tbl.Render())

	return nil
}
