// Copyright 2026 printing-press-golden and contributors. 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 (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/spf13/cobra"
	"printing-press-oauth2-pp-cli/internal/cliutil"
	"printing-press-oauth2-pp-cli/internal/config"
	"printing-press-oauth2-pp-cli/internal/oauth"
)

func newAuthCmd(flags *rootFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "auth",
		Short: "Manage authentication for Printing Press Oauth2",
		RunE:  parentNoSubcommandRunE(flags),
	}

	cmd.AddCommand(newAuthSetupCmd(flags))
	cmd.AddCommand(newAuthLoginCmd(flags))
	cmd.AddCommand(newAuthPollCmd(flags))
	cmd.AddCommand(newAuthRefreshCmd(flags))
	cmd.AddCommand(newAuthStatusCmd(flags))
	cmd.AddCommand(newAuthSetTokenCmd(flags))
	cmd.AddCommand(newAuthLogoutCmd(flags))

	return cmd
}

func newAuthSetupCmd(_ *rootFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "setup",
		Short: "Print OAuth2 device-code setup details",
		RunE: func(cmd *cobra.Command, args []string) error {
			w := cmd.OutOrStdout()
			fmt.Fprintln(w, "No setup URL is configured for this CLI; check the API's docs.")
			fmt.Fprintln(w, "")
			fmt.Fprintln(w, "Then run:")
			fmt.Fprintln(w, "  printing-press-oauth2-pp-cli auth login --device-code --client-id <id>")
			return nil
		},
	}
	return cmd
}

