// Copyright 2026 printing-press-golden. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.

package cli

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/spf13/cobra"
)

// FeedbackEntry is one line in the local feedback ledger. Every run of
// the feedback command appends one entry; upstream POST is a separate,
// optional step.
type FeedbackEntry struct {
	Text      string    `json:"text"`
	CLI       string    `json:"cli"`
	Version   string    `json:"version"`
	AgentID   string    `json:"agent_id,omitempty"`
	Timestamp time.Time `json:"timestamp"`
}

const feedbackMaxTextLen = 4096

func feedbackFilePath() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("resolving home dir: %w", err)
	}
	dir := filepath.Join(home, ".printing-press-golden-pp-cli")
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return "", fmt.Errorf("creating state dir: %w", err)
	}
	return filepath.Join(dir, "feedback.jsonl"), nil
}

// FeedbackEndpointConfigured reports whether an upstream feedback URL
// is available. Surfaced via agent-context so introspecting agents know
// whether their feedback will ship upstream.
func FeedbackEndpointConfigured() bool {
	return os.Getenv("PRINTING_PRESS_GOLDEN_FEEDBACK_ENDPOINT") != ""
}

func feedbackEndpoint() string {
	return os.Getenv("PRINTING_PRESS_GOLDEN_FEEDBACK_ENDPOINT")
}

func feedbackAutoSend() bool {
	v := strings.ToLower(strings.TrimSpace(os.Getenv("PRINTING_PRESS_GOLDEN_FEEDBACK_AUTO_SEND")))
	return v == "1" || v == "true" || v == "yes"
}

func appendFeedback(entry FeedbackEntry) error {
	p, err := feedbackFilePath()
	if err != nil {
		return err
	}
	f, err := os.OpenFile(p, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
	if err != nil {
		return fmt.Errorf("opening feedback ledger: %w", err)
	}
	defer f.Close()
	return json.NewEncoder(f).Encode(entry)
}

func postFeedback(url string, entry FeedbackEntry) error {
	body, err := json.Marshal(entry)
	if err != nil {
		return err
	}
	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("building feedback request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "printing-press-golden-pp-cli/feedback")
	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("posting feedback: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		return fmt.Errorf("feedback endpoint returned %s", resp.Status)
	}
	return nil
}

func newFeedbackCmd(flags *rootFlags) *cobra.Command {
	var useStdin bool
	var send bool
	cmd := &cobra.Command{
		Use:   "feedback [text]",
		Short: "Record feedback about this CLI (local by default; upstream opt-in)",
		Long: `Feedback is captured locally first at ~/.printing-press-golden-pp-cli/feedback.jsonl.
When ` + "`PRINTING_PRESS_GOLDEN_FEEDBACK_ENDPOINT`" + ` is set and either --send is
passed or ` + "`PRINTING_PRESS_GOLDEN_FEEDBACK_AUTO_SEND=true`" + `, the entry is
POSTed as JSON after the local write.

Write what surprised you or tripped you up, not a bug report. The
loop is: agent notices friction -> one invocation -> captured -> the
maintainer sees it.`,
		RunE: func(cmd *cobra.Command, args []string) error {
			var text string
			if useStdin {
				data, err := io.ReadAll(cmd.InOrStdin())
				if err != nil {
					return fmt.Errorf("reading stdin: %w", err)
				}
				text = strings.TrimSpace(string(data))
			} else if len(args) > 0 {
				text = strings.Join(args, " ")
			}
			text = strings.TrimSpace(text)
			if text == "" {
				return fmt.Errorf("feedback text is empty (pass arguments or --stdin)")
			}
			truncated := false
			if len(text) > feedbackMaxTextLen {
				text = text[:feedbackMaxTextLen]
				truncated = true
			}

			entry := FeedbackEntry{
				Text:      text,
				CLI:       "printing-press-golden-pp-cli",
				Version:   version,
				AgentID:   os.Getenv("AGENT_ID"),
				Timestamp: time.Now().UTC(),
			}
			if err := appendFeedback(entry); err != nil {
				return err
			}

			upstreamResult := map[string]any{"sent": false}
			if endpoint := feedbackEndpoint(); endpoint != "" && (send || feedbackAutoSend()) {
				if err := postFeedback(endpoint, entry); err != nil {
					fmt.Fprintf(cmd.ErrOrStderr(), "warning: feedback upstream POST failed: %v\n", err)
					upstreamResult["sent"] = false
					upstreamResult["error"] = err.Error()
				} else {
					upstreamResult["sent"] = true
					upstreamResult["endpoint"] = endpoint
				}
			}

			if flags.asJSON {
				return printJSONFiltered(cmd.OutOrStdout(), map[string]any{
					"recorded":  true,
					"truncated": truncated,
					"upstream":  upstreamResult,
					"entry":     entry,
				}, flags)
			}
			fmt.Fprintf(cmd.OutOrStdout(), "feedback recorded locally (%d chars%s)\n", len(text), func() string {
				if truncated {
					return ", truncated"
				}
				return ""
			}())
			if sent, _ := upstreamResult["sent"].(bool); sent {
				fmt.Fprintf(cmd.OutOrStdout(), "upstream POST: %v\n", upstreamResult["endpoint"])
			}
			return nil
		},
	}
	cmd.Flags().BoolVar(&useStdin, "stdin", false, "Read feedback body from stdin rather than arguments")
	cmd.Flags().BoolVar(&send, "send", false, "POST to the configured feedback endpoint in addition to local write")

	cmd.AddCommand(newFeedbackListCmd(flags))
	return cmd
}

func newFeedbackListCmd(flags *rootFlags) *cobra.Command {
	var limit int
	cmd := &cobra.Command{
		Use:   "list",
		Short: "List recent feedback entries",
		Example: `  printing-press-golden-pp-cli feedback list
  printing-press-golden-pp-cli feedback list --limit 5
  printing-press-golden-pp-cli feedback list --json`,
		RunE: func(cmd *cobra.Command, _ []string) error {
			p, err := feedbackFilePath()
			if err != nil {
				return err
			}
			data, err := os.ReadFile(p)
			if err != nil {
				if os.IsNotExist(err) {
					if flags.asJSON {
						return printJSONFiltered(cmd.OutOrStdout(), []FeedbackEntry{}, flags)
					}
					return nil
				}
				return err
			}
			var entries []FeedbackEntry
			for _, line := range strings.Split(string(data), "\n") {
				line = strings.TrimSpace(line)
				if line == "" {
					continue
				}
				var e FeedbackEntry
				if err := json.Unmarshal([]byte(line), &e); err != nil {
					continue
				}
				entries = append(entries, e)
			}
			if limit > 0 && limit < len(entries) {
				entries = entries[len(entries)-limit:]
			}
			return printJSONFiltered(cmd.OutOrStdout(), entries, flags)
		},
	}
	cmd.Flags().IntVar(&limit, "limit", 20, "Maximum number of recent entries to return")
	return cmd
}
