// cert.go implements the `orca cert` subcommand family.
//
// Subcommands:
//
//	orca cert ca-init         — bootstrap a local CA in ~/.orca/
//	orca cert gen             — generate a server CSR + sign it with the local CA
//	orca cert show            — print the active server cert (redacted; REQ-035)
//	orca cert renew           — re-issue and rotate the server cert
//	orca cert fingerprint     — print the SHA-256 of ca.crt or server.crt
//
// All subcommands refuse to operate if the on-disk CA / cert file modes
// do not match REQ-033 (0600 for keys, 0644 for certs).
package cli

import (
	"encoding/pem"
	"fmt"
	"log/slog"
	"os"

	"github.com/spf13/cobra"

	"git.cloudinit.dev/coreci/orca/internal/certpaths"
	"git.cloudinit.dev/coreci/orca/internal/security"
)

// CADir returns the directory the local CA lives in. Re-exported for
// backward compatibility with callers that imported this from the cli
// package directly.
func CADir() string { return certpaths.Dir() }

// CACertPath returns the path to ca.crt.
func CACertPath() string { return certpaths.CACertPath() }

// CAKeyPath returns the path to ca.key.
func CAKeyPath() string { return certpaths.CAKeyPath() }

// ServerCertPath returns the path to server.crt.
func ServerCertPath() string { return certpaths.ServerCertPath() }

// ServerKeyPath returns the path to server.key.
func ServerKeyPath() string { return certpaths.ServerKeyPath() }

// NewCommand builds the `orca cert` command tree.
//
// Deprecated: v0.9 re-architecture replaces the internal CA with step-ca
// (D-101/REQ-076). The `orca cert` command tree is retained for the
// dual-write window and scheduled for deletion in v0.10. See
// .ciagent/PRD_v0.9.md.
func NewCommand(log *slog.Logger) *cobra.Command {
	if log == nil {
		log = slog.Default()
	}
	certCmd := &cobra.Command{
		Use:   "cert",
		Short: "Manage orca certificates (CA, server, rotation)",
		Long: `Manage orca certificates (CA, server, rotation).

Deprecated: v0.9 re-architecture replaces the internal CA with step-ca
(D-101/REQ-076). The ` + "`orca cert`" + ` command tree is retained for the
dual-write window and scheduled for deletion in v0.10. See
.ciagent/PRD_v0.9.md.`,
	}

	certCmd.AddCommand(newCAInitCmd(log))
	certCmd.AddCommand(newGenCmd(log))
	certCmd.AddCommand(newShowCmd(log))
	certCmd.AddCommand(newRenewCmd(log))
	certCmd.AddCommand(newFingerprintCmd(log))
	return certCmd
}

func newCAInitCmd(log *slog.Logger) *cobra.Command {
	var cn string
	cmd := &cobra.Command{
		Use:   "ca-init",
		Short: "Initialize a local orca CA (ca.crt + ca.key) under ~/.orca",
		Long:  "Generates a new RSA CA cert and writes it to ~/.orca/ca.crt (0644) and ~/.orca/ca.key (0600) per REQ-033.",
		RunE: func(cmd *cobra.Command, args []string) error {
			warnDeprecated("orca cert ca-init is deprecated: use step-ca (R-006); the internal CA is replaced by step-ca (D-101) — see .ciagent/PRD_v0.9.md")
			dir := CADir()
			if err := os.MkdirAll(dir, 0o755); err != nil {
				return fmt.Errorf("mkdir %s: %w", dir, err)
			}
			ca, err := security.CAInit(dir, cn)
			if err != nil {
				return fmt.Errorf("ca-init: %w", err)
			}
			fp := ca.Fingerprint()
			if _, err := fmt.Fprintf(cmd.OutOrStdout(), "✓ CA initialized at %s\n  fingerprint (sha256): %s\n  not_after: %s\n",
				dir, fp, ca.NotAfter.UTC().Format("2006-01-02")); err != nil {
				return err
			}
			log.Info("cert.ca_init",
				slog.String("event", "cert.ca_init"),
				slog.String("dir", dir),
				slog.String("cert_fp", fp),
			)
			return nil
		},
	}
	cmd.Flags().StringVar(&cn, "cn", "orca-local-ca", "CA common name")
	return cmd
}