func newAuthLoginCmd(flags *rootFlags) *cobra.Command {
	var clientID string
	var deviceCode bool
	var poll bool

	cmd := &cobra.Command{
		Use:   "login",
		Short: "Authenticate via OAuth2 device-code flow",
		Long: strings.TrimSpace(`
Request an OAuth2 device code, show the user code and verification URL, then
poll the token endpoint until authorization completes. This flow is designed
for CLIs and agents: no localhost callback server and no client secret.
`),
		Example: strings.Trim(`
  printing-press-oauth2-pp-cli auth login --device-code --client-id <id>
`, "\n"),
		Annotations: map[string]string{"mcp:hidden": "true"},
		RunE: func(cmd *cobra.Command, args []string) error {
			if cliutil.IsVerifyEnv() {
				fmt.Fprintln(cmd.OutOrStdout(), "would start OAuth2 device-code flow")
				return nil
			}
			if !deviceCode {
				return usageErr(fmt.Errorf("--device-code=false is not supported; device-code is the only login flow for this CLI"))
			}

			cfg, err := config.Load(flags.configPath)
			if err != nil {
				return configErr(err)
			}
			if clientID == "" {
				clientID = os.Getenv("DEVICE_CODE_CLIENT_ID")
			}
			if clientID == "" {
				clientID = cfg.ClientID
			}
			if clientID == "" {
				return authErr(fmt.Errorf("client ID required (set --client-id or DEVICE_CODE_CLIENT_ID)"))
			}

			tokenURL := cfg.TokenURL
			if tokenURL == "" {
				tokenURL = "https://login.device.example/oauth/token"
			}
			deviceAuthorizationURL := cfg.DeviceAuthorizationURL
			if deviceAuthorizationURL == "" {
				deviceAuthorizationURL = "https://login.device.example/oauth/device"
			}
			scopes := []string{"items.read"}
			if scope := os.Getenv("PRINTING_PRESS_OAUTH2_OAUTH_SCOPE"); scope != "" {
				scopes = strings.Fields(scope)
			}

			ctx := cmd.Context()
			device, err := oauth.RequestDeviceCode(ctx, nil, deviceAuthorizationURL, clientID, scopes)
			if err != nil {
				return authErr(err)
			}

			w := cmd.OutOrStdout()
			if device.Message != "" {
				fmt.Fprintln(w, device.Message)
			} else {
				fmt.Fprintf(w, "Open %s and enter code: %s\n", device.VerificationURI, device.UserCode)
			}
			if device.VerificationURIComplete != "" {
				fmt.Fprintf(w, "Direct URL: %s\n", device.VerificationURIComplete)
			}
			if !poll {
				if device.ExpiresIn > 0 {
					fmt.Fprintf(w, "Code expires in %d seconds.\n", device.ExpiresIn)
				}
				state := pendingDeviceCodeState{
					DeviceCode: device.DeviceCode,
					ClientID:   clientID,
					TokenURL:   tokenURL,
				}
				if device.ExpiresIn > 0 {
					state.ExpiresAt = time.Now().Add(time.Duration(device.ExpiresIn) * time.Second)
				}
				if err := savePendingDeviceCode(cfg, state); err != nil {
					return configErr(fmt.Errorf("saving pending device code: %w", err))
				}
				fmt.Fprintln(w, "After approving the device code, finish with:")
				fmt.Fprintln(w, "  printing-press-oauth2-pp-cli auth poll")
				return nil
			}

			timeout := 15 * time.Minute
			if device.ExpiresIn > 0 {
				timeout = time.Duration(device.ExpiresIn) * time.Second
			}
			pollCtx, cancel := context.WithTimeout(ctx, timeout)
			defer cancel()
			interval := time.Duration(device.Interval) * time.Second
			tok, err := oauth.PollDeviceToken(pollCtx, nil, tokenURL, clientID, device.DeviceCode, interval)
			if err != nil {
				return authErr(err)
			}
			expiry := time.Time{}
			if tok.ExpiresIn > 0 {
				expiry = time.Now().Add(time.Duration(tok.ExpiresIn) * time.Second)
			}
			cfg.AuthHeaderVal = ""
			if err := cfg.SaveTokens(clientID, "", tok.AccessToken, tok.RefreshToken, expiry); err != nil {
				return configErr(fmt.Errorf("saving tokens: %w", err))
			}
			if expiry.IsZero() {
				fmt.Fprintln(w, "Logged in. Token expiry not provided.")
			} else {
				fmt.Fprintf(w, "Logged in. Token expires %s (in %ds).\n", expiry.Format(time.RFC3339), tok.ExpiresIn)
			}
			return nil
		},
	}
	cmd.Flags().StringVar(&clientID, "client-id", "", "OAuth2 public client ID; defaults to $DEVICE_CODE_CLIENT_ID, config, or the spec default")
	cmd.Flags().BoolVar(&deviceCode, "device-code", true, "Use OAuth2 device-code flow")
	cmd.Flags().BoolVar(&poll, "poll", outputIsTerminal(), "Poll until authorization completes; defaults to false when stdout is non-interactive")
	return cmd
}

func newAuthPollCmd(flags *rootFlags) *cobra.Command {
	var timeout time.Duration

	cmd := &cobra.Command{
		Use:         "poll",
		Short:       "Poll the OAuth2 token endpoint for an approved device code",
		Annotations: map[string]string{"mcp:hidden": "true"},
		RunE: func(cmd *cobra.Command, args []string) error {
			if cliutil.IsVerifyEnv() {
				fmt.Fprintln(cmd.OutOrStdout(), "would poll OAuth2 token endpoint")
				return nil
			}
			cfg, err := config.Load(flags.configPath)
			if err != nil {
				return configErr(err)
			}
			state, err := loadPendingDeviceCode(cfg)
			if err != nil {
				return authErr(err)
			}
			if !state.ExpiresAt.IsZero() && time.Now().After(state.ExpiresAt) {
				_ = clearPendingDeviceCode(cfg)
				return authErr(fmt.Errorf("pending device code expired; run auth login --device-code again"))
			}
			clientID := state.ClientID
			tokenURL := state.TokenURL
			if tokenURL == "" {
				tokenURL = "https://login.device.example/oauth/token"
			}
			ctx := cmd.Context()
			if timeout > 0 {
				var cancel context.CancelFunc
				ctx, cancel = context.WithTimeout(ctx, timeout)
				defer cancel()
			}
			tok, err := oauth.PollDeviceToken(ctx, nil, tokenURL, clientID, state.DeviceCode, 0)
			if err != nil {
				return authErr(err)
			}
			expiry := time.Time{}
			if tok.ExpiresIn > 0 {
				expiry = time.Now().Add(time.Duration(tok.ExpiresIn) * time.Second)
			}
			cfg.AuthHeaderVal = ""
			if err := cfg.SaveTokens(clientID, "", tok.AccessToken, tok.RefreshToken, expiry); err != nil {
				return configErr(fmt.Errorf("saving tokens: %w", err))
			}
			_ = clearPendingDeviceCode(cfg)
			fmt.Fprintln(cmd.OutOrStdout(), "Logged in.")
			return nil
		},
	}
	cmd.Flags().DurationVar(&timeout, "timeout", 15*time.Minute, "How long to poll before giving up")
	return cmd
}

