#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
INVENTORY_FILE="${HOMELAB_INVENTORY_FILE:-${REPO_ROOT}/homelab.yml}"
MIN_DAYS="${LAB_CERT_MIN_DAYS:-21}"
DEFAULT_DOMAIN="${LAB_DOMAIN:-lab2025.duckdns.org}"
DEFAULT_PUBLIC_URL="${LAB_PUBLIC_URL:-https://${DEFAULT_DOMAIN}/}"
DEFAULT_GITEA_URL="${LAB_GITEA_URL:-https://${DEFAULT_DOMAIN}/git/jv/my-homelab-configs}"

failures=0
hosts_file=""

cleanup() {
    if [ -n "$hosts_file" ] && [ -f "$hosts_file" ]; then
        rm -f "$hosts_file"
    fi
}
trap cleanup EXIT

section() {
    printf '\n== %s ==\n' "$1"
}

have() {
    command -v "$1" >/dev/null 2>&1
}

mark_fail() {
    failures=$((failures + 1))
    printf 'fail: %s\n' "$1"
}

inventory_scalar() {
    local path="$1"

    if have ruby && [ -f "$INVENTORY_FILE" ]; then
        ruby -ryaml -e '
          data = YAML.load_file(ARGV[0])
          value = ARGV[1].split(".").reduce(data) { |memo, key| memo.respond_to?(:[]) ? memo[key] : nil }
          puts value if value.is_a?(String)
        ' "$INVENTORY_FILE" "$path" 2>/dev/null || true
    fi
}

inventory_hosts() {
    if have ruby && [ -f "$INVENTORY_FILE" ]; then
        ruby -ryaml -e '
          data = YAML.load_file(ARGV[0])
          domain = data.fetch("domain", {})
          hosts = [domain["base"]] + Array(domain["subdomains"])
          puts hosts.compact.uniq
        ' "$INVENTORY_FILE" 2>/dev/null || true
    fi
}

domain_base="$(inventory_scalar domain.base)"
public_url="$(inventory_scalar domain.public_url)"
gitea_url="$(inventory_scalar domain.gitea_url)"

domain_base="${domain_base:-$DEFAULT_DOMAIN}"
public_url="${public_url:-$DEFAULT_PUBLIC_URL}"
gitea_url="${gitea_url:-$DEFAULT_GITEA_URL}"

hosts="$(inventory_hosts)"
if [ -z "$hosts" ]; then
    hosts="$domain_base"
fi

hosts_file="$(mktemp "${TMPDIR:-/tmp}/homelab-cert-hosts.XXXXXX")"
printf '%s\n' "$hosts" >"$hosts_file"

section "DNS Resolution"
while IFS= read -r host; do
    [ -n "$host" ] || continue
    if have getent; then
        if getent hosts "$host" >/dev/null 2>&1; then
            printf '%-40s ok\n' "$host"
        else
            printf '%-40s fail\n' "$host"
            failures=$((failures + 1))
        fi
    elif have dig; then
        if dig "$host" A +time=3 +tries=1 +short | grep -q .; then
            printf '%-40s ok\n' "$host"
        else
            printf '%-40s fail\n' "$host"
            failures=$((failures + 1))
        fi
    else
        echo "getent or dig is required for DNS checks."
        failures=$((failures + 1))
        break
    fi
done <"$hosts_file"

section "TLS Expiry"
while IFS= read -r host; do
    [ -n "$host" ] || continue
    if python3 - "$host" "$MIN_DAYS" <<'PY'
import datetime
import os
import socket
import ssl
import sys

host = sys.argv[1]
minimum_days = int(sys.argv[2])
context = ssl.create_default_context()

try:
    with socket.create_connection((host, 443), timeout=8) as sock:
        with context.wrap_socket(sock, server_hostname=host) as tls_sock:
            cert = tls_sock.getpeercert()
except Exception as exc:
    print(f"{host:<40} fail tls_connect={exc}")
    sys.exit(1)

not_after = datetime.datetime.strptime(
    cert["notAfter"], "%b %d %H:%M:%S %Y %Z"
).replace(tzinfo=datetime.timezone.utc)
days = (not_after - datetime.datetime.now(datetime.timezone.utc)).days
issuer_parts = cert.get("issuer", ())
issuer = ",".join("=".join(item) for group in issuer_parts for item in group)
status = "ok" if days >= minimum_days else "warn"
print(f"{host:<40} {status} expires={not_after.date()} days={days} issuer={issuer}")
sys.exit(0 if days >= minimum_days else 1)
PY
    then
        :
    else
        failures=$((failures + 1))
    fi
done <"$hosts_file"

section "Public HTTP"
for url in "$public_url" "$gitea_url"; do
    status="$(curl -k -sS -o /dev/null -w '%{http_code}' --max-time 10 "$url" 2>/dev/null || true)"
    case "$status" in
        200|301|302|307|308)
            printf '%-60s ok http=%s\n' "$url" "$status"
            ;;
        *)
            printf '%-60s fail http=%s\n' "$url" "${status:-none}"
            failures=$((failures + 1))
            ;;
    esac
done

section "Edge IP Drift"
edge_ip="$(inventory_scalar hosts.oci_edge.public_ip)"
if [ -n "$edge_ip" ] && have dig; then
    resolved="$(dig "$domain_base" A +time=3 +tries=1 +short | tail -1)"
    if [ "$resolved" = "$edge_ip" ]; then
        printf '%-40s ok resolved=%s\n' "$domain_base" "$resolved"
    else
        mark_fail "$domain_base resolves to ${resolved:-none}, expected OCI edge $edge_ip"
    fi
else
    echo "Skipping edge IP drift check; dig or OCI edge inventory value is unavailable."
fi

if [ "$failures" -gt 0 ]; then
    printf '\ncert-check found %s issue(s).\n' "$failures"
    exit 1
fi

printf '\ncert-check found no blocking issues.\n'
