#!/command/with-contenv sh

# AliasVault Container Initialization Script
# This script runs once at container startup and handles all initialization tasks

# Get verbosity level (0=minimal, 1=normal, 2=verbose)
VERBOSITY="${ALIASVAULT_VERBOSITY:-0}"

# Helper function for logging based on verbosity
log() {
    level=$1
    shift
    if [ "$VERBOSITY" -ge "$level" ]; then
        echo "$@"
    fi
}

# Always show header and critical messages (level 0)
echo ""
echo "=================================================="
echo "    _    _ _           __      __         _ _   "
echo "   / \\  | (_) __ _ ___ \\ \\    / /_ _ _   _| | |_"
echo "  / _ \\ | | |/ _\` / __| \\ \\/\\/ / _\` | | | | | __|"
echo " / ___ \\| | | (_| \\__ \\  \\  / / (_| | |_| | | |_ "
echo "/_/   \\_\\_|_|\\__,_|___/   \\/  \\__,__|\\__,_|_|\\__|"
echo ""
echo "=================================================="
echo ""

echo "[init] Starting AliasVault..."
echo ""

# Create required directories
if [ ! -d /database/postgres ] || [ ! -d /logs/postgres ] || [ ! -d /certificates/ssl ] || [ ! -d /certificates/smtp ] || [ ! -d /secrets ] || [ ! -d /var/run/postgresql ]; then
    mkdir -p /database/postgres /logs/postgres /certificates/ssl /certificates/smtp /secrets /var/run/postgresql
    log 0 "[init] Required directories created"
else
    log 0 "[init] ✅ Required directories already exist"
fi

if [ ! -f /secrets/postgres_password ]; then
    log 0 "[init] → Generating PostgreSQL password..."
    openssl rand -base64 32 | tr -d "\n" > /secrets/postgres_password
    chmod 600 /secrets/postgres_password
else
    log 0 "[init] ✅ PostgreSQL password already exists "
fi

if [ ! -f /secrets/data_protection_cert_pass ]; then
    log 0 "[init] → Generating Data Protection Certificate password..."
    openssl rand -base64 32 | tr -d "\n" > /secrets/data_protection_cert_pass
    chmod 600 /secrets/data_protection_cert_pass
else
    log 0 "[init] ✅ Data Protection Certificate password already exists"
fi

if [ ! -f /secrets/jwt_key ]; then
    log 0 "[init] → Generating JWT key..."
    openssl rand -base64 32 | tr -d "\n" > /secrets/jwt_key
    chmod 600 /secrets/jwt_key
else
    log 0 "[init] ✅ JWT key already exists"
fi

# Admin password is not created by default
# Users must run reset-admin-password.sh to configure the admin password
log 0 "[init] ✅ Admin password configuration deferred to manual setup"

# Read PostgreSQL password for database initialization
POSTGRES_PASSWORD=$(cat /secrets/postgres_password)
export PGDATA="/database/postgres"

# Marker written only after the AliasVault role + database have been fully
# provisioned, to allow for self-heal in case of errors during first startup.
PROVISION_MARKER="$PGDATA/.aliasvault_provisioned"

# Step 1: initialize the physical PostgreSQL cluster if the data dir is empty.
if [ ! -d "$PGDATA/base" ]; then
    log 0 ""
    log 0 "[init] PostgreSQL data directory not found, initializing cluster..."

    # Set proper permissions
    chown -R postgres:postgres /database/postgres /logs/postgres /var/run/postgresql
    chmod 700 /database/postgres

    # Initialize database with "aliasvault" as the bootstrap superuser, matching
    # the multi-container setup (official postgres image with POSTGRES_USER=aliasvault)
    # so data dirs stay interchangeable between both setups.
    log 1 "[init] → Running initdb..."
    if [ "$VERBOSITY" -ge 2 ]; then
        su - postgres -c "/usr/lib/postgresql/16/bin/initdb -D $PGDATA --username=aliasvault --locale=C.UTF-8 --encoding=UTF8" 2>&1 | tee /logs/postgres/initdb.log
    else
        su - postgres -c "/usr/lib/postgresql/16/bin/initdb -D $PGDATA --username=aliasvault --locale=C.UTF-8 --encoding=UTF8" > /logs/postgres/initdb.log 2>&1
    fi

    # Fresh cluster always needs (re)provisioning
    rm -f "$PROVISION_MARKER"

    # Configure PostgreSQL
    log 0 "[init] → Configuring PostgreSQL..."

    # Listen on all container interfaces (covers localhost & Docker networks)
    echo "listen_addresses = '*'" >> "$PGDATA/postgresql.conf"
    # Store SCRAM hashes instead of MD5
    echo "password_encryption = scram-sha-256" >> "$PGDATA/postgresql.conf"
    # Allow local Unix-socket connections (processes inside the same container)
    echo "local   all   all                                   trust" >> "$PGDATA/pg_hba.conf"
    # Allow any client on the same subnet(s) as this container (bridge/overlay)
    # Prefer SCRAM if enabled above; otherwise MD5
    if grep -qi '^password_encryption *= *scram-sha-256' "$PGDATA/postgresql.conf"; then
        AUTH_METHOD="scram-sha-256"
    else
        AUTH_METHOD="md5"
    fi
    echo "host    all   all           samenet                  ${AUTH_METHOD}" >> "$PGDATA/pg_hba.conf"
