package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/term"

	"gitea.apointless.space/bsncubed/dial/internal/keygen"
)

// runKeygen implements `dial keygen [--rsa]`: generate a key pair and,
// optionally, append a matching Host block to ~/.ssh/config.
func runKeygen(args []string) error {
	algo := keygen.AlgoED25519
	for _, a := range args {
		switch a {
		case "--rsa":
			algo = keygen.AlgoRSA
		case "--ed25519":
			algo = keygen.AlgoED25519
		case "-h", "--help":
			fmt.Println("Usage: dial keygen [--rsa]\n\nGenerate an SSH key pair (ed25519 by default) and optionally add a Host entry.")
			return nil
		default:
			return fmt.Errorf("unknown keygen flag %q", a)
		}
	}

	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}
	sshDir := filepath.Join(home, ".ssh")
	in := bufio.NewReader(os.Stdin)

	fmt.Printf("%s (%s)\n", cyan("dial keygen"), algo)

	// Key filename.
	name := ask(in, "Key name / filename", "")
	name = strings.TrimSpace(name)
	if name == "" {
		return fmt.Errorf("a key name is required")
	}
	if strings.ContainsAny(name, "/\\") {
		return fmt.Errorf("key name must not contain path separators")
	}

	// Optional passphrase (hidden input, confirmed).
	passphrase, err := askPassphrase(in)
	if err != nil {
		return err
	}

	comment := name
	fmt.Printf("Generating %s key…\n", algo)
	res, err := keygen.Generate(algo, comment, passphrase)
	if err != nil {
		return err
	}
	privPath, pubPath, err := keygen.WriteKeyPair(sshDir, name, res)
	if err != nil {
		return err
	}
	fmt.Printf("%s\n  private: %s\n  public:  %s\n", green("✓ key written"), privPath, pubPath)

	// Public key printed for convenience — the .pub file is kept, so this is
	// not a last-chance display.
	fmt.Println("\nPublic key (also saved to the .pub file above):")
	fmt.Println(strings.TrimRight(string(res.PublicLine), "\n"))

	// Optional config entry.
	if !askYesNo(in, "\nAdd a Host entry to ~/.ssh/config now?", false) {
		fmt.Println("Done. No config entry added.")
		return nil
	}

	entry := keygen.HostEntry{
		IdentityFile: "~/.ssh/" + name,
	}
	entry.Alias = strings.TrimSpace(ask(in, "Host alias (what you'll type: ssh <alias>)", name))
	entry.HostName = strings.TrimSpace(ask(in, "HostName (IP or DNS)", ""))
	entry.User = strings.TrimSpace(ask(in, "User", ""))
	entry.Port = strings.TrimSpace(ask(in, "Port", "22"))
	if tags := strings.TrimSpace(ask(in, "Tags (comma-separated, optional)", "")); tags != "" {
		for _, t := range strings.Split(tags, ",") {
			if t = strings.TrimSpace(t); t != "" {
				entry.Tags = append(entry.Tags, t)
			}
		}
	}

	cfgPath := filepath.Join(sshDir, "config")
	backup, err := keygen.AppendHostEntry(cfgPath, entry)
	if err != nil {
		return err
	}
	if backup != "" {
		fmt.Printf("%s (backup: %s)\n", green("✓ Host \""+entry.Alias+"\" added to ~/.ssh/config"), filepath.Base(backup))
	} else {
		fmt.Printf("%s\n", green("✓ Host \""+entry.Alias+"\" added to ~/.ssh/config"))
	}
	fmt.Printf("\nConnect with:  %s\n", cyan("dial")+"  (then pick "+entry.Alias+")   or   ssh "+entry.Alias)
	return nil
}

// ask prints a prompt (with an optional default) and returns the entered line,
// falling back to def when the user just presses Enter.
func ask(in *bufio.Reader, prompt, def string) string {
	if def != "" {
		fmt.Printf("%s [%s]: ", blue(prompt), def)
	} else {
		fmt.Printf("%s: ", blue(prompt))
	}
	line, _ := in.ReadString('\n')
	line = strings.TrimRight(line, "\r\n")
	if line == "" {
		return def
	}
	return line
}

func askYesNo(in *bufio.Reader, prompt string, def bool) bool {
	hint := "y/N"
	if def {
		hint = "Y/n"
	}
	fmt.Printf("%s [%s]: ", blue(prompt), hint)
	line, _ := in.ReadString('\n')
	line = strings.ToLower(strings.TrimSpace(line))
	if line == "" {
		return def
	}
	return line == "y" || line == "yes"
}

// askPassphrase reads an optional passphrase twice (hidden), returning nil for
// an empty (passphraseless) key. Falls back to visible input on a non-terminal.
func askPassphrase(in *bufio.Reader) ([]byte, error) {
	fmt.Print(blue("Passphrase (optional, Enter for none)") + ": ")
	p1, err := readSecret(in)
	if err != nil {
		return nil, err
	}
	if len(p1) == 0 {
		return nil, nil
	}
	fmt.Print(blue("Confirm passphrase") + ": ")
	p2, err := readSecret(in)
	if err != nil {
		return nil, err
	}
	if string(p1) != string(p2) {
		return nil, fmt.Errorf("passphrases did not match")
	}
	return p1, nil
}

// readSecret reads a hidden line from a real terminal, or falls back to the
// shared buffered reader when stdin isn't a terminal (piped input) so the input
// stream stays in sync with the other prompts.
func readSecret(in *bufio.Reader) ([]byte, error) {
	fd := int(os.Stdin.Fd())
	if term.IsTerminal(fd) {
		b, err := term.ReadPassword(fd)
		fmt.Println()
		return b, err
	}
	line, _ := in.ReadString('\n')
	return []byte(strings.TrimRight(line, "\r\n")), nil
}

// Minimal ANSI colour helpers for the keygen prompts, matching dial's palette.
func cyan(s string) string  { return "\033[1;36m" + s + "\033[0m" }
func blue(s string) string  { return "\033[1;34m" + s + "\033[0m" }
func green(s string) string { return "\033[1;32m" + s + "\033[0m" }
