#!/usr/bin/env bash
# policy.module.sh — policy system
# Policies define behavioral flags for subnets, identities, and future contexts.
# Chain: Subnet → Policy → Identity → Peer

function policy::_hardcoded_field() {
  local name="${1:-}" field="${2:-}"
  # Only fallback for 'default' policy if policies.json is unavailable
  [[ "$name" != "default" ]] && echo "" && return 0
  case "$field" in
    tunnel_mode)  echo "split"  ;;
    default_rule) echo ""       ;;
    strict_rule)  echo "false"  ;;
    auto_apply)   echo "true"   ;;
    *)            echo ""       ;;
  esac
}

# ======================================================
# Core Accessors
# ======================================================

function policy::exists() {
  local name="${1:-}"
  json::policy_exists "$(ctx::policies)" "$name" 2>/dev/null
}

function policy::require_exists() {
  local name="${1:-}"
  if ! policy::exists "$name"; then
    log::error "Policy '${name}' not found. Use 'wgctl policy list' to see available policies."
    return 1
  fi
}

function policy::get() {
  local name="${1:-}" field="${2:-}"
  local result
  result=$(json::policy_get "$(ctx::policies)" "$name" "$field" 2>/dev/null) || true
  if [[ -n "$result" ]]; then
    echo "$result"
    return 0
  fi
  # Fallback to hardcoded
  [[ -n "$field" ]] && policy::_hardcoded_field "$name" "$field"
}

function policy::tunnel_mode() {
  local name="${1:-default}"
  policy::get "$name" "tunnel_mode"
}

function policy::default_rule() {
  local name="${1:-default}"
  policy::get "$name" "default_rule"
}

function policy::strict_rule() {
  local name="${1:-default}"
  local val
  val=$(policy::get "$name" "strict_rule")
  [[ "$val" == "true" ]]
}

function policy::auto_apply() {
  local name="${1:-default}"
  local val
  val=$(policy::get "$name" "auto_apply")
  [[ "$val" != "false" ]]
}

function policy::list_data() {
  json::policy_list "$(ctx::policies)" 2>/dev/null || true
}

# ======================================================
# Subnet Policy Resolution
# ======================================================

# policy::for_subnet <subnet_name> [type_key]
# Returns the policy name for a subnet entry.
# Falls back to "default" if no policy set on the subnet.
function policy::for_subnet() {
  local subnet_name="${1:-}" type_key="${2:-}"
  local result
  result=$(json::subnet_policy "$(ctx::subnets)" "$subnet_name" "$type_key" 2>/dev/null) || true
  echo "${result:-default}"
}

# policy::resolve_for_add <subnet_name> [type_key]
# Returns the fully resolved policy for use during wgctl add.
# Output: policy_name
function policy::resolve_for_add() {
  local subnet_name="${1:-}" type_key="${2:-}"
  if [[ -z "$subnet_name" ]]; then
    echo "default"
    return 0
  fi
  policy::for_subnet "$subnet_name" "$type_key"
}

# ======================================================
# Identity Policy Resolution
# ======================================================

# policy::for_identity <identity_name>
# Returns the policy name stored in an identity file.
# Falls back to "default".
function policy::for_identity() {
  local identity_name="${1:-}"
  local id_file
  id_file=$(ctx::identity::path "${identity_name}.identity")
  local result
  result=$(json::get "$id_file" "policy" 2>/dev/null) || true
  echo "${result:-default}"
}

# policy::effective <subnet_name> <type_key> <identity_name>
# Returns the effective policy name for a peer being added.
# Priority: identity policy > subnet policy > default
function policy::effective() {
  local subnet_name="${1:-}" type_key="${2:-}" identity_name="${3:-}"

  # Identity policy takes precedence if explicitly set
  if [[ -n "$identity_name" ]]; then
    local id_policy
    id_policy=$(policy::for_identity "$identity_name")
    if [[ "$id_policy" != "default" ]]; then
      echo "$id_policy"
      return 0
    fi
  fi

  # Subnet policy next
  if [[ -n "$subnet_name" ]]; then
    local subnet_policy
    subnet_policy=$(policy::for_subnet "$subnet_name" "$type_key")
    echo "$subnet_policy"
    return 0
  fi

  echo "default"
}

# ======================================================
# Warning Helpers (called from add.command.sh)
# ======================================================

function policy::warn_strict_rule() {
  local identity_name="${1:-}" policy_name="${2:-}" identity_rule="${3:-}"
  log::warn "Identity '${identity_name}' has policy '${policy_name}' with strict rule enabled — peer rule will not be applied, '${identity_rule}' is the only active rule"
}

function policy::warn_additive_rule() {
  local identity_name="${1:-}" identity_rule="${1:-}" peer_rule="${2:-}"
  log::info "Identity '${identity_name}' has strict rule disabled — '${identity_rule}' and '${peer_rule}' will both apply"
}

function policy::warn_no_rule() {
  local peer_name="${1:-}"
  log::warn "'${peer_name}' has no rule assigned — peer has unrestricted access"
}