func newGenCmd(log *slog.Logger) *cobra.Command {
	var cn string
	var sans []string
	cmd := &cobra.Command{
		Use:   "gen",
		Short: "Generate a server cert (CSR + sign) under ~/.orca",
		Long:  "Builds a CSR with the requested SANs, signs it with the local CA, and writes server.crt + server.key.",
		RunE: func(cmd *cobra.Command, args []string) error {
			warnDeprecated("orca cert gen is deprecated: use step-ca + orca node join for cert generation (D-101) — see .ciagent/PRD_v0.9.md")
			dir := CADir()
			if cn == "" {
				cn = "orca-server"
			}
			ca, err := security.LoadCA(dir)
			if err != nil {
				return fmt.Errorf("load CA (run `orca cert ca-init` first): %w", err)
			}
			keyPEM, csrPEM, err := security.GenerateCSR(cn, sans)
			if err != nil {
				return fmt.Errorf("generate CSR: %w", err)
			}
			certPEM, err := ca.SignCSR(csrPEM)
			if err != nil {
				return fmt.Errorf("sign CSR: %w", err)
			}
			certPath := ServerCertPath()
			keyPath := ServerKeyPath()
			if err := security.WriteCert(certPath, certPEM); err != nil {
				return fmt.Errorf("write cert: %w", err)
			}
			if err := security.WriteKey(keyPath, keyPEM); err != nil {
				return fmt.Errorf("write key: %w", err)
			}
			fp := security.FingerprintOf(parseFirstCertDER(certPEM))
			if _, err := fmt.Fprintf(cmd.OutOrStdout(), "✓ Server cert generated\n  cert: %s\n  key:  %s\n  fingerprint (sha256): %s\n",
				certPath, keyPath, fp); err != nil {
				return err
			}
			log.Info("cert.issued",
				slog.String("event", "cert.issued"),
				slog.String("cn", cn),
				slog.String("cert_fp", fp),
			)
			return nil
		},
	}
	cmd.Flags().StringVar(&cn, "cn", "orca-server", "server cert common name")
	cmd.Flags().StringSliceVar(&sans, "san", []string{"localhost", "127.0.0.1"}, "SAN entries (DNS or IP) — at least one required (REQ-036)")
	return cmd
}

func newShowCmd(log *slog.Logger) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "show",
		Short: "Print the server cert (private keys redacted; REQ-035)",
		RunE: func(cmd *cobra.Command, args []string) error {
			pem, err := os.ReadFile(ServerCertPath())
			if err != nil {
				return fmt.Errorf("read server cert: %w", err)
			}
			// Per REQ-035, strip private key material before display.
			out := security.Redact(pem)
			if _, err := cmd.OutOrStdout().Write(out); err != nil {
				return err
			}
			log.Debug("cert.show", slog.String("event", "cert.show"))
			return nil
		},
	}
	return cmd
}

func newRenewCmd(log *slog.Logger) *cobra.Command {
	var cn string
	var sans []string
	cmd := &cobra.Command{
		Use:   "renew",
		Short: "Rotate the server cert (hot-swapped by the daemon; REQ-034)",
		Long:  "Re-runs `cert gen` and overwrites server.crt / server.key in place. The daemon's GetCertificate callback picks up the new cert on the next handshake — no restart required.",
		RunE: func(cmd *cobra.Command, args []string) error {
			warnDeprecated("orca cert renew is deprecated: use step-ca for cert rotation (D-101) — see .ciagent/PRD_v0.9.md")
			dir := CADir()
			if cn == "" {
				cn = "orca-server"
			}
			ca, err := security.LoadCA(dir)
			if err != nil {
				return fmt.Errorf("load CA: %w", err)
			}
			keyPEM, csrPEM, err := security.GenerateCSR(cn, sans)
			if err != nil {
				return fmt.Errorf("generate CSR: %w", err)
			}
			certPEM, err := ca.SignCSR(csrPEM)
			if err != nil {
				return fmt.Errorf("sign CSR: %w", err)
			}
			if err := security.WriteCert(ServerCertPath(), certPEM); err != nil {
				return fmt.Errorf("write cert: %w", err)
			}
			if err := security.WriteKey(ServerKeyPath(), keyPEM); err != nil {
				return fmt.Errorf("write key: %w", err)
			}
			fp := security.FingerprintOf(parseFirstCertDER(certPEM))
			if _, err := fmt.Fprintln(cmd.OutOrStdout(), "✓ Server cert rotated"); err != nil {
				return err
			}
			log.Info("cert.renewed",
				slog.String("event", "cert.renewed"),
				slog.String("cert_fp", fp),
			)
			return nil
		},
	}
	cmd.Flags().StringVar(&cn, "cn", "orca-server", "server cert common name")
	cmd.Flags().StringSliceVar(&sans, "san", []string{"localhost", "127.0.0.1"}, "SAN entries (DNS or IP)")
	return cmd
}

func newFingerprintCmd(log *slog.Logger) *cobra.Command {
	var which string
	cmd := &cobra.Command{
		Use:   "fingerprint",
		Short: "Print the SHA-256 fingerprint of ca.crt or server.crt",
		RunE: func(cmd *cobra.Command, args []string) error {
			var path string
			switch which {
			case "ca", "":
				path = CACertPath()
			case "server":
				path = ServerCertPath()
			default:
				return fmt.Errorf("--which must be 'ca' or 'server'")
			}
			fp, err := security.Fingerprint(path)
			if err != nil {
				return err
			}
			if _, err := fmt.Fprintln(cmd.OutOrStdout(), fp); err != nil {
				return err
			}
			log.Debug("cert.fingerprint",
				slog.String("event", "cert.fingerprint"),
				slog.String("path", path),
				slog.String("cert_fp", fp),
			)
			return nil
		},
	}
	cmd.Flags().StringVar(&which, "which", "ca", "which cert: 'ca' or 'server'")
	return cmd
}

// parseFirstCertDER decodes the first CERTIFICATE PEM block in pemBytes
// and returns the DER bytes. Used by the cert cli for fingerprint calc
// after a fresh issuance.
func parseFirstCertDER(pemBytes []byte) []byte {
	block, _ := pem.Decode(pemBytes)
	if block == nil {
		return nil
	}
	return block.Bytes
}

func init() {
	rootCmd.AddCommand(NewCommand(slog.Default()))
}
