package plan

import (
	"bytes"
	"fmt"
	"sort"
	"strings"

	"metis/pkg/config"
	"metis/pkg/secrets"
)

func cloudInitUserData(cfg *config.NodeConfig, sec *secrets.NodeSecrets) string {
	if cfg == nil {
		return ""
	}
	if sec != nil && sec.CloudInit != "" {
		return sec.CloudInit
	}
	var b bytes.Buffer
	b.WriteString("#cloud-config\n")
	b.WriteString(fmt.Sprintf("hostname: %s\n", cfg.Hostname))
	if len(cfg.SSHKeys) > 0 {
		b.WriteString("ssh_authorized_keys:\n")
		for _, k := range cfg.SSHKeys {
			b.WriteString(fmt.Sprintf("  - %s\n", k))
		}
	}
	if hasNodePasswords(sec) {
		b.WriteString("ssh_pwauth: true\n")
		b.WriteString("disable_root: false\n")
	}
	b.WriteString("runcmd:\n")
	b.WriteString("  - [bash, -lc, \"/usr/local/sbin/metis-apply-node-identity.sh\"]\n")
	return b.String()
}

func firstbootEnvContent(cfg *config.NodeConfig, sec *secrets.NodeSecrets) string {
	var b bytes.Buffer
	b.WriteString(fmt.Sprintf("METIS_HOSTNAME=%s\n", shellQuote(cfg.Hostname)))
	b.WriteString(fmt.Sprintf("METIS_SSH_USER=%s\n", shellQuote(cfg.SSHUser)))
	b.WriteString("METIS_ATLAS_USER='atlas'\n")
	b.WriteString(fmt.Sprintf("METIS_K3S_VERSION=%s\n", shellQuote(cfg.K3s.Version)))
	if sec != nil {
		if value := effectiveAtlasPassword(sec); value != "" {
			b.WriteString(fmt.Sprintf("METIS_ATLAS_PASSWORD=%s\n", shellQuote(value)))
		}
		if value := strings.TrimSpace(sec.RootPassword); value != "" {
			b.WriteString(fmt.Sprintf("METIS_ROOT_PASSWORD=%s\n", shellQuote(value)))
		}
	}
	return b.String()
}

func cloudInitRootFSContent(sec *secrets.NodeSecrets) string {
	var b bytes.Buffer
	b.WriteString("#cloud-config\n")
	if hasNodePasswords(sec) {
		b.WriteString("ssh_pwauth: true\n")
		b.WriteString("disable_root: false\n")
	}
	b.WriteString("runcmd:\n")
	b.WriteString("  - [bash, -lc, \"/usr/local/sbin/metis-apply-node-identity.sh\"]\n")
	return b.String()
}

