#!/usr/bin/env bash

# ─── local init hook ────────────────────────────────────────────────────────
# If test-init.local.sh exists alongside this file, source it before anything
# else.  Use it to set up your local environment (e.g. activate nvm, set
# DOCKER_IMAGE, ...).  The file is git-ignored so it is safe to put personal
# or machine-specific configuration there.
_TEST_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
[[ -f "$_TEST_HELPERS_DIR/test-init.local.sh" ]] && source "$_TEST_HELPERS_DIR/test-init.local.sh"
unset _TEST_HELPERS_DIR

cli_test_init_cli_cmd() {
    if [[ "${VERBOSE_TEST_LOGGING:-0}" == "1" ]]; then
        CLI_CMD=(npm --silent run cli -- -v)
    else
        CLI_CMD=(npm --silent run cli --)
    fi
}

run_cli() {
    "${CLI_CMD[@]}" "$@"
}

cli_test_require_env() {
    local var_name="$1"
    local env_file="${2:-${TEST_ENV_FILE:-environment}}"
    if [[ -z "${!var_name:-}" ]]; then
        echo "[ERROR] required variable '$var_name' is missing in $env_file" >&2
        exit 1
    fi
}

cli_test_assert_contains() {
    local haystack="$1"
    local needle="$2"
    local message="$3"
    if ! grep -Fq "$needle" <<< "$haystack"; then
        echo "[FAIL] $message" >&2
        echo "[FAIL] expected to find: $needle" >&2
        echo "[FAIL] actual output:" >&2
        echo "$haystack" >&2
        exit 1
    fi
}

cli_test_assert_equal() {
    local expected="$1"
    local actual="$2"
    local message="$3"
    if [[ "$expected" != "$actual" ]]; then
        echo "[FAIL] $message" >&2
        echo "[FAIL] expected: $expected" >&2
        echo "[FAIL] actual:   $actual" >&2
        exit 1
    fi
}

cli_test_assert_command_fails() {
    local message="$1"
    local log_file="$2"
    shift 2
    set +e
    "$@" >"$log_file" 2>&1
    local exit_code=$?
    set -e
    if [[ "$exit_code" -eq 0 ]]; then
        echo "[FAIL] $message" >&2
        cat "$log_file" >&2
        exit 1
    fi
}

cli_test_assert_files_equal() {
    local expected_file="$1"
    local actual_file="$2"
    local message="$3"
    if ! cmp -s "$expected_file" "$actual_file"; then
        echo "[FAIL] $message" >&2
        echo "[FAIL] expected sha256: $(sha256sum "$expected_file" | awk '{print $1}')" >&2
        echo "[FAIL] actual   sha256: $(sha256sum "$actual_file" | awk '{print $1}')" >&2
        exit 1
    fi
}

cli_test_sanitise_cat_stdout() {
    sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d'
}

cli_test_json_string_field_from_stdin() {
    local field_name="$1"
    node -e '
const fs = require("node:fs");
const fieldName = process.argv[1];
const data = JSON.parse(fs.readFileSync(0, "utf-8"));
const value = data[fieldName];
if (typeof value === "string") {
    process.stdout.write(value);
}
' "$field_name"
}

cli_test_json_string_field_from_file() {
    local json_file="$1"
    local field_name="$2"
    node -e '
const fs = require("node:fs");
const jsonFile = process.argv[1];
const fieldName = process.argv[2];
const data = JSON.parse(fs.readFileSync(jsonFile, "utf-8"));
const value = data[fieldName];
if (typeof value === "string") {
    process.stdout.write(value);
}
' "$json_file" "$field_name"
}

cli_test_json_field_is_na() {
    local json_file="$1"
    local field_name="$2"
    [[ "$(cli_test_json_string_field_from_file "$json_file" "$field_name")" == "N/A" ]]
}

cli_test_curl_json() {
    curl -4 -sS --fail --connect-timeout 3 --max-time 15 "$@"
}

cli_test_init_settings_file() {
    local settings_file="$1"
    run_cli init-settings --force "$settings_file" >/dev/null
}

cli_test_mark_settings_configured() {
    local settings_file="$1"
    SETTINGS_FILE="$settings_file" node <<'NODE'
const fs = require("node:fs");
const settingsPath = process.env.SETTINGS_FILE;
const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
data.isConfigured = true;
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
NODE
}

