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

package note

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

	"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"
	"github.com/charmbracelet/lipgloss"
	"github.com/charmbracelet/lipgloss/table"
	"github.com/spf13/cobra"
)

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

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

func init() {
	ListCmd.Flags().StringP("notebook", "b", "", "Filter by notebook key")
	ListCmd.Flags().String("source", "", "Filter by source")
	ListCmd.Flags().String("source-id", "", "Filter by source ID")
	ListCmd.Flags().Bool("json", false, "Output as JSON")

	_ = ListCmd.RegisterFlagCompletionFunc("notebook", completion.Notebooks)
}

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

	opts := buildListOptions(cmd)

	notes, err := ui.Spin("Fetching notes…", func() ([]lunatask.Note, error) {
		return apiClient.ListNotes(cmd.Context(), opts)
	})
	if err != nil {
		return err
	}

	notebookID, err := resolveNotebookFilter(cmd)
	if err != nil {
		return err
	}

	if notebookID != "" {
		notes = filterByNotebook(notes, notebookID)
	}

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

		return nil
	}

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

	return outputTable(cmd, notes)
}

func buildListOptions(cmd *cobra.Command) *lunatask.ListNotesOptions {
	source, _ := cmd.Flags().GetString("source")
	sourceID, _ := cmd.Flags().GetString("source-id")

	if source == "" && sourceID == "" {
		return nil
	}

	opts := &lunatask.ListNotesOptions{}
	if source != "" {
		opts.Source = &source
	}

	if sourceID != "" {
		opts.SourceID = &sourceID
	}

	return opts
}

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

	cfg, err := config.Load()
	if err != nil {
		if errors.Is(err, config.ErrNotFound) {
			return "", err
		}

		return "", err
	}

	notebook := cfg.NotebookByKey(notebookKey)
	if notebook == nil {
		return "", fmt.Errorf("%w: %s", ErrUnknownNotebook, notebookKey)
	}

	return notebook.ID, nil
}

func filterByNotebook(notes []lunatask.Note, notebookID string) []lunatask.Note {
	filtered := make([]lunatask.Note, 0, len(notes))

	for _, note := range notes {
		if note.NotebookID != nil && *note.NotebookID == notebookID {
			filtered = append(filtered, note)
		}
	}

	return filtered
}

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()
}

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 outputJSON(cmd *cobra.Command, notes []lunatask.Note) error {
	enc := json.NewEncoder(cmd.OutOrStdout())
	enc.SetIndent("", "  ")

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

	return nil
}

func outputTable(cmd *cobra.Command, notes []lunatask.Note) error {
	cfg, _ := config.Load()
	rows := make([][]string, 0, len(notes))

	for _, note := range notes {
		notebook := "-"
		if note.NotebookID != nil {
			notebook = *note.NotebookID
			if cfg != nil {
				if nb := cfg.NotebookByID(*note.NotebookID); nb != nil {
					notebook = nb.Key
				}
			}
		}

		dateOn := "-"
		if note.DateOn != nil {
			dateOn = ui.FormatDate(note.DateOn.Time)
		}

		pinned := ""
		if note.Pinned {
			pinned = "📌"
		}

		created := ui.FormatDate(note.CreatedAt)

		rows = append(rows, []string{note.ID, notebook, dateOn, pinned, created})
	}

	tbl := table.New().
		Headers("ID", "NOTEBOOK", "DATE", "📌", "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
}