func newAuthRefreshCmd(flags *rootFlags) *cobra.Command {
	return &cobra.Command{
		Use:         "refresh",
		Short:       "Refresh the stored OAuth2 access token",
		Annotations: map[string]string{"mcp:hidden": "true"},
		RunE: func(cmd *cobra.Command, args []string) error {
			if cliutil.IsVerifyEnv() {
				fmt.Fprintln(cmd.OutOrStdout(), "would refresh OAuth2 token")
				return nil
			}
			cfg, err := config.Load(flags.configPath)
			if err != nil {
				return configErr(err)
			}
			if cfg.RefreshToken == "" {
				return authErr(fmt.Errorf("no refresh token stored; run auth login --device-code"))
			}
			tokenURL := cfg.TokenURL
			if tokenURL == "" {
				tokenURL = "https://login.device.example/oauth/token"
			}
			tok, err := oauth.RefreshToken(cmd.Context(), nil, tokenURL, cfg.ClientID, cfg.ClientSecret, cfg.RefreshToken)
			if err != nil {
				return authErr(err)
			}
			refreshToken := cfg.RefreshToken
			if tok.RefreshToken != "" {
				refreshToken = tok.RefreshToken
			}
			expiry := time.Time{}
			if tok.ExpiresIn > 0 {
				expiry = time.Now().Add(time.Duration(tok.ExpiresIn) * time.Second)
			}
			cfg.AuthHeaderVal = ""
			if err := cfg.SaveTokens(cfg.ClientID, cfg.ClientSecret, tok.AccessToken, refreshToken, expiry); err != nil {
				return configErr(fmt.Errorf("saving refreshed token: %w", err))
			}
			fmt.Fprintln(cmd.OutOrStdout(), "Token refreshed.")
			return nil
		},
	}
}

func outputIsTerminal() bool {
	info, err := os.Stdout.Stat()
	return err == nil && (info.Mode()&os.ModeCharDevice) != 0
}

type pendingDeviceCodeState struct {
	DeviceCode string    `json:"device_code"`
	ClientID   string    `json:"client_id"`
	TokenURL   string    `json:"token_url"`
	ExpiresAt  time.Time `json:"expires_at,omitempty"`
}

func savePendingDeviceCode(cfg *config.Config, state pendingDeviceCodeState) error {
	path := pendingDeviceCodePath(cfg)
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		return err
	}
	data, err := json.MarshalIndent(state, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(path, data, 0o600)
}

func loadPendingDeviceCode(cfg *config.Config) (pendingDeviceCodeState, error) {
	var state pendingDeviceCodeState
	data, err := os.ReadFile(pendingDeviceCodePath(cfg))
	if err != nil {
		if os.IsNotExist(err) {
			return state, fmt.Errorf("no pending device-code login; run auth login --device-code --poll=false")
		}
		return state, err
	}
	if err := json.Unmarshal(data, &state); err != nil {
		return state, err
	}
	if strings.TrimSpace(state.DeviceCode) == "" || strings.TrimSpace(state.ClientID) == "" {
		return state, fmt.Errorf("pending device-code state is incomplete; run auth login --device-code --poll=false")
	}
	return state, nil
}

func clearPendingDeviceCode(cfg *config.Config) error {
	err := os.Remove(pendingDeviceCodePath(cfg))
	if os.IsNotExist(err) {
		return nil
	}
	return err
}

