#!/usr/bin/env bash
# PostToolUse hook: registra cada invocacion del agente en
# projects/fn_monitoring/apps/call_monitor/operations.db (issue 0085b).
#
# Identifica tool, extrae function_id cuando es posible, clasifica el patron
# (mcp_*, fn_cli_run, heredoc_py, sqlite_direct, edit_registry, ...) y
# detecta antipatrones para registrar violations.
#
# NUNCA bloquea la herramienta. Falla silenciosamente si la BD no esta lista.
# Solo guarda args_hash, jamas valores concretos.

set -euo pipefail

# ---- Resolve registry root (walks up from cwd looking for registry.db) ----
resolve_root() {
    local d="${PWD}"
    while [ "$d" != "/" ]; do
        if [ -f "$d/registry.db" ]; then
            printf '%s' "$d"
            return 0
        fi
        d=$(dirname "$d")
    done
    return 1
}

ROOT=$(resolve_root) || exit 0
DB="$ROOT/projects/fn_monitoring/apps/call_monitor/operations.db"

# Si la BD aun no existe, el hook no hace nada (esperando init).
[ -f "$DB" ] || exit 0

# ---- Read stdin JSON ----
INPUT=$(cat)
if [ -z "$INPUT" ]; then exit 0; fi

# Required jq presence
command -v jq >/dev/null 2>&1 || exit 0
command -v sqlite3 >/dev/null 2>&1 || exit 0

TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // ""')
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // ""')
TS=$(date -u +%s)

# Tool response success/error
SUCCESS=1
ERROR_CLASS=""
ERROR_SNIPPET=""
RESP_IS_ERROR=$(printf '%s' "$INPUT" | jq -r 'if (.tool_response | type) == "object" then (.tool_response.is_error // false) else false end')
if [ "$RESP_IS_ERROR" = "true" ]; then
    SUCCESS=0
    ERROR_SNIPPET=$(printf '%s' "$INPUT" | jq -r 'if (.tool_response | type) == "object" then (.tool_response.error // .tool_response.content // "") else "" end' | head -c 240 | tr '\n' ' ')
fi

# args_hash: sha256 truncado del tool_input (sin valores)
ARGS_HASH=$(printf '%s' "$INPUT" | jq -c '.tool_input // {}' | sha256sum | cut -c1-16)

# Helpers SQL
sql_escape() { printf '%s' "$1" | sed "s/'/''/g"; }

insert_call() {
    local fn_id="$1" tool_used="$2" duration_ms="${3:-0}" snippet="${4:-}"
    local fn_esc tu_esc ec_esc es_esc sid_esc ah_esc snip_esc
    # Politica issue 0087: command_snippet solo se rellena cuando function_id
    # esta vacio. Si la call golpea una funcion del registry, su ID y
    # tool_used bastan; no duplicamos el comando.
    if [ -n "$fn_id" ]; then snippet=""; fi
    # Redact common secrets antes de persistir
    snippet=$(printf '%s' "$snippet" \
        | sed -E 's/(password|token|secret|api[_-]?key|bearer)([[:space:]]*[=:][[:space:]]*)[^[:space:]]+/\1\2<REDACTED>/Ig' \
        | head -c 200)
    fn_esc=$(sql_escape "$fn_id")
    tu_esc=$(sql_escape "$tool_used")
    ec_esc=$(sql_escape "$ERROR_CLASS")
    es_esc=$(sql_escape "$ERROR_SNIPPET")
    sid_esc=$(sql_escape "$SESSION_ID")
    ah_esc=$(sql_escape "$ARGS_HASH")
    snip_esc=$(sql_escape "$snippet")
    sqlite3 "$DB" "INSERT INTO calls (session_id, function_id, tool_used, args_hash, duration_ms, success, error_class, error_snippet, command_snippet, ts) VALUES ('$sid_esc','$fn_esc','$tu_esc','$ah_esc',$duration_ms,$SUCCESS,'$ec_esc','$es_esc','$snip_esc',$TS);" 2>/dev/null || true
}

insert_code_write() {
    local fn_id="$1" file_path="$2" added="${3:-0}" removed="${4:-0}"
    local fn_esc fp_esc sid_esc
    fn_esc=$(sql_escape "$fn_id")
    fp_esc=$(sql_escape "$file_path")
    sid_esc=$(sql_escape "$SESSION_ID")
    sqlite3 "$DB" "INSERT INTO code_writes (session_id, function_id, file_path, lines_added, lines_removed, ts) VALUES ('$sid_esc','$fn_esc','$fp_esc',$added,$removed,$TS);" 2>/dev/null || true
}

