package main

import (
	"crypto/rand"
	"fmt"
	"os"

	"github.com/spf13/cobra"

	"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/keymanager"
	"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/mlkem768"
	"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/x25519"
	"git.tswf.io/infra/go-synapse-backupper/pkg/domain/crypto"
)

func newRegistry() crypto.Registry {
	reg := crypto.NewRegistry()
	_ = reg.Register(0x0006, func() crypto.KEM { return mlkem768.New() })
	_ = reg.Register(0x0007, func() crypto.KEM { return x25519.New() })
	return reg
}

func newKeygenCmd() *cobra.Command {
	return newKeygenCmdWithDeps(newRegistry())
}

func newKeygenCmdWithDeps(reg crypto.Registry) *cobra.Command {
	var (
		keyType   string
		outPrefix string
		force     bool
	)

	cmd := &cobra.Command{
		Use:   "keygen",
		Short: "Generate encryption key pairs",
		RunE: func(cmd *cobra.Command, args []string) error {
			schemes := []struct {
				name     string
				schemeID uint16
			}{}

			switch keyType {
			case "pq":
				schemes = append(schemes, struct {
					name     string
					schemeID uint16
				}{"pq", 0x0006})
			case "classical":
				schemes = append(schemes, struct {
					name     string
					schemeID uint16
				}{"classical", 0x0007})
			case "both":
				schemes = append(
					schemes,
					struct {
						name     string
						schemeID uint16
					}{"pq", 0x0006},
					struct {
						name     string
						schemeID uint16
					}{"classical", 0x0007},
				)
			default:
				return fmt.Errorf("invalid --type %q; must be pq, classical, or both", keyType)
			}

			if !force {
				for _, scheme := range schemes {
					pubPath := outPrefix + "." + scheme.name + ".pub.pem"
					privPath := outPrefix + "." + scheme.name + ".priv.pem"
					paths := make([]string, 0, 2)
					paths = append(paths, pubPath, privPath)
					for _, path := range paths {
						if _, err := os.Stat(path); err == nil {
							return fmt.Errorf("file already exists: %s (use --force to overwrite)", path)
						}
					}
				}
			}

			km := keymanager.NewKeyManager(reg)

			for _, scheme := range schemes {
				pubPath := outPrefix + "." + scheme.name + ".pub.pem"
				privPath := outPrefix + "." + scheme.name + ".priv.pem"

				pubFile, err := os.OpenFile(pubPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
				if err != nil {
					return fmt.Errorf("create public key file %s: %w", pubPath, err)
				}

				privFile, err := os.OpenFile(privPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
				if err != nil {
					_ = pubFile.Close()
					return fmt.Errorf("create private key file %s: %w", privPath, err)
				}

				if err := km.Generate(scheme.schemeID, pubFile, privFile, rand.Reader); err != nil {
					_ = pubFile.Close()
					_ = privFile.Close()
					return fmt.Errorf("generate %s keys: %w", scheme.name, err)
				}

				if err := pubFile.Close(); err != nil {
					return fmt.Errorf("close public key file %s: %w", pubPath, err)
				}
				if err := privFile.Close(); err != nil {
					return fmt.Errorf("close private key file %s: %w", privPath, err)
				}
				if err := os.Chmod(pubPath, 0o644); err != nil {
					return fmt.Errorf("chmod public key file %s: %w", pubPath, err)
				}
				if err := os.Chmod(privPath, 0o600); err != nil {
					return fmt.Errorf("chmod private key file %s: %w", privPath, err)
				}
			}

			return nil
		},
	}

	cmd.Flags().StringVar(&keyType, "type", "both", "Key type to generate (pq|classical|both)")
	cmd.Flags().StringVar(&outPrefix, "out-prefix", "", "Output file path prefix")
	cmd.Flags().BoolVar(&force, "force", false, "Overwrite existing files")

	return cmd
}
