#!/bin/bash
set -u

PACKAGE_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
APP_ROOT="${TRIM_APPDEST:-$PACKAGE_ROOT/app}"
DATA_ROOT="${TRIM_PKGVAR:-$PACKAGE_ROOT/var}"
VOLUME_ROOT="${TRIM_APPDEST_VOL:-$DATA_ROOT/volume}"
RECORD_ROOT="${LIVE_RECORDER_RECORD_ROOT:-$VOLUME_ROOT/@appshare/liverecorder/records}"
RUNTIME_ROOT="$APP_ROOT/runtime"
SERVER="$APP_ROOT/server/LiveRecorder.WebApi"
PG_BIN="$RUNTIME_ROOT/usr/lib/postgresql/15/bin"
PG_SHARE="$RUNTIME_ROOT/usr/share/postgresql/15"
PG_LIB="$RUNTIME_ROOT/usr/lib/postgresql/15/lib"
NODE_BIN="$RUNTIME_ROOT/bin/node"
CURL_BIN="$RUNTIME_ROOT/bin/curl"
CA_BUNDLE="$RUNTIME_ROOT/etc/ssl/certs/ca-certificates.crt"
PG_DATA="$DATA_ROOT/postgres"
RUN_ROOT="$DATA_ROOT/run"
LOG_ROOT="$DATA_ROOT/log"
APP_PID_FILE="$RUN_ROOT/liverecorder.pid"
APP_LOG="$LOG_ROOT/liverecorder.log"
PG_LOG="$LOG_ROOT/postgresql.log"
ADMIN_PASSWORD_FILE="$DATA_ROOT/admin-password.seed"
POSTGRES_ENROLLMENT_TOKEN_FILE="$DATA_ROOT/postgres-enrollment-token.seed"
POSTGRES_CREDENTIALS_FILE="$DATA_ROOT/postgres-client.conf"
POSTGRES_MIGRATION_ROOT="$DATA_ROOT/postgres-migration"
POSTGRES_MIGRATION_MARKER="$POSTGRES_MIGRATION_ROOT/shared-database.active"
PG_PORT="${LIVE_RECORDER_POSTGRES_PORT:-54329}"
SERVICE_PORT="${TRIM_SERVICE_PORT:-18080}"
POSTGRES_SERVICE_API="${POSTGRES_SERVICE_API:-http://127.0.0.1:15433}"
SYSTEM_PATH="${PATH:-/usr/local/bin:/usr/bin:/bin}"
RUNTIME_PATH="$RUNTIME_ROOT/bin:$SYSTEM_PATH"
RUNTIME_LIBRARY_PATH="$RUNTIME_ROOT/lib:$PG_LIB"
DATABASE_CONNECTION_STRING=""
SHARED_DB_HOST=""
SHARED_DB_PORT=""
SHARED_DB_NAME=""
SHARED_DB_USER=""
SHARED_DB_PASSWORD=""

log_message() {
    mkdir -p "$LOG_ROOT"
    printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$APP_LOG"
}

app_pid() {
    if [ -f "$APP_PID_FILE" ]; then
        pid=$(sed -n '1p' "$APP_PID_FILE" | tr -d '[:space:]')
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            printf '%s' "$pid"
            return 0
        fi
    fi
    return 1
}

run_pg() {
    env LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH" PATH="$PG_BIN:$RUNTIME_PATH" "$@"
}

run_native() {
    env LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH" PATH="$RUNTIME_PATH" \
        SSL_CERT_FILE="$CA_BUNDLE" CURL_CA_BUNDLE="$CA_BUNDLE" "$@"
}

read_credential_field() {
    key=$1
    sed -n "s/^${key}=//p" "$POSTGRES_CREDENTIALS_FILE" | sed -n '1p'
}

configure_shared_credentials() {
    [ -s "$POSTGRES_CREDENTIALS_FILE" ] || return 1
    SHARED_DB_HOST=$(read_credential_field host)
    SHARED_DB_PORT=$(read_credential_field port)
    SHARED_DB_NAME=$(read_credential_field database)
    SHARED_DB_USER=$(read_credential_field username)
    SHARED_DB_PASSWORD=$(read_credential_field password)
    [ "$SHARED_DB_HOST" = "127.0.0.1" ] || return 1
    case "$SHARED_DB_PORT" in ''|*[!0-9]*) return 1 ;; esac
    case "$SHARED_DB_NAME$SHARED_DB_USER" in *[!a-z0-9_]*) return 1 ;; esac
    [ -n "$SHARED_DB_PASSWORD" ] || return 1
    DATABASE_CONNECTION_STRING="Host=$SHARED_DB_HOST;Port=$SHARED_DB_PORT;Database=$SHARED_DB_NAME;Username=$SHARED_DB_USER;Password=$SHARED_DB_PASSWORD;SSL Mode=Disable;Timeout=15;Command Timeout=120;Keepalive=30"
}

enroll_shared_database() {
    [ -s "$POSTGRES_ENROLLMENT_TOKEN_FILE" ] || {
        log_message "缺少 PostgreSQL 共享服务接入令牌。请在升级或安装向导中重新填写。"
        return 1
    }
    token=$(sed -n '1p' "$POSTGRES_ENROLLMENT_TOKEN_FILE")
    [ -n "$token" ] || return 1
    response_file="$DATA_ROOT/postgres-enrollment-response.tmp"
    rm -f "$response_file"
    attempt=0
    while [ "$attempt" -lt 60 ]; do
        if run_native "$CURL_BIN" \
            --fail --silent --show-error \
            --connect-timeout 3 --max-time 10 \
            -H "Authorization: Bearer $token" \
            -H 'Content-Type: application/json' \
            --data '{"appId":"liverecorder","displayName":"Live Recorder","requestedExtensions":[]}' \
            "$POSTGRES_SERVICE_API/internal/v1/enroll" >"$response_file" 2>>"$APP_LOG"; then
            break
        fi
        attempt=$((attempt + 1))
        sleep 2
    done
    unset token
    [ -s "$response_file" ] || {
        log_message "无法从 PostgreSQL 共享服务取得数据库凭据。"
        rm -f "$response_file"
        return 1
    }

    host=$(sed -n 's/.*"host":"\([^"]*\)".*/\1/p' "$response_file")
    port=$(sed -n 's/.*"port":\([0-9][0-9]*\).*/\1/p' "$response_file")
    database=$(sed -n 's/.*"database":"\([^"]*\)".*/\1/p' "$response_file")
    username=$(sed -n 's/.*"username":"\([^"]*\)".*/\1/p' "$response_file")
    password=$(sed -n 's/.*"password":"\([^"]*\)".*/\1/p' "$response_file")
    [ "$host" = "127.0.0.1" ] && [ -n "$port" ] && [ -n "$database" ] && [ -n "$username" ] && [ -n "$password" ] || {
        log_message "PostgreSQL 共享服务返回了无效凭据。"
        rm -f "$response_file"
        return 1
    }
    umask 077
    {
        printf 'host=%s\n' "$host"
        printf 'port=%s\n' "$port"
        printf 'database=%s\n' "$database"
        printf 'username=%s\n' "$username"
        printf 'password=%s\n' "$password"
    } >"$POSTGRES_CREDENTIALS_FILE.tmp"
    mv "$POSTGRES_CREDENTIALS_FILE.tmp" "$POSTGRES_CREDENTIALS_FILE"
    chmod 0600 "$POSTGRES_CREDENTIALS_FILE"
    rm -f "$POSTGRES_ENROLLMENT_TOKEN_FILE" "$response_file"
    configure_shared_credentials
}

ensure_shared_credentials() {
    if configure_shared_credentials; then
        return 0
    fi
    rm -f "$POSTGRES_CREDENTIALS_FILE"
    enroll_shared_database
}

run_shared_pg() {
    env LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH" PATH="$PG_BIN:$RUNTIME_PATH" \
        PGPASSWORD="$SHARED_DB_PASSWORD" "$@"
}

collect_database_counts() {
    mode=$1
    destination=$2
    : >"$destination"
    for table_name in AppSettings CleanupOperations LiveRooms RecordResults RecordSessions RecordTasks RecordUploadJobs SystemLogEntries UserAccounts UserSessions; do
        if [ "$mode" = "private" ]; then
            count=$(run_pg "$PG_BIN/psql" -h "$RUN_ROOT" -p "$PG_PORT" -U liverecorder -d live_recorder -Atqc \
                "SELECT count(*) FROM \"$table_name\"" 2>/dev/null) || return 1
        else
            count=$(run_shared_pg "$PG_BIN/psql" -h "$SHARED_DB_HOST" -p "$SHARED_DB_PORT" -U "$SHARED_DB_USER" -d "$SHARED_DB_NAME" -Atqc \
                "SELECT count(*) FROM \"$table_name\"" 2>/dev/null) || return 1
        fi
        printf '%s=%s\n' "$table_name" "$count" >>"$destination"
    done
}

migrate_private_postgres() {
    mkdir -p "$POSTGRES_MIGRATION_ROOT"
    chmod 0700 "$POSTGRES_MIGRATION_ROOT"
    dump_file="$POSTGRES_MIGRATION_ROOT/private-postgres-15.dump"
    source_counts="$POSTGRES_MIGRATION_ROOT/source-counts.txt"
    target_counts="$POSTGRES_MIGRATION_ROOT/target-counts.txt"
    start_postgres || return 1

    log_message "开始导出原内置 PostgreSQL 数据库。"
    run_pg "$PG_BIN/pg_dump" \
        -h "$RUN_ROOT" -p "$PG_PORT" -U liverecorder -d live_recorder \
        --format=custom --no-owner --no-acl --file "$dump_file.tmp" >>"$PG_LOG" 2>&1 || return 1
    mv "$dump_file.tmp" "$dump_file"
    sha256sum "$dump_file" >"$dump_file.sha256"
    collect_database_counts private "$source_counts" || return 1

    log_message "开始恢复数据到 PostgreSQL 共享服务。"
    run_shared_pg "$PG_BIN/psql" \
        -h "$SHARED_DB_HOST" -p "$SHARED_DB_PORT" -U "$SHARED_DB_USER" -d "$SHARED_DB_NAME" \
        -v ON_ERROR_STOP=1 -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public AUTHORIZATION \"$SHARED_DB_USER\"" >>"$PG_LOG" 2>&1 || return 1
    run_shared_pg "$PG_BIN/pg_restore" \
        -h "$SHARED_DB_HOST" -p "$SHARED_DB_PORT" -U "$SHARED_DB_USER" -d "$SHARED_DB_NAME" \
        --no-owner --no-acl --exit-on-error "$dump_file" >>"$PG_LOG" 2>&1 || return 1
    collect_database_counts shared "$target_counts" || return 1
    if ! cmp -s "$source_counts" "$target_counts"; then
        log_message "共享数据库行数校验失败，继续使用原数据库。"
        return 1
    fi

    umask 077
    {
        printf 'migrated_at=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
        printf 'database=%s\n' "$SHARED_DB_NAME"
        printf 'dump=%s\n' "$dump_file"
    } >"$POSTGRES_MIGRATION_MARKER"
    stop_postgres || return 1
    log_message "内置 PostgreSQL 已迁移到共享服务，旧数据和迁移转储已保留。"
}

select_database_connection() {
    if ! ensure_shared_credentials; then
        if [ -f "$POSTGRES_MIGRATION_MARKER" ]; then
            log_message "已完成共享数据库迁移，但当前凭据不可用；为避免使用过期旧库，应用不会启动。"
            return 1
        fi
        if [ -f "$PG_DATA/PG_VERSION" ]; then
            log_message "共享服务暂不可用，本次启动继续使用原内置 PostgreSQL。"
            start_postgres || return 1
            DATABASE_CONNECTION_STRING="Host=$RUN_ROOT;Port=$PG_PORT;Database=live_recorder;Username=liverecorder;Timeout=15;Command Timeout=120;Keepalive=30"
            return 0
        fi
        return 1
    fi

    if [ -f "$PG_DATA/PG_VERSION" ] && [ ! -f "$POSTGRES_MIGRATION_MARKER" ]; then
        if ! migrate_private_postgres; then
            log_message "共享数据库迁移失败，本次启动继续使用原内置 PostgreSQL；下次启动会重试迁移。"
            start_postgres || return 1
            DATABASE_CONNECTION_STRING="Host=$RUN_ROOT;Port=$PG_PORT;Database=live_recorder;Username=liverecorder;Timeout=15;Command Timeout=120;Keepalive=30"
            return 0
        fi
    elif [ ! -f "$PG_DATA/PG_VERSION" ] && [ ! -f "$POSTGRES_MIGRATION_MARKER" ]; then
        mkdir -p "$POSTGRES_MIGRATION_ROOT"
        umask 077
        printf 'fresh_install_at=%s\ndatabase=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$SHARED_DB_NAME" >"$POSTGRES_MIGRATION_MARKER"
    fi
    return 0
}