else
    log 0 "[init] ✅ PostgreSQL data directory already initialized"

    # Ensure proper permissions
    chown -R postgres:postgres /database/postgres /logs/postgres /var/run/postgresql
    chmod 700 /database/postgres
fi

# Step 2: ensure the AliasVault role + database exist
if [ ! -f "$PROVISION_MARKER" ]; then
    log 0 ""
    log 0 "[init] → Provisioning AliasVault database and user..."

    # Fail the whole init if provisioning cannot complete, so the failure
    # is visible in the container logs and retried on the next start.
    fail_init() {
        echo "[init] ❌ ERROR: $1" >&2
        echo "[init]    See /logs/postgres/postgres.log for details." >&2
        echo "[init]    Provisioning will be retried on the next container restart." >&2
        su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA stop" >/dev/null 2>&1
        exit 1
    }

    # Start PostgreSQL temporarily for provisioning
    log 0 "[init] → Starting PostgreSQL temporarily for setup..."
    if [ "$VERBOSITY" -ge 2 ]; then
        su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA -l /logs/postgres/postgres.log start"
    else
        su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA -l /logs/postgres/postgres.log start" >/dev/null 2>&1
    fi

    # Wait until PostgreSQL actually accepts connections
    log 0 "[init] → Waiting for PostgreSQL to be ready..."
    PG_READY=0
    i=1
    while [ $i -le 60 ]; do
        if su - postgres -c "/usr/lib/postgresql/16/bin/pg_isready -q -U aliasvault -d postgres"; then
            PG_READY=1
            break
        fi
        sleep 1
        i=$((i + 1))
    done
    [ "$PG_READY" -eq 1 ] || fail_init "PostgreSQL did not become ready within 60 seconds."

    # Determine which superuser role this cluster was initialized with. New
    # clusters and multi-container data dirs use "aliasvault"; clusters created
    # by older versions of this image use "postgres".
    PG_SUPERUSER=""
    for CANDIDATE in aliasvault postgres; do
        if su - postgres -c "/usr/lib/postgresql/16/bin/psql -U $CANDIDATE -d postgres -tAc 'SELECT 1'" >/dev/null 2>&1; then
            PG_SUPERUSER="$CANDIDATE"
            break
        fi
    done
    [ -n "$PG_SUPERUSER" ] || fail_init "Could not connect to PostgreSQL as a superuser (tried roles: postgres, aliasvault)."

    # Helper: run a single SQL statement as the cluster superuser, stopping on the
    # first server error (-v ON_ERROR_STOP=1) so failures surface as a non-zero exit.
    pg_super() {
        if [ "$VERBOSITY" -ge 2 ]; then
            su - postgres -c "/usr/lib/postgresql/16/bin/psql -U $PG_SUPERUSER -d postgres -v ON_ERROR_STOP=1 -tAc \"$1\""
        else
            su - postgres -c "/usr/lib/postgresql/16/bin/psql -U $PG_SUPERUSER -d postgres -v ON_ERROR_STOP=1 -tAc \"$1\"" >/dev/null 2>&1
        fi
    }

    pg_super_query() {
        su - postgres -c "/usr/lib/postgresql/16/bin/psql -U $PG_SUPERUSER -d postgres -v ON_ERROR_STOP=1 -tAc \"$1\"" 2>/dev/null
    }

    # Create the role if it does not already exist.
    ESCAPED_PASSWORD=$(printf '%s' "$POSTGRES_PASSWORD" | sed "s/'/''/g")
    if [ "$(pg_super_query "SELECT 1 FROM pg_roles WHERE rolname='aliasvault'")" = "1" ]; then
        pg_super "ALTER USER aliasvault WITH PASSWORD '$ESCAPED_PASSWORD';" \
            || fail_init "Failed to update the aliasvault role password."
    else
        pg_super "CREATE USER aliasvault WITH PASSWORD '$ESCAPED_PASSWORD';" \
            || fail_init "Failed to create the aliasvault role."
    fi

    # Create the database if it does not already exist.
    if [ "$(pg_super_query "SELECT 1 FROM pg_database WHERE datname='aliasvault'")" != "1" ]; then
        pg_super "CREATE DATABASE aliasvault OWNER aliasvault LOCALE 'C.UTF-8';" \
            || fail_init "Failed to create the aliasvault database."
    fi

    pg_super "GRANT ALL PRIVILEGES ON DATABASE aliasvault TO aliasvault;" \
        || fail_init "Failed to grant privileges on the aliasvault database."

    # Mark provisioning complete only after every step above succeeded.
    touch "$PROVISION_MARKER"

    # Stop PostgreSQL
    log 0 "[init] → Stopping PostgreSQL..."
    if [ "$VERBOSITY" -ge 2 ]; then
        su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA -w stop"
    else
        su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA -w stop" >/dev/null 2>&1
    fi
    sleep 2

    log 0 "[init] PostgreSQL initialization complete"