cli_test_apply_couchdb_settings() {
    local settings_file="$1"
    local couchdb_uri="$2"
    local couchdb_user="$3"
    local couchdb_password="$4"
    local couchdb_dbname="$5"
    local live_sync="${6:-0}"
    SETTINGS_FILE="$settings_file" \
    COUCHDB_URI="$couchdb_uri" \
    COUCHDB_USER="$couchdb_user" \
    COUCHDB_PASSWORD="$couchdb_password" \
    COUCHDB_DBNAME="$couchdb_dbname" \
    LIVE_SYNC="$live_sync" \
    node <<'NODE'
const fs = require("node:fs");
const settingsPath = process.env.SETTINGS_FILE;
const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
data.couchDB_URI = process.env.COUCHDB_URI;
data.couchDB_USER = process.env.COUCHDB_USER;
data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD;
data.couchDB_DBNAME = process.env.COUCHDB_DBNAME;
if (process.env.LIVE_SYNC === "1") {
    data.liveSync = true;
    data.syncOnStart = false;
    data.syncOnSave = false;
    data.usePluginSync = false;
}
data.isConfigured = true;
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
NODE
}

cli_test_apply_remote_sync_settings() {
    local settings_file="$1"
    SETTINGS_FILE="$settings_file" \
    REMOTE_TYPE="$REMOTE_TYPE" \
    COUCHDB_URI="$COUCHDB_URI" \
    COUCHDB_USER="${COUCHDB_USER:-}" \
    COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-}" \
    COUCHDB_DBNAME="$COUCHDB_DBNAME" \
    MINIO_ENDPOINT="${MINIO_ENDPOINT:-}" \
    MINIO_BUCKET="$MINIO_BUCKET" \
    MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-}" \
    MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-}" \
    ENCRYPT="${ENCRYPT:-0}" \
    E2E_PASSPHRASE="${E2E_PASSPHRASE:-}" \
    node <<'NODE'
const fs = require("node:fs");
const settingsPath = process.env.SETTINGS_FILE;
const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));

const remoteType = process.env.REMOTE_TYPE;
if (remoteType === "COUCHDB") {
    data.remoteType = "";
    data.couchDB_URI = process.env.COUCHDB_URI;
    data.couchDB_USER = process.env.COUCHDB_USER;
    data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD;
    data.couchDB_DBNAME = process.env.COUCHDB_DBNAME;
} else if (remoteType === "MINIO") {
    data.remoteType = "MINIO";
    data.bucket = process.env.MINIO_BUCKET;
    data.endpoint = process.env.MINIO_ENDPOINT;
    data.accessKey = process.env.MINIO_ACCESS_KEY;
    data.secretKey = process.env.MINIO_SECRET_KEY;
    data.region = "auto";
    data.forcePathStyle = true;
}

data.liveSync = true;
data.syncOnStart = false;
data.syncOnSave = false;
data.usePluginSync = false;
data.encrypt = process.env.ENCRYPT === "1";
data.passphrase = data.encrypt ? process.env.E2E_PASSPHRASE : "";
data.isConfigured = true;

fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
NODE
}

cli_test_apply_p2p_settings() {
    local settings_file="$1"
    local room_id="$2"
    local passphrase="$3"
    local app_id="${4:-self-hosted-livesync-cli-tests}"
    local relays="${5:-ws://localhost:4000/}"
    local auto_accept="${6:-~.*}"
    SETTINGS_FILE="$settings_file" \
    P2P_ROOM_ID="$room_id" \
    P2P_PASSPHRASE="$passphrase" \
    P2P_APP_ID="$app_id" \
    P2P_RELAYS="$relays" \
    P2P_AUTO_ACCEPT="$auto_accept" \
    node <<'NODE'
const fs = require("node:fs");
const settingsPath = process.env.SETTINGS_FILE;
const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));

data.P2P_Enabled = true;
data.P2P_AutoStart = false;
data.P2P_AutoBroadcast = false;
data.P2P_AppID = process.env.P2P_APP_ID;
data.P2P_roomID = process.env.P2P_ROOM_ID;
data.P2P_passphrase = process.env.P2P_PASSPHRASE;
data.P2P_relays = process.env.P2P_RELAYS;
data.P2P_AutoAcceptingPeers = process.env.P2P_AUTO_ACCEPT;
data.P2P_AutoDenyingPeers = "";
data.P2P_IsHeadless = true;
data.isConfigured = true;

fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
NODE
}

cli_test_is_local_p2p_relay() {
    local relay_url="$1"
    [[ "$relay_url" == "ws://localhost:4000" || "$relay_url" == "ws://localhost:4000/" ]]
}

cli_test_stop_p2p_relay() {
    bash "$CLI_DIR/util/p2p-stop.sh" >/dev/null 2>&1 || true
}

cli_test_start_p2p_relay() {
    echo "[INFO] stopping leftover P2P relay container if present"
    cli_test_stop_p2p_relay

    echo "[INFO] starting local P2P relay container"
    bash "$CLI_DIR/util/p2p-start.sh"
}

cli_test_stop_couchdb() {
    bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true
}

cli_test_start_couchdb() {
    local couchdb_uri="$1"
    local couchdb_user="$2"
    local couchdb_password="$3"
    local couchdb_dbname="$4"
    echo "[INFO] stopping leftover CouchDB container if present"
    cli_test_stop_couchdb

    echo "[INFO] starting CouchDB test container"
    bash "$CLI_DIR/util/couchdb-start.sh"

    echo "[INFO] initialising CouchDB test container"
    bash "$CLI_DIR/util/couchdb-init.sh"

    echo "[INFO] CouchDB create test database: $couchdb_dbname"
    until (cli_test_curl_json -X PUT --user "${couchdb_user}:${couchdb_password}" "${couchdb_uri}/${couchdb_dbname}"); do sleep 5; done
}

cli_test_stop_minio() {
    bash "$CLI_DIR/util/minio-stop.sh" >/dev/null 2>&1 || true
}

cli_test_wait_for_minio_bucket() {
    local minio_endpoint="$1"
    local minio_access_key="$2"
    local minio_secret_key="$3"
    local minio_bucket="$4"
    local retries=30
    local delay_sec=2
    local i
    for ((i = 1; i <= retries; i++)); do
        if docker run --rm --network host --entrypoint=/bin/sh minio/mc -c "mc alias set myminio $minio_endpoint $minio_access_key $minio_secret_key >/dev/null 2>&1 && mc ls myminio/$minio_bucket >/dev/null 2>&1"; then
            return 0
        fi
        bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-init.sh" >/dev/null 2>&1 || true
        sleep "$delay_sec"
    done
    return 1
}

cli_test_start_minio() {
    local minio_endpoint="$1"
    local minio_access_key="$2"
    local minio_secret_key="$3"
    local minio_bucket="$4"
    local minio_init_ok=0

    echo "[INFO] stopping leftover MinIO container if present"
    cli_test_stop_minio

    echo "[INFO] starting MinIO test container"
    bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-start.sh"

    echo "[INFO] initialising MinIO test bucket: $minio_bucket"
    for _ in 1 2 3 4 5; do
        if bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-init.sh"; then
            minio_init_ok=1
            break
        fi
        sleep 2
    done
    if [[ "$minio_init_ok" != "1" ]]; then
        echo "[FAIL] could not initialise MinIO bucket after retries: $minio_bucket" >&2
        exit 1
    fi
    if ! cli_test_wait_for_minio_bucket "$minio_endpoint" "$minio_access_key" "$minio_secret_key" "$minio_bucket"; then
        echo "[FAIL] MinIO bucket not ready: $minio_bucket" >&2
        exit 1
    fi
}

display_test_info(){
    echo "======================"
    echo "Script: ${BASH_SOURCE[1]:-$0}"
    echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    echo "Git commit: $(git -C "$SCRIPT_DIR/.." rev-parse --short HEAD 2>/dev/null || echo "N/A")"
    echo "======================"
}

# Docker-mode hook — source overrides when LIVESYNC_TEST_DOCKER=1.
if [[ "${LIVESYNC_TEST_DOCKER:-0}" == "1" ]]; then
    # shellcheck source=/dev/null
    source "$(dirname "${BASH_SOURCE[0]}")/test-helpers-docker.sh"
fi