system_media_tools_available() {
    missing_tools=""
    for tool_name in ffmpeg ffprobe; do
        if ! PATH="$SYSTEM_PATH" command -v "$tool_name" >/dev/null 2>&1; then
            missing_tools="$missing_tools $tool_name"
        fi
    done
    if [ -n "$missing_tools" ]; then
        log_message "缺少 fnOS 系统媒体工具：${missing_tools# }。请先在系统环境中安装 FFmpeg（必须同时提供 ffmpeg 与 ffprobe）。"
        return 1
    fi
    return 0
}

postgres_running() {
    [ -x "$PG_BIN/pg_ctl" ] || return 1
    [ -f "$PG_DATA/PG_VERSION" ] || return 1
    run_pg "$PG_BIN/pg_ctl" -D "$PG_DATA" status >/dev/null 2>&1
}

initialize_postgres() {
    if [ -f "$PG_DATA/PG_VERSION" ]; then
        return 0
    fi

    mkdir -p "$PG_DATA" "$RUN_ROOT"
    chmod 0700 "$PG_DATA" "$RUN_ROOT"
    if ! run_pg "$PG_BIN/initdb" \
        -D "$PG_DATA" \
        -L "$PG_SHARE" \
        --username=liverecorder \
        --auth-local=trust \
        --auth-host=reject \
        --encoding=UTF8 \
        --no-locale >>"$PG_LOG" 2>&1; then
        log_message "PostgreSQL 初始化失败。"
        return 1
    fi

    {
        printf "listen_addresses = ''\n"
        printf "port = %s\n" "$PG_PORT"
        printf "unix_socket_directories = '%s'\n" "$RUN_ROOT"
        printf "max_connections = 40\n"
        printf "shared_buffers = '64MB'\n"
        printf "timezone = 'UTC'\n"
        printf "log_timezone = 'UTC'\n"
    } >>"$PG_DATA/postgresql.conf"
    return 0
}

start_postgres() {
    if postgres_running; then
        return 0
    fi
    initialize_postgres || return 1
    if ! run_pg "$PG_BIN/pg_ctl" -D "$PG_DATA" -l "$PG_LOG" -w start; then
        log_message "PostgreSQL 启动失败。"
        return 1
    fi

    database_exists=$(run_pg "$PG_BIN/psql" \
        -h "$RUN_ROOT" -p "$PG_PORT" -U liverecorder -d postgres -Atqc \
        "SELECT 1 FROM pg_database WHERE datname = 'live_recorder'" 2>/dev/null || true)
    if [ "$database_exists" != "1" ]; then
        if ! run_pg "$PG_BIN/createdb" \
            -h "$RUN_ROOT" -p "$PG_PORT" -U liverecorder live_recorder >>"$PG_LOG" 2>&1; then
            log_message "Live Recorder 数据库创建失败。"
            return 1
        fi
    fi
    return 0
}

stop_postgres() {
    if postgres_running; then
        # A first-run migration can dirty enough pages that a NAS needs more
        # than pg_ctl's 60-second default to finish the shutdown checkpoint.
        # Do not let restart race a database that is still shutting down.
        if ! run_pg "$PG_BIN/pg_ctl" -D "$PG_DATA" -m fast -t 180 -w stop >>"$PG_LOG" 2>&1; then
            log_message "PostgreSQL 未能在 180 秒内安全停止，已取消后续重启。"
            return 1
        fi
    fi
}

