#!/usr/bin/env bash

# ============================================
# Lifecycle
# ============================================
 
function cmd::add::on_load() {
  load_module subnet
  load_module identity
  load_module policy
 
  flag::register --name
  flag::register --identity
  flag::register --type
  flag::register --subnet
  flag::register --rule
  flag::register --group
  flag::register --ip
  flag::register --tunnel
  flag::register --show-qr
 
  # Dynamically register --<subnet_name> as shorthand flags
  local subnet_name
  while IFS= read -r subnet_name; do
    [[ -n "$subnet_name" ]] && flag::register "--${subnet_name}"
  done < <(subnet::list_names)
}
 
# ============================================
# Help
# ============================================
 
function cmd::add::help() {
  cat <<EOF
Usage: wgctl add --name <name> --type <type> [options]
   or: wgctl add --identity <identity> --type <type> [options]
 
Add a new WireGuard client.
 
Options:
  --name <name>         Client name (e.g. nuno) — combined with type: phone-nuno
  --identity <name>     Identity name — auto-names peer with next available index
  --type <type>         Device type: desktop, laptop, phone, tablet, server, iot
  --subnet <subnet>     Subnet to allocate from (default: type-native)
  --ip <ip>             Override auto-assigned IP (optional)
  --tunnel <mode>       Tunnel mode: split|full (overrides policy)
  --rule <rule>         Peer rule (default: from policy default_rule or none)
  --group <group>       Add to group on creation (group must exist)
  --show-qr             Show the WireGuard config as a QR code after creation
 
Subnet shorthands (equivalent to --subnet <name>):
  --guests, --servers, --iot, ... (see: wgctl subnet list)
 
Examples:
  wgctl add --name nuno --type phone
  wgctl add --identity nuno --type phone
  wgctl add --name zephyr --type desktop --guests
  wgctl add --identity zephyr --type desktop --guests
  wgctl add --name visitor --type phone --guests --show-qr
  wgctl add --name dev --type laptop --rule dev-01
EOF
}
 
# ============================================
# Validation
# ============================================
 
function cmd::add::_validate() {
  local name="$1" identity="$2" type="$3" ip="$4" tunnel="$5"
 
  if [[ -z "$name" && -z "$identity" ]]; then
    log::error "Missing required flag: --name or --identity"
    return 1
  fi
 
  if [[ -z "$type" ]]; then
    log::error "Missing required flag: --type"
    return 1
  fi
 
  if ! json::subnet_exists "$(ctx::subnets)" "$type" 2>/dev/null; then
    log::error "Unknown device type: '${type}'"
    log::info  "Use 'wgctl subnet list' to see valid types and subnets"
    return 1
  fi
 
  if [[ -n "$tunnel" && "$tunnel" != "split" && "$tunnel" != "full" ]]; then
    log::error "Invalid tunnel mode: '${tunnel}' (use 'split' or 'full')"
    return 1
  fi
 
  if [[ -n "$ip" ]]; then
    ip::require_valid "$ip"
  fi
}
 
function cmd::add::_validate_not_exists() {
  local full_name="$1"
  if [[ -f "$(ctx::clients)/${full_name}.conf" ]]; then
    log::error "Client already exists: ${full_name}"
    return 1
  fi
}
 
# ============================================
# Display helpers
# ============================================
 
function cmd::add::is_mobile() {
  local type="$1"
  [[ "$type" == "phone" || "$type" == "tablet" ]]
}
 
# ============================================
# Run
# ============================================
 