else
    log 0 "[init] ✅ AliasVault database and user already provisioned"
fi

# Function to check if certificate needs regeneration
needs_cert_regeneration() {
    # If cert doesn't exist, need to generate
    if [ ! -f /certificates/ssl/cert.pem ] || [ ! -f /certificates/ssl/key.pem ]; then
        return 0
    fi

    # Check if we have a hostname marker file
    if [ -f /certificates/ssl/.hostname_marker ]; then
        STORED_HOSTNAME=$(cat /certificates/ssl/.hostname_marker)
        if [ "$STORED_HOSTNAME" != "${HOSTNAME:-localhost}" ]; then
            log 0 "[init] Hostname changed from '$STORED_HOSTNAME' to '${HOSTNAME:-localhost}', regenerating certificate..."
            return 0
        fi
    else
        # No marker file, regenerate to be safe
        return 0
    fi

    return 1
}

# Function to check if a string is an IP address (IPv4 or IPv6)
is_ip_address() {
    local value="$1"

    # Check for IPv4 pattern
    if echo "$value" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then
        # Validate each octet is <= 255
        local valid=1
        # Use a simple approach to split the IP address
        local o1=$(echo "$value" | cut -d. -f1)
        local o2=$(echo "$value" | cut -d. -f2)
        local o3=$(echo "$value" | cut -d. -f3)
        local o4=$(echo "$value" | cut -d. -f4)
        for octet in "$o1" "$o2" "$o3" "$o4"; do
            if [ "$octet" -gt 255 ]; then
                valid=0
                break
            fi
        done
        if [ "$valid" -eq 1 ]; then
            return 0  # It's a valid IPv4
        fi
    fi

    # Check for IPv6 pattern (simplified check)
    if echo "$value" | grep -qE '^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$|^::1$|^::$'; then
        return 0  # It's likely IPv6
    fi

    return 1  # Not an IP address
}

# Generate SSL certificates if needed or hostname changed
if needs_cert_regeneration; then
    log 0 ""
    log 0 "[init] Generating SSL certificates..."
    mkdir -p /certificates/ssl

    HOSTNAME_VALUE="${HOSTNAME:-localhost}"

    if [ "$HOSTNAME_VALUE" = "localhost" ] || [ -z "$HOSTNAME_VALUE" ]; then
        # Default localhost-only configuration
        if [ "$VERBOSITY" -ge 2 ]; then
            openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
                -keyout /certificates/ssl/key.pem \
                -out /certificates/ssl/cert.pem \
                -subj "/C=US/ST=State/L=City/O=AliasVault/CN=localhost"
        else
            openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
                -keyout /certificates/ssl/key.pem \
                -out /certificates/ssl/cert.pem \
                -subj "/C=US/ST=State/L=City/O=AliasVault/CN=localhost" \
                >/dev/null 2>&1
        fi
    else
        # Determine if the hostname is an IP address or a DNS name
        if is_ip_address "$HOSTNAME_VALUE"; then
            # It's an IP address - use IP: prefix in SAN
            SAN_ENTRY="IP:${HOSTNAME_VALUE}"
            log 1 "[init] Detected IP address: ${HOSTNAME_VALUE}"
        else
            # It's a DNS name - use DNS: prefix in SAN
            SAN_ENTRY="DNS:${HOSTNAME_VALUE}"
            log 1 "[init] Detected hostname: ${HOSTNAME_VALUE}"
        fi

        # Generate certificate with the appropriate SAN entry
        if [ "$VERBOSITY" -ge 2 ]; then
            openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
                -keyout /certificates/ssl/key.pem \
                -out /certificates/ssl/cert.pem \
                -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \
                -addext "subjectAltName=${SAN_ENTRY},DNS:localhost,IP:127.0.0.1"
        else
            openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
                -keyout /certificates/ssl/key.pem \
                -out /certificates/ssl/cert.pem \
                -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \
                -addext "subjectAltName=${SAN_ENTRY},DNS:localhost,IP:127.0.0.1" \
                >/dev/null 2>&1
        fi
    fi

    chmod 600 /certificates/ssl/key.pem
    chmod 644 /certificates/ssl/cert.pem

    # Store current hostname for change detection
    echo "${HOSTNAME:-localhost}" > /certificates/ssl/.hostname_marker
    chmod 644 /certificates/ssl/.hostname_marker

    log 0 "[init] Self-signed SSL certificates generated"
else
    log 0 "[init] ✅ Self-signed SSL certificates already exist"
fi


echo ""
echo "[init] Waiting for all services to be ready... this can take a short while..."
echo ""

# Oneshot service exits successfully, dependencies can now start