# Snapshot a function version row when an edit lands on a registry file.
# Uses sha256 of file bytes as content_hash (separate namespace from index source).
insert_edit_version() {
    local fn_id="$1" abs_path="$2"
    [ -f "$abs_path" ] || return 0
    command -v sha256sum >/dev/null 2>&1 || return 0
    local hash
    hash=$(sha256sum "$abs_path" 2>/dev/null | awk '{print $1}')
    [ -z "$hash" ] && return 0
    local fn_esc h_esc
    fn_esc=$(sql_escape "$fn_id")
    h_esc=$(sql_escape "$hash")
    sqlite3 "$DB" "INSERT OR IGNORE INTO function_versions (function_id, content_hash, version, snapped_at, source, lines_added, lines_removed) VALUES ('$fn_esc','$h_esc','',$TS,'edit_hook',0,0);" 2>/dev/null || true
}

insert_violation() {
    local rule_id="$1" fn_id="$2" snippet="$3" severity="${4:-warning}"
    local r_esc fn_esc sn_esc sev_esc sid_esc
    r_esc=$(sql_escape "$rule_id")
    fn_esc=$(sql_escape "$fn_id")
    sn_esc=$(sql_escape "$(printf '%s' "$snippet" | head -c 240 | tr '\n' ' ')")
    sev_esc=$(sql_escape "$severity")
    sid_esc=$(sql_escape "$SESSION_ID")
    sqlite3 "$DB" "INSERT INTO violations (session_id, rule_id, function_id, command_snippet, severity, ts) VALUES ('$sid_esc','$r_esc','$fn_esc','$sn_esc','$sev_esc',$TS);" 2>/dev/null || true
}

