#!/bin/bash
set -euo pipefail

# Airsonic Advanced + PostgreSQL diagnostics
# Focus: slow media scans, index efficiency, write pressure, and cache tuning signals.

PGHOST="${PGHOST:-localhost}"
PGPORT="${PGPORT:-5432}"
PGUSER="${PGUSER:-postgres}"
PGDATABASE="${PGDATABASE:-airsonic}"
OUT_FILE="${1:-airsonic_pg_diagnostics_$(date +%Y%m%d_%H%M%S).txt}"

PSQL=(psql -X -v ON_ERROR_STOP=1 -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE")

run_section() {
    local title="$1"
    local sql="$2"

    {
        echo
        echo "================================================================"
        echo "$title"
        echo "================================================================"
    } >> "$OUT_FILE"

    if ! "${PSQL[@]}" -c "$sql" >> "$OUT_FILE" 2>&1; then
        echo "Query failed for section: $title" >> "$OUT_FILE"
    fi
}

query_single_value() {
    local sql="$1"
    "${PSQL[@]}" -Atc "$sql" 2>/dev/null || true
}

bytes_to_human() {
    local bytes="$1"
    if [[ -z "$bytes" || ! "$bytes" =~ ^[0-9]+$ ]]; then
        echo "unknown"
        return
    fi

    local kib=$((1024))
    local mib=$((1024 * 1024))
    local gib=$((1024 * 1024 * 1024))

    if (( bytes >= gib )); then
        awk -v b="$bytes" -v g="$gib" 'BEGIN { printf "%.2f GiB", b/g }'
    elif (( bytes >= mib )); then
        awk -v b="$bytes" -v m="$mib" 'BEGIN { printf "%.2f MiB", b/m }'
    elif (( bytes >= kib )); then
        awk -v b="$bytes" -v k="$kib" 'BEGIN { printf "%.2f KiB", b/k }'
    else
        echo "${bytes} B"
    fi
}

{
    echo "Airsonic PostgreSQL Diagnostics"
    echo "Generated at: $(date -Iseconds)"
    echo "Target: host=$PGHOST port=$PGPORT db=$PGDATABASE user=$PGUSER"
} > "$OUT_FILE"

run_section "1) Server identity and uptime" "
SELECT version();
SELECT now() AS current_time, pg_postmaster_start_time() AS postmaster_start_time;
"

run_section "2) Key PostgreSQL tuning parameters" "
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name IN (
    'shared_buffers',
    'work_mem',
    'maintenance_work_mem',
    'effective_cache_size',
    'max_connections',
    'effective_io_concurrency',
    'random_page_cost',
    'seq_page_cost',
    'checkpoint_timeout',
    'checkpoint_completion_target',
    'autovacuum',
    'autovacuum_max_workers',
    'autovacuum_naptime'
)
ORDER BY name;
"

run_section "3) Database cache hit ratio and temp usage" "
SELECT
    datname,
    blks_read,
    blks_hit,
    ROUND(100.0 * blks_hit / NULLIF(blks_hit + blks_read, 0), 2) AS cache_hit_pct,
    temp_files,
    pg_size_pretty(temp_bytes) AS temp_bytes,
    deadlocks,
    stats_reset
FROM pg_stat_database
WHERE datname = current_database();
"

run_section "4) Most read tables and their index-vs-seq scan profile" "
SELECT
    relname,
    seq_scan,
    idx_scan,
    n_live_tup,
    n_dead_tup,
    ROUND((100.0 * idx_scan / NULLIF(idx_scan + seq_scan, 0))::numeric, 2) AS index_scan_pct,
    ROUND((100.0 * heap_blks_hit / NULLIF(heap_blks_hit + heap_blks_read, 0))::numeric, 2) AS table_cache_hit_pct
FROM pg_stat_user_tables t
JOIN pg_statio_user_tables io USING (relid)
ORDER BY (seq_scan + idx_scan) DESC NULLS LAST
LIMIT 25;
"

run_section "5) Potentially unused indexes (can slow writes during scans/indexing)" "
SELECT
    s.schemaname,
    s.relname AS table_name,
    s.indexrelname AS index_name,
    s.idx_scan,
    pg_size_pretty(pg_relation_size(s.indexrelid)) AS index_size,
    t.n_tup_ins + t.n_tup_upd + t.n_tup_del AS table_writes_since_reset
FROM pg_stat_user_indexes s
JOIN pg_index i ON i.indexrelid = s.indexrelid
JOIN pg_stat_user_tables t ON t.relid = s.relid
WHERE s.idx_scan = 0
  AND NOT i.indisunique
  AND NOT i.indisprimary
ORDER BY pg_relation_size(s.indexrelid) DESC
LIMIT 50;
"

run_section "6) Write-heavy tables (hot spot during media metadata indexing)" "
SELECT
    relname,
    n_tup_ins,
    n_tup_upd,
    n_tup_del,
    n_tup_hot_upd,
    n_dead_tup,
    vacuum_count,
    autovacuum_count,
    analyze_count,
    autoanalyze_count,
    COALESCE(last_autovacuum::text, 'never') AS last_autovacuum,
    COALESCE(last_autoanalyze::text, 'never') AS last_autoanalyze
FROM pg_stat_user_tables
ORDER BY (n_tup_ins + n_tup_upd + n_tup_del) DESC
LIMIT 25;
"

run_section "7) Largest relations (tables and indexes)" "
SELECT
    n.nspname AS schema_name,
    c.relname,
    c.relkind,
    pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
  AND c.relkind IN ('r', 'i')
ORDER BY pg_total_relation_size(c.oid) DESC
LIMIT 30;
"

if [[ "$(query_single_value "SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements';")" == "1" ]]; then
    run_section "8) Slowest normalized SQL from pg_stat_statements" "
    SELECT
        calls,
        ROUND(total_exec_time::numeric, 2) AS total_exec_ms,
        ROUND((total_exec_time / NULLIF(calls, 0))::numeric, 2) AS mean_exec_ms,
        ROUND(rows::numeric / NULLIF(calls, 0), 2) AS avg_rows,
        shared_blks_read,
        shared_blks_hit,
        temp_blks_read,
        temp_blks_written,
        LEFT(query, 300) AS query_sample
    FROM pg_stat_statements
    WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())
    ORDER BY mean_exec_ms DESC
    LIMIT 20;
    "
else
    {
        echo
        echo "================================================================"
        echo "8) Slowest normalized SQL from pg_stat_statements"
        echo "================================================================"
        echo "Extension pg_stat_statements is not installed."
        echo "Install with: CREATE EXTENSION pg_stat_statements;"
    } >> "$OUT_FILE"
fi

# Heuristic recommendation summary
CACHE_HIT_PCT="$(query_single_value "
SELECT ROUND(100.0 * blks_hit / NULLIF(blks_hit + blks_read, 0), 2)
FROM pg_stat_database
WHERE datname = current_database();")"

UNUSED_INDEX_COUNT="$(query_single_value "
SELECT COUNT(*)
FROM pg_stat_user_indexes s
JOIN pg_index i ON i.indexrelid = s.indexrelid
WHERE s.idx_scan = 0
  AND NOT i.indisunique
  AND NOT i.indisprimary;")"

SHARED_BUFFERS_BYTES="$(query_single_value "
SELECT pg_size_bytes(setting || unit)
FROM pg_settings
WHERE name = 'shared_buffers';")"

TOTAL_RAM_KB=""
if [[ -r /proc/meminfo ]]; then
    TOTAL_RAM_KB="$(awk '/MemTotal:/ {print $2}' /proc/meminfo)"
fi

{
    echo
    echo "================================================================"
    echo "9) Recommendations summary"
    echo "================================================================"

    if [[ -n "$CACHE_HIT_PCT" ]]; then
        echo "Cache hit ratio: ${CACHE_HIT_PCT}%"
        awk -v hit="$CACHE_HIT_PCT" 'BEGIN {
            if (hit < 98.0) {
                print "- Action: cache hit ratio is low for media workloads. Increase shared_buffers/effective_cache_size and review slow seq scans."
            } else if (hit < 99.0) {
                print "- Action: cache hit ratio is acceptable but can likely improve for large libraries."
            } else {
                print "- Action: cache hit ratio looks healthy."
            }
        }'
    else
        echo "Cache hit ratio: unavailable"
    fi

    if [[ -n "$UNUSED_INDEX_COUNT" ]]; then
        echo "Potentially unused non-unique indexes: $UNUSED_INDEX_COUNT"
        if [[ "$UNUSED_INDEX_COUNT" =~ ^[0-9]+$ ]] && (( UNUSED_INDEX_COUNT > 0 )); then
            echo "- Action: validate and drop truly unused indexes to reduce write overhead during scans/indexing."
        else
            echo "- Action: no obvious unused non-unique indexes from current stats."
        fi
    fi

    if [[ -n "$SHARED_BUFFERS_BYTES" ]]; then
        echo "shared_buffers: $(bytes_to_human "$SHARED_BUFFERS_BYTES")"
        if [[ -n "$TOTAL_RAM_KB" && "$TOTAL_RAM_KB" =~ ^[0-9]+$ ]]; then
            TOTAL_RAM_BYTES=$((TOTAL_RAM_KB * 1024))
            SHARED_BUFFER_PCT=$(awk -v s="$SHARED_BUFFERS_BYTES" -v t="$TOTAL_RAM_BYTES" 'BEGIN { printf "%.2f", (s/t)*100 }')
            echo "Estimated shared_buffers/system RAM: ${SHARED_BUFFER_PCT}%"
            awk -v p="$SHARED_BUFFER_PCT" 'BEGIN {
                if (p < 10.0) {
                    print "- Action: shared_buffers appears low; typical starting point is ~15-25% of system RAM."
                } else if (p > 40.0) {
                    print "- Action: shared_buffers appears high; ensure OS cache remains adequate."
                } else {
                    print "- Action: shared_buffers proportion looks reasonable."
                }
            }'
        else
            echo "- Action: compare shared_buffers to system RAM manually (target often 15-25% for dedicated DB hosts)."
        fi
    fi

    echo "- Action: for media scans, verify high-read tables use selective indexes and that autovacuum keeps dead tuples controlled."
    echo "- Action: rerun this script before and during a scan to compare write pressure and cache behavior over time."
} >> "$OUT_FILE"

echo "Diagnostics complete. Report written to: $OUT_FILE"