function cmd::add::run() {
  local name="" identity="" type="" subnet_name="" rule="" \
        group="" ip="" tunnel="" show_qr=false
 
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)     name="$2";        shift 2 ;;
      --identity) identity="$2";    shift 2 ;;
      --type)     type="$2";        shift 2 ;;
      --subnet)   subnet_name="$2"; shift 2 ;;
      --rule)     rule="$2";        shift 2 ;;
      --group)    group="$2";       shift 2 ;;
      --ip)       ip="$2";          shift 2 ;;
      --tunnel)   tunnel="$2";      shift 2 ;;
      --show-qr)  show_qr=true;     shift   ;;
      --help)     cmd::add::help;   return  ;;
      --*)
        local flag_name="${1#--}"
        if subnet::exists "$flag_name" 2>/dev/null; then
          subnet_name="$flag_name"
          shift
        else
          log::error "Unknown flag: $1"
          cmd::add::help
          return 1
        fi
        ;;
      *)
        log::error "Unknown flag: $1"
        cmd::add::help
        return 1
        ;;
    esac
  done
 
  cmd::add::_validate "$name" "$identity" "$type" "$ip" "$tunnel" || return 1
 
  # Resolve full peer name
  local full_name
  if [[ -n "$identity" ]]; then
    full_name=$(identity::next_peer_name "$identity" "$type") || return 1
    log::info "Auto-named: ${full_name}"
  else
    full_name="${type}-${name}"
  fi
 
  cmd::add::_validate_not_exists "$full_name" || return 1
 
  # Resolve subnet CIDR and canonical type
  local resolved_cidr resolved_type
  resolved_cidr=$(subnet::resolve_for_add "$type" "$subnet_name") || return 1
  resolved_type=$(subnet::type_for_add    "$type" "$subnet_name") || return 1
 
  # Resolve effective policy
  local identity_name="${identity:-$(identity::get_name "$full_name")}"
  local effective_policy
  effective_policy=$(policy::effective "$subnet_name" "$resolved_type" "$identity_name")
 
  # Resolve tunnel mode — flag overrides policy
  if [[ -z "$tunnel" ]]; then
    tunnel=$(policy::tunnel_mode "$effective_policy")
  fi
 
  # Resolve peer rule — explicit flag overrides policy default_rule
  if [[ -z "$rule" ]]; then
    rule=$(policy::default_rule "$effective_policy")
  fi
 
  # Validate rule if set
  if [[ -n "$rule" ]]; then
    rule::exists "$rule" || { log::error "Rule not found: ${rule}"; return 1; }
  fi
 
  local allowed_ips
  allowed_ips=$(config::allowed_ips_for "$tunnel") || return 1
 
  log::section "Adding client: ${full_name}"
 
  # Allocate IP
  if [[ -n "$ip" ]]; then
    subnet::require_ip_valid_for "$resolved_cidr" "$ip" || return 1
  else
    ip=$(ip::next_for_subnet "$resolved_cidr") || return 1
  fi
 
  cmd::add::_log_plan "$full_name" "$type" "$resolved_type" \
    "$subnet_name" "$resolved_cidr" "$ip" "$tunnel" \
    "$allowed_ips" "${rule:---}" "$effective_policy"
 
  keys::generate_pair "$full_name"                                               || return 1
  peers::create_client_config "$full_name" "$resolved_type" "$ip" "$allowed_ips" || return 1
 
  # Write meta — type, subnet, rule (if set)
  peers::set_meta "$full_name" "type" "$resolved_type"
  if [[ -n "$subnet_name" ]]; then
    peers::set_meta "$full_name" "subnet" "$subnet_name"
  fi
  if [[ -n "$rule" ]]; then
    peers::set_meta "$full_name" "rule" "$rule"
  fi
 
  cmd::add::_assign_group "$full_name" "$group"
 
  local public_key
  public_key=$(keys::public "$full_name")               || return 1
  peers::add_to_server "$full_name" "$public_key" "$ip"  || return 1
 
  # Apply peer rule if set
  if [[ -n "$rule" ]]; then
    rule::apply "$rule" "$ip" "$full_name" || return 1
  fi
 
  # Auto-attach to identity and apply identity rule if set
  identity::auto_attach "$full_name" "$resolved_type"
  cmd::add::_apply_identity_rule "$full_name" "$ip" "$identity_name" "$effective_policy" "$rule"
 
  peers::reload || return 1
 
  log::wg_success "Client added: ${full_name} (${ip}) [${tunnel} tunnel]"
  cmd::add::_show_result "$full_name" "$resolved_type" "$show_qr"
}
 
# ============================================
# Internal helpers
# ============================================
 
function cmd::add::_log_plan() {
  local full_name="${1:-}" type="${2:-}" resolved_type="${3:-}" \
        subnet_name="${4:-}" resolved_cidr="${5:-}" ip="${6:-}" \
        tunnel="${7:-}" allowed_ips="${8:-}" rule="${9:-}" policy="${10:-}"
 
  log::wg_add "Name:     ${full_name}"
  log::wg_add "Type:     ${resolved_type}"
  [[ -n "$subnet_name" ]] && log::wg_add "Subnet:   ${subnet_name} (${resolved_cidr})"
  log::wg_add "IP:       ${ip}"
  log::wg_add "Tunnel:   ${tunnel} (${allowed_ips})"
  log::wg_add "Endpoint: $(config::endpoint)"
  log::wg_add "Rule:     ${rule}"
  log::wg_add "Policy:   ${policy}"
}
 
function cmd::add::_assign_group() {
  local full_name="${1:-}" group="${2:-}"
  [[ -z "$group" ]] && return 0
  if ! group::exists "$group"; then
    log::wg_warning "Group '${group}' not found — skipping group assignment"
    return 0
  fi
  group::add_peer "$group" "$full_name"
  log::wg "Added to group: ${group}"
}
 
function cmd::add::_apply_identity_rule() {
  local full_name="${1:-}" ip="${2:-}" identity_name="${3:-}" \
        effective_policy="${4:-}" peer_rule="${5:-}"
 
  [[ -z "$identity_name" ]] && return 0
 
  local rules
  rules=$(identity::rules "$identity_name")
 
  if [[ -z "$rules" ]]; then
    # No identity rules — warn if no peer rule either
    if [[ -z "$peer_rule" ]]; then
      policy::warn_no_rule "$full_name"
    fi
    return 0
  fi
 
  # Apply all identity rules
  rule::_apply_identity_rule "$full_name" "$ip"
 
  # Warn based on strict_rule
  local strict
  strict=$(identity::rule_flags "$identity_name" "strict_rule")
  if [[ "$strict" == "true" ]]; then
    local rule_list
    rule_list=$(echo "$rules" | tr '\n' ',' | sed 's/,$//')
    policy::warn_strict_rule "$identity_name" "$effective_policy" "$rule_list"
  elif [[ -n "$peer_rule" ]]; then
    local rule_list
    rule_list=$(echo "$rules" | tr '\n' ',' | sed 's/,$//')
    policy::warn_additive_rule "$identity_name" "$rule_list" "$peer_rule"
  fi
}

function cmd::add::_show_result() {
  local full_name="${1:-}" type="${2:-}" show_qr="${3:-false}"
  if $show_qr || cmd::add::is_mobile "$type"; then
    log::section "Client QR"
    keys::qr "$full_name"
  else
    log::section "Client Config"
    cat "$(ctx::clients)/${full_name}.conf"
  fi
}