launch_app_process() {
    (
        export ASPNETCORE_ENVIRONMENT=Production
        export ASPNETCORE_URLS="http://0.0.0.0:$SERVICE_PORT"
        export ConnectionStrings__DefaultConnection="$DATABASE_CONNECTION_STRING"
        export LIVE_RECORDER_DEFAULT_OUTPUT_ROOT="$RECORD_ROOT"
        if [ -f "$ADMIN_PASSWORD_FILE" ]; then
            LIVE_RECORDER_DEFAULT_ADMIN_PASSWORD=$(sed -n '1p' "$ADMIN_PASSWORD_FILE")
            export LIVE_RECORDER_DEFAULT_ADMIN_PASSWORD
        fi
        export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
        export DOTNET_BUNDLE_EXTRACT_BASE_DIR="$DATA_ROOT/dotnet-bundle"
        export XDG_CACHE_HOME="$DATA_ROOT/cache"
        export TMPDIR="$DATA_ROOT/tmp"
        export LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH"
        export PATH="$RUNTIME_PATH"
        export SSL_CERT_FILE="$CA_BUNDLE"
        export CURL_CA_BUNDLE="$CA_BUNDLE"
        mkdir -p "$XDG_CACHE_HOME" "$TMPDIR"
        cd "$APP_ROOT/server" || exit 1
        exec "$SERVER"
    ) >>"$APP_LOG" 2>&1 &
    APP_PROCESS_PID=$!
    printf '%s\n' "$APP_PROCESS_PID" >"$APP_PID_FILE"
}

start_app() {
    mkdir -p "$DATA_ROOT" "$RUN_ROOT" "$LOG_ROOT" "$RECORD_ROOT" "$DATA_ROOT/dotnet-bundle"
    chmod 0700 "$DATA_ROOT" "$RUN_ROOT" "$DATA_ROOT/dotnet-bundle" 2>/dev/null || true

    if [ ! -x "$SERVER" ]; then
        log_message "应用程序不存在或不可执行：$SERVER"
        return 1
    fi
    if [ ! -x "$PG_BIN/postgres" ] || [ ! -x "$NODE_BIN" ] || [ ! -x "$CURL_BIN" ] || [ ! -s "$CA_BUNDLE" ]; then
        log_message "FPK 原生运行环境不完整。"
        return 1
    fi
    system_media_tools_available || return 1
    if pid=$(app_pid); then
        log_message "应用已运行，PID $pid。"
        return 0
    fi

    rm -f "$APP_PID_FILE"
    select_database_connection || return 1
    launch_attempt=1
    launch_app_process
    pid=$APP_PROCESS_PID

    attempt=0
    while [ "$attempt" -lt 90 ]; do
        if run_native "$CURL_BIN" -fsS "http://127.0.0.1:$SERVICE_PORT/health/ready" >/dev/null 2>&1; then
            rm -f "$ADMIN_PASSWORD_FILE"
            log_message "应用启动成功，PID $pid，端口 $SERVICE_PORT。"
            return 0
        fi
        if ! kill -0 "$pid" 2>/dev/null; then
            if [ "$launch_attempt" -ge 3 ]; then
                break
            fi
            log_message "应用启动进程提前退出，3 秒后重试（$launch_attempt/3）。"
            sleep 3
            launch_attempt=$((launch_attempt + 1))
            launch_app_process
            pid=$APP_PROCESS_PID
        fi
        attempt=$((attempt + 1))
        sleep 1
    done

    log_message "应用未能在 90 秒内就绪。"
    stop_app
    return 1
}

stop_app() {
    if pid=$(app_pid); then
        kill "$pid" 2>/dev/null || true
        attempt=0
        while kill -0 "$pid" 2>/dev/null && [ "$attempt" -lt 30 ]; do
            sleep 1
            attempt=$((attempt + 1))
        done
        if kill -0 "$pid" 2>/dev/null; then
            kill -9 "$pid" 2>/dev/null || true
        fi
    fi
    rm -f "$APP_PID_FILE"
    stop_postgres
}

case "${1:-status}" in
    start) start_app ;;
    stop) stop_app ;;
    restart) stop_app && start_app ;;
    status)
        if app_pid >/dev/null; then
            exit 0
        fi
        exit 3
        ;;
    *) printf 'usage: %s {start|stop|restart|status}\n' "$0" >&2; exit 2 ;;
esac