func pendingDeviceCodePath(cfg *config.Config) string {
	return cfg.Path + ".device-code.json"
}

func newAuthStatusCmd(flags *rootFlags) *cobra.Command {
	return &cobra.Command{
		Use:     "status",
		Short:   "Show authentication status",
		Example: "  printing-press-oauth2-pp-cli auth status",
		RunE: func(cmd *cobra.Command, args []string) error {
			cfg, err := config.Load(flags.configPath)
			if err != nil {
				return configErr(err)
			}
			w := cmd.OutOrStdout()
			header := cfg.AuthHeader()
			authed := header != ""
			if flags.asJSON {
				out := map[string]any{
					"authenticated": authed,
					"verified":      false,
					"source":        cfg.AuthSource,
					"config":        cfg.Path,
				}
				if printErr := printJSONFiltered(w, out, flags); printErr != nil {
					return printErr
				}
				if !authed {
					return authErr(fmt.Errorf("no credentials configured"))
				}
				return nil
			}
			if !authed {
				fmt.Fprintln(w, red("Not authenticated"))
				fmt.Fprintln(w, "")
				fmt.Fprintf(w, "  printing-press-oauth2-pp-cli auth login --device-code\n")
				return authErr(fmt.Errorf("no credentials configured"))
			}
			fmt.Fprintln(w, green("Credentials present (not verified)"))
			fmt.Fprintf(w, "  Source: %s\n", cfg.AuthSource)
			fmt.Fprintf(w, "  Config: %s\n", cfg.Path)
			if !cfg.TokenExpiry.IsZero() {
				fmt.Fprintf(w, "  Expires: %s\n", cfg.TokenExpiry.Format(time.RFC3339))
			}
			return nil
		},
	}
}

func newAuthSetTokenCmd(flags *rootFlags) *cobra.Command {
	return &cobra.Command{
		Use:     "set-token <token>",
		Short:   "Save an API token to the credentials file (override the OAuth flow)",
		Example: "  printing-press-oauth2-pp-cli auth set-token <bearer-jwt>",
		Args:    cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cfg, err := config.Load(flags.configPath)
			if err != nil {
				return configErr(err)
			}
			cfg.AuthHeaderVal = ""
			if err := cfg.SaveTokens("", "", args[0], "", cfg.TokenExpiry); err != nil {
				return configErr(fmt.Errorf("saving token: %w", err))
			}
			savePath := credentialSavePath(cfg)
			if flags.asJSON {
				out := map[string]any{
					"saved":       true,
					"config_path": cfg.Path,
				}
				if !cfg.AgentcookieManagedByExternalStore() {
					out["credentials_path"] = savePath
				}
				return printJSONFiltered(cmd.OutOrStdout(), out, flags)
			}
			fmt.Fprintf(cmd.OutOrStdout(), "Token saved to %s\n", savePath)
			return nil
		},
	}
}

func credentialSavePath(cfg *config.Config) string {
	if cfg != nil && cfg.AgentcookieManagedByExternalStore() {
		return cfg.Path
	}
	if path, err := cliutil.CredentialsFilePath(); err == nil {
		return path
	}
	if cfg != nil {
		return cfg.Path
	}
	return ""
}

func newAuthLogoutCmd(flags *rootFlags) *cobra.Command {
	return &cobra.Command{
		Use:     "logout",
		Short:   "Clear stored credentials",
		Example: "  printing-press-oauth2-pp-cli auth logout",
		RunE: func(cmd *cobra.Command, args []string) error {
			cfg, err := config.Load(flags.configPath)
			if err != nil {
				return configErr(err)
			}
			if err := cfg.ClearTokens(); err != nil {
				return configErr(fmt.Errorf("clearing tokens: %w", err))
			}
			_ = clearPendingDeviceCode(cfg)
			if flags.asJSON {
				return printJSONFiltered(cmd.OutOrStdout(), map[string]any{"cleared": true}, flags)
			}
			fmt.Fprintln(cmd.OutOrStdout(), "Logged out. Credentials cleared.")
			return nil
		},
	}
}