# ---- Derive function_id from registry file path ----
# Matches paths under functions/<domain>/<name>.<ext>, python/functions/<domain>/<name>.py,
# bash/functions/<domain>/<name>.sh, frontend/functions/<domain>/<name>.ts(x)
derive_fn_id_from_path() {
    local p="$1"
    [ -z "$p" ] && return 1
    case "$p" in
        functions/*/*.go|*/functions/*/*.go)
            local dom name
            dom=$(printf '%s' "$p" | sed -E 's|.*functions/([^/]+)/.*|\1|')
            name=$(printf '%s' "$p" | sed -E 's|.*functions/[^/]+/([^/.]+)\..*|\1|')
            [ -n "$dom" ] && [ -n "$name" ] && printf '%s_go_%s' "$name" "$dom" && return 0 ;;
        python/functions/*/*.py)
            local dom name
            dom=$(printf '%s' "$p" | sed -E 's|python/functions/([^/]+)/.*|\1|')
            name=$(printf '%s' "$p" | sed -E 's|python/functions/[^/]+/([^/.]+)\..*|\1|')
            [ -n "$dom" ] && [ -n "$name" ] && printf '%s_py_%s' "$name" "$dom" && return 0 ;;
        bash/functions/*/*.sh)
            local dom name
            dom=$(printf '%s' "$p" | sed -E 's|bash/functions/([^/]+)/.*|\1|')
            name=$(printf '%s' "$p" | sed -E 's|bash/functions/[^/]+/([^/.]+)\..*|\1|')
            [ -n "$dom" ] && [ -n "$name" ] && printf '%s_bash_%s' "$name" "$dom" && return 0 ;;
        frontend/functions/*/*.ts|frontend/functions/*/*.tsx)
            local dom name
            dom=$(printf '%s' "$p" | sed -E 's|frontend/functions/([^/]+)/.*|\1|')
            name=$(printf '%s' "$p" | sed -E 's|frontend/functions/[^/]+/([^/.]+)\..*|\1|')
            [ -n "$dom" ] && [ -n "$name" ] && printf '%s_ts_%s' "$name" "$dom" && return 0 ;;
    esac
    return 1
}

# ---- Dispatch by tool ----
case "$TOOL_NAME" in
    mcp__registry__fn_search)
        insert_call "" "mcp_fn_search"
        ;;
    mcp__registry__fn_show)
        ID=$(printf '%s' "$INPUT" | jq -r '.tool_input.id // ""')
        insert_call "$ID" "mcp_fn_show"
        ;;
    mcp__registry__fn_code)
        ID=$(printf '%s' "$INPUT" | jq -r '.tool_input.id // ""')
        insert_call "$ID" "mcp_fn_code"
        ;;
    mcp__registry__fn_uses)
        ID=$(printf '%s' "$INPUT" | jq -r '.tool_input.id // ""')
        insert_call "$ID" "mcp_fn_uses"
        ;;
    mcp__registry__fn_run)
        ID=$(printf '%s' "$INPUT" | jq -r '.tool_input.id // ""')
        insert_call "$ID" "mcp_fn_run"
        ;;
    mcp__registry__fn_list_domains)
        insert_call "" "mcp_fn_list_domains"
        ;;
    mcp__registry__fn_proposal)
        insert_call "" "mcp_fn_proposal"
        ;;
    mcp__registry__fn_doctor)
        insert_call "" "mcp_fn_doctor"
        ;;
    mcp__registry__fn_create_function)
        insert_call "" "mcp_fn_create_function"
        ;;

    Edit|Write|MultiEdit)
        FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // ""')
        ABS_PATH="$FILE_PATH"
        # Make path relative to root if absolute and inside root
        case "$FILE_PATH" in
            "$ROOT"/*) FILE_PATH="${FILE_PATH#$ROOT/}" ;;
            /*) ABS_PATH="$FILE_PATH" ;;
            *) ABS_PATH="$ROOT/$FILE_PATH" ;;
        esac
        FN_ID=$(derive_fn_id_from_path "$FILE_PATH" || true)
        if [ -n "$FN_ID" ]; then
            insert_code_write "$FN_ID" "$FILE_PATH" 0 0
            insert_call "$FN_ID" "edit_registry"
            insert_edit_version "$FN_ID" "$ABS_PATH"
        fi
        ;;

    Bash)
        CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')
        CMD_HEAD=$(printf '%s' "$CMD" | head -c 200 | tr '\n' ' ')

        # Classify
        TOOL_USED="bash_other"
        FN_ID=""

        if printf '%s' "$CMD" | grep -qE '(^|[[:space:]])\./fn[[:space:]]+run[[:space:]]+'; then
            TOOL_USED="fn_cli_run"
            FN_ID=$(printf '%s' "$CMD" | sed -nE 's/.*\.\/fn[[:space:]]+run[[:space:]]+([A-Za-z0-9_]+).*/\1/p' | head -n1)
        elif printf '%s' "$CMD" | grep -qE 'python/\.venv/bin/python3[[:space:]]+-[[:space:]]+<<'; then
            TOOL_USED="heredoc_py"
        elif printf '%s' "$CMD" | grep -qE 'sqlite3[[:space:]][^|]*\bregistry\.db\b'; then
            TOOL_USED="sqlite_direct"
        fi

        insert_call "$FN_ID" "$TOOL_USED" 0 "$CMD_HEAD"

        # ---- Violation rules ----
        # 1. sqlite3 directo SELECT sobre registry.db (excepto schema/pragma/count/join)
        if [ "$TOOL_USED" = "sqlite_direct" ]; then
            if ! printf '%s' "$CMD" | grep -qiE '(\.schema|\.tables|PRAGMA[[:space:]]+(table_info|index_list)|COUNT\(|GROUP[[:space:]]+BY|JOIN[[:space:]])'; then
                insert_violation "sqlite3_registry_select" "" "$CMD_HEAD" "warning"
            fi
        fi

        # 2. python -c "import X; dir(X)"
        if printf '%s' "$CMD" | grep -qE 'python[3]?[[:space:]]+-c[[:space:]]+["'\''].*import.*(dir|help)\('; then
            insert_violation "python_dir_inspect" "" "$CMD_HEAD" "info"
        fi

        # 3. from <pkg> import *  (en heredoc python)
        if [ "$TOOL_USED" = "heredoc_py" ]; then
            if printf '%s' "$CMD" | grep -qE 'from[[:space:]]+[A-Za-z0-9_.]+[[:space:]]+import[[:space:]]+\*'; then
                insert_violation "import_star_in_heredoc" "" "$CMD_HEAD" "warning"
            fi
            if printf '%s' "$CMD" | grep -qE 'client\._http\.request\('; then
                insert_violation "client_http_request_direct" "" "$CMD_HEAD" "warning"
            fi
        fi
        ;;
esac

exit 0
