// Package cli: auth.go implements the `orca auth` subcommand family
// (REQ-144, D-239, D-242, D-246). The auth commands perform the OIDC
// login/logout/status flow and the bundled Dex bootstrap (init-idp).
//
// R-021 invariant: Orca never issues, stores, or accepts human-identity
// credentials. The IdP issues tokens; Orca only stores them (short-
// lived, 0600, refreshable). No passwords, no Orca-issued tokens.
package cli

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"time"

	"github.com/spf13/cobra"

	"git.cloudinit.dev/coreci/orca/internal/identity"
)

var authCmd = &cobra.Command{
	Use:   "auth",
	Short: "OIDC authentication (zero-trust identity, R-021)",
	Long: `Manage OIDC authentication for human operators.

Orca uses OIDC for human-identity authentication (R-021: no Orca-
issued credentials). The bundled Dex (deployed by 'orca auth init-idp')
is the default issuer; 'oidc.issuer' in config can repoint to a BYO
external IdP. The CLI performs the authorization-code + PKCE + local
loopback redirect flow; headless/CI uses the device-code flow.`,
}

var (
	authIssuer       string
	authClientID     string
	authClientSecret string
	authDeviceFlow   bool
	authOpenBrowser  bool
)

var authLoginCmd = &cobra.Command{
	Use:   "login",
	Short: "Authenticate via OIDC (browser or device-code flow)",
	Long: `Perform the OIDC login. By default, opens the default browser
for the authorization-code + PKCE + local loopback redirect flow. Use
--device-code for the headless/CI flow. Credentials are stored at
~/.orca/credentials.json (0600, short-lived + refresh).`,
	Args: cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		cfg, err := loadOIDCConfig()
		if err != nil {
			return err
		}
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
		defer cancel()
		client, err := identity.NewOIDCClient(ctx, *cfg)
		if err != nil {
			return fmt.Errorf("auth login: %w", err)
		}
		if authDeviceFlow {
			creds, err := client.DeviceFlowLogin(ctx, os.Stdout)
			if err != nil {
				return fmt.Errorf("auth login (device): %w", err)
			}
			if err := identity.SaveCredentials(creds); err != nil {
				return fmt.Errorf("auth login: %w", err)
			}
			fmt.Fprintf(cmd.OutOrStdout(), "✓ Logged in as %s (sub=%s)\n", creds.Issuer, creds.Subject)
			return nil
		}
		openBrowser := func(url string) error {
			if !authOpenBrowser {
				fmt.Fprintf(os.Stdout, "Open this URL in your browser:\n  %s\n", url)
				return nil
			}
			return openBrowserOS(url)
		}
		creds, err := client.Login(ctx, openBrowser)
		if err != nil {
			return fmt.Errorf("auth login: %w", err)
		}
		if err := identity.SaveCredentials(creds); err != nil {
			return fmt.Errorf("auth login: %w", err)
		}
		fmt.Fprintf(cmd.OutOrStdout(), "✓ Logged in as %s (sub=%s, groups=%v)\n", creds.Issuer, creds.Subject, creds.Groups)
		return nil
	},
}

var authLogoutCmd = &cobra.Command{
	Use:   "logout",
	Short: "Clear the stored OIDC credentials",
	Args:  cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		if err := identity.ClearCredentials(); err != nil {
			return fmt.Errorf("auth logout: %w", err)
		}
		fmt.Fprintln(cmd.OutOrStdout(), "✓ Logged out (credentials cleared)")
		return nil
	},
}

var authStatusCmd = &cobra.Command{
	Use:   "status",
	Short: "Show the current OIDC authentication status",
	Args:  cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		creds, err := identity.LoadCredentials()
		if err != nil {
			fmt.Fprintln(cmd.OutOrStdout(), "Not authenticated (no credentials)")
			return nil
		}
		expired := time.Now().After(creds.Expiry)
		fmt.Fprintf(cmd.OutOrStdout(), "Issuer:  %s\n", creds.Issuer)
		fmt.Fprintf(cmd.OutOrStdout(), "Subject: %s\n", creds.Subject)
		fmt.Fprintf(cmd.OutOrStdout(), "Groups: %v\n", creds.Groups)
		fmt.Fprintf(cmd.OutOrStdout(), "Expiry: %s\n", creds.Expiry.Format(time.RFC3339))
		if expired {
			fmt.Fprintln(cmd.OutOrStdout(), "Status: EXPIRED (run 'orca auth login' to refresh)")
		} else {
			fmt.Fprintln(cmd.OutOrStdout(), "Status: valid")
		}
		return nil
	},
}