func nodeIdentityScriptContent() string {
	return `#!/usr/bin/env bash
set -euo pipefail

marker="/var/lib/metis/node-identity-applied.done"
env_file="/etc/metis/firstboot.env"
key_file="/etc/metis/authorized_keys"
sudoers_file="/etc/metis/sudoers-hecate"
default_groups=(adm sudo tty disk dialout audio video plugdev games users systemd-journal input render netdev)

if [ -f "${marker}" ]; then
  exit 0
fi

mkdir -p /var/lib/metis
if [ -f "${env_file}" ]; then
  # shellcheck disable=SC1090
  . "${env_file}"
fi

atlas_user="${METIS_ATLAS_USER:-atlas}"
ssh_user="${METIS_SSH_USER:-${atlas_user}}"
atlas_password="${METIS_ATLAS_PASSWORD:-}"
root_password="${METIS_ROOT_PASSWORD:-}"

group_list=()
for group_name in "${default_groups[@]}"; do
  if getent group "${group_name}" >/dev/null 2>&1; then
    group_list+=("${group_name}")
  fi
done
if [ "${#group_list[@]}" -gt 0 ]; then
  group_csv="$(IFS=,; printf '%s' "${group_list[*]}")"
else
  group_csv=""
fi

ensure_user() {
  local user_name="$1"
  [ -n "${user_name}" ] || return 0
  if ! id "${user_name}" >/dev/null 2>&1; then
    if [ -n "${group_csv}" ]; then
      useradd -m -s /bin/bash -G "${group_csv}" "${user_name}"
    else
      useradd -m -s /bin/bash "${user_name}"
    fi
  elif [ -n "${group_csv}" ]; then
    usermod -a -G "${group_csv}" "${user_name}" || true
  fi
}

apply_password() {
  local user_name="$1"
  local plain_password="$2"
  if ! id "${user_name}" >/dev/null 2>&1; then
    return 0
  fi
  if [ -n "${plain_password}" ]; then
    printf '%s:%s\n' "${user_name}" "${plain_password}" | chpasswd
    passwd -u "${user_name}" >/dev/null 2>&1 || true
  fi
}

install_keys() {
  local user_name="$1"
  [ -n "${user_name}" ] || return 0
  [ -s "${key_file}" ] || return 0
  local home_dir
  home_dir="$(getent passwd "${user_name}" | cut -d: -f6)"
  if [ -z "${home_dir}" ]; then
    if [ "${user_name}" = "root" ]; then
      home_dir="/root"
    else
      home_dir="/home/${user_name}"
    fi
  fi
  install -d -m 700 "${home_dir}/.ssh"
  install -m 600 "${key_file}" "${home_dir}/.ssh/authorized_keys"
  chown -R "${user_name}:${user_name}" "${home_dir}/.ssh" 2>/dev/null || true
}

ensure_user "${atlas_user}"
if [ -n "${ssh_user}" ] && [ "${ssh_user}" != "root" ] && [ "${ssh_user}" != "${atlas_user}" ]; then
  ensure_user "${ssh_user}"
fi

apply_password root "${root_password}"
apply_password "${atlas_user}" "${atlas_password}"

if [ -s "${key_file}" ]; then
  install_keys root
  install_keys "${atlas_user}"
  if [ -n "${ssh_user}" ] && [ "${ssh_user}" != "root" ] && [ "${ssh_user}" != "${atlas_user}" ]; then
    install_keys "${ssh_user}"
  fi
fi

if [ -s "${sudoers_file}" ]; then
  install -d -m 755 /etc/sudoers.d
  install -m 440 "${sudoers_file}" /etc/sudoers.d/90-hecate-atlas
  if command -v visudo >/dev/null 2>&1; then
    visudo -cf /etc/sudoers.d/90-hecate-atlas >/dev/null 2>&1 || rm -f /etc/sudoers.d/90-hecate-atlas
  fi
fi

systemctl restart ssh.service >/dev/null 2>&1 || systemctl restart sshd.service >/dev/null 2>&1 || systemctl restart ssh.socket >/dev/null 2>&1 || true
touch "${marker}"
`
}

func sshPasswordConfigContent(sec *secrets.NodeSecrets) string {
	if !hasNodePasswords(sec) {
		return ""
	}
	return "PasswordAuthentication yes\nKbdInteractiveAuthentication no\nChallengeResponseAuthentication no\nPermitRootLogin yes\nUsePAM yes\n"
}

func hasNodePasswords(sec *secrets.NodeSecrets) bool {
	if sec == nil {
		return false
	}
	return effectiveAtlasPassword(sec) != "" || strings.TrimSpace(sec.RootPassword) != ""
}

func effectiveAtlasPassword(sec *secrets.NodeSecrets) string {
	if sec == nil {
		return ""
	}
	return firstNonEmptyString(sec.AtlasPassword)
}

func firstNonEmptyString(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func redactedSecretsForImage(sec *secrets.NodeSecrets) map[string]any {
	if sec == nil {
		return nil
	}
	debug := map[string]any{
		"has_atlas_password":      strings.TrimSpace(sec.AtlasPassword) != "",
		"has_root_password":       strings.TrimSpace(sec.RootPassword) != "",
		"has_k3s_token":           strings.TrimSpace(sec.K3sToken) != "",
		"has_cloud_init_override": strings.TrimSpace(sec.CloudInit) != "",
	}
	if len(sec.Extra) > 0 {
		keys := make([]string, 0, len(sec.Extra))
		for key := range sec.Extra {
			key = strings.TrimSpace(key)
			if key == "" {
				continue
			}
			keys = append(keys, key)
		}
		sort.Strings(keys)
		debug["extra_keys"] = keys
	}
	return debug
}

func shellQuote(value string) string {
	if value == "" {
		return "''"
	}
	return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
}
