#!/usr/bin/env bash
# test/unit.sh — unit test sections
# Tests pure functions directly — no binary, no state changes.
# Sourced by test.command.sh — do not execute directly.

# ============================================
# Helpers
# ============================================

function cmd::test::assert() {
  local desc="${1:-}" result="${2:-}" expected="${3:-}"
  if [[ "$result" == "$expected" ]]; then
    test::pass "$desc"
  else
    test::fail "${desc} (expected '${expected}', got '${result}')"
  fi
}

function cmd::test::assert_true() {
  local desc="${1:-}"
  shift
  if "$@" 2>/dev/null; then
    test::pass "$desc"
  else
    test::fail "$desc (expected true, got false)"
  fi
}

function cmd::test::assert_false() {
  local desc="${1:-}"
  shift
  if ! "$@" 2>/dev/null; then
    test::pass "$desc"
  else
    test::fail "$desc (expected false, got true)"
  fi
}

# ============================================
# Sections
# ============================================

function cmd::test::run_all_unit_sections() {
  cmd::test::unit_subnet
  cmd::test::unit_ip
  cmd::test::unit_identity
}

function cmd::test::unit_subnet() {
  test::section "Unit: subnet CIDR utilities"
  load_module subnet

  # subnet::prefix
  cmd::test::assert "subnet::prefix /24"    "$(subnet::prefix  '10.1.3.0/24')"  "10.1.3"
  cmd::test::assert "subnet::prefix /16"    "$(subnet::prefix  '10.1.0.0/16')"  "10.1.0"
  cmd::test::assert "subnet::mask /24"      "$(subnet::mask    '10.1.3.0/24')"  "24"
  cmd::test::assert "subnet::mask /16"      "$(subnet::mask    '10.1.0.0/16')"  "16"
  cmd::test::assert "subnet::base_ip"       "$(subnet::base_ip '10.1.3.0/24')"  "10.1.3.0"

  # subnet::contains
  cmd::test::assert_true  "subnet::contains inside"    subnet::contains "10.1.3.0/24" "10.1.3.5"
  cmd::test::assert_true  "subnet::contains boundary"  subnet::contains "10.1.3.0/24" "10.1.3.254"
  cmd::test::assert_false "subnet::contains outside"   subnet::contains "10.1.3.0/24" "10.1.4.1"
  cmd::test::assert_false "subnet::contains wrong net" subnet::contains "10.1.3.0/24" "192.168.1.1"

  # subnet::is_valid_cidr
  cmd::test::assert_true  "is_valid_cidr valid"       subnet::is_valid_cidr "10.1.3.0/24"
  cmd::test::assert_true  "is_valid_cidr /16"         subnet::is_valid_cidr "10.1.0.0/16"
  cmd::test::assert_false "is_valid_cidr no mask"     subnet::is_valid_cidr "10.1.3.0"
  cmd::test::assert_false "is_valid_cidr bad octet"   subnet::is_valid_cidr "999.1.3.0/24"
  cmd::test::assert_false "is_valid_cidr empty"       subnet::is_valid_cidr ""

  # subnet::ip_valid_for
  cmd::test::assert_true  "ip_valid_for valid host"    subnet::ip_valid_for "10.1.3.0/24" "10.1.3.5"
  cmd::test::assert_false "ip_valid_for network addr"  subnet::ip_valid_for "10.1.3.0/24" "10.1.3.0"
  cmd::test::assert_false "ip_valid_for broadcast"     subnet::ip_valid_for "10.1.3.0/24" "10.1.3.255"
  cmd::test::assert_false "ip_valid_for wrong subnet"  subnet::ip_valid_for "10.1.3.0/24" "10.1.4.1"
  cmd::test::assert_false "ip_valid_for invalid ip"    subnet::ip_valid_for "10.1.3.0/24" "not-an-ip"
}

function cmd::test::unit_ip() {
  test::section "Unit: ip validation"
  load_module ip

  cmd::test::assert_true  "ip::is_valid plain"    ip::is_valid "10.1.3.5"
  cmd::test::assert_true  "ip::is_valid cidr"     ip::is_valid "10.1.3.0/24"
  cmd::test::assert_false "ip::is_valid empty"    ip::is_valid ""
  cmd::test::assert_false "ip::is_valid hostname" ip::is_valid "phone-nuno"
  cmd::test::assert_false "ip::is_valid bad oct"  ip::is_valid "999.1.3.5"

  cmd::test::assert_true  "ip::is_cidr with mask"    ip::is_cidr "10.1.3.0/24"
  cmd::test::assert_false "ip::is_cidr without mask" ip::is_cidr "10.1.3.5"
}

function cmd::test::unit_identity() {
  test::section "Unit: identity inference"
  load_module identity

  cmd::test::assert "infer phone-nuno"     "$(identity::infer 'phone-nuno')"     "nuno|phone|1"
  cmd::test::assert "infer phone-nuno-2"   "$(identity::infer 'phone-nuno-2')"   "nuno|phone|2"
  cmd::test::assert "infer desktop-zephyr" "$(identity::infer 'desktop-zephyr')" "zephyr|desktop|1"
  cmd::test::assert "infer laptop-nuno"    "$(identity::infer 'laptop-nuno')"    "nuno|laptop|1"
  cmd::test::assert "infer no convention"  "$(identity::infer 'roboclean')"      ""
  cmd::test::assert "infer guest-zephyr"   "$(identity::infer 'guest-zephyr')"   ""
}