var (
	authInitIDP  string
	authInitRPID string
)

var authInitIDPCmd = &cobra.Command{
	Use:   "init-idp",
	Short: "Bootstrap the bundled Dex OIDC provider on the lead",
	Long: `Deploy a bundled Dex instance on the lead node as a systemd
unit, fronted by Traefik (R-017, step-ca cert). This is the default
zero-trust identity provider; 'oidc.issuer' can be repointed to a BYO
external IdP anytime. The WebAuthn connector (P05) provides the
password-free upstream authenticator.

--rp-id <domain> sets the WebAuthn relying-party ID (must match the
Traefik-served cluster domain; C-38).`,
	Args: cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		if authInitRPID == "" {
			return fmt.Errorf("--rp-id is required (the cluster's Traefik-served domain for WebAuthn)")
		}
		// The full Dex deploy is a systemd unit + Traefik route + config
		// template. For v0.12 P04 we emit the config + unit files; the
		// WebAuthn connector ships in P05.
		fmt.Fprintf(cmd.OutOrStdout(), "Dex bootstrap planned for RP ID: %s\n", authInitRPID)
		fmt.Fprintln(cmd.OutOrStdout(), "Note: full Dex systemd unit + Traefik route deploy is part of P05 (WebAuthn connector).")
		fmt.Fprintln(cmd.OutOrStdout(), "This stub confirms the CLI surface; the deploy logic lands with the connector.")
		return nil
	},
}

// loadOIDCConfig loads the OIDC config from flags or the cluster config.
func loadOIDCConfig() (*identity.OIDCConfig, error) {
	cfg := &identity.OIDCConfig{
		Issuer:       authIssuer,
		ClientID:     authClientID,
		ClientSecret: authClientSecret,
	}
	if cfg.Issuer == "" {
		// TODO: load from cluster config (oidc block). For v0.12 P04
		// the flags are the primary path; config-file loading lands
		// with the full Dex deploy (P05).
		return nil, fmt.Errorf("auth: --issuer is required (or set oidc.issuer in config)")
	}
	if cfg.ClientID == "" {
		cfg.ClientID = "orca-cli"
	}
	return cfg, nil
}

// openBrowserOS opens the URL in the default browser.
func openBrowserOS(url string) error {
	switch runtime.GOOS {
	case "linux":
		return exec.Command("xdg-open", url).Start()
	case "darwin":
		return exec.Command("open", url).Start()
	case "windows":
		return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
	}
	return fmt.Errorf("unsupported OS for browser open: %s", runtime.GOOS)
}

func init() {
	authLoginCmd.Flags().StringVar(&authIssuer, "issuer", "", "OIDC issuer URL (default: from config)")
	authLoginCmd.Flags().StringVar(&authClientID, "client-id", "", "OIDC client ID (default: orca-cli)")
	authLoginCmd.Flags().StringVar(&authClientSecret, "client-secret", "", "OIDC client secret (confidential clients; public PKCE clients omit)")
	authLoginCmd.Flags().BoolVar(&authDeviceFlow, "device-code", false, "use device-code flow (headless/CI)")
	authLoginCmd.Flags().BoolVar(&authOpenBrowser, "open-browser", true, "open the default browser (set false to print URL only)")

	authInitIDPCmd.Flags().StringVar(&authInitRPID, "rp-id", "", "WebAuthn relying-party ID (cluster Traefik domain)")

	authCmd.AddCommand(authLoginCmd)
	authCmd.AddCommand(authLogoutCmd)
	authCmd.AddCommand(authStatusCmd)
	authCmd.AddCommand(authInitIDPCmd)
	rootCmd.AddCommand(authCmd)
}
