#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C

usage() {
    printf 'usage: %s SCENARIO {blocks|inodes|du|logs|approved|candidates|stat|remove|exists} [PATH]\n' "${0##*/}" >&2
    exit 64
}

die() {
    printf 'diskfixture: %s\n' "$*" >&2
    exit 65
}

(( $# >= 2 )) || usage
scenario=$1
command=$2
shift 2

for required in filesystem.tsv inventory.tsv du.tsv system.log approved-caches.txt cleanup-candidates.txt; do
    [[ -f "$scenario/$required" ]] || die "missing fixture file: $required"
done
[[ -d "$scenario/fs" ]] || die 'missing fixture filesystem'

metric() {
    local wanted=$1 key value extra found=
    while IFS=$'\t' read -r key value extra || [[ -n "${key:-}${value:-}${extra:-}" ]]; do
        [[ -z "${key:-}" || "${key:0:1}" == '#' ]] && continue
        [[ -z "${extra:-}" && -n "$value" ]] || die 'malformed filesystem.tsv'
        case "$key" in
            mount) [[ "$value" == /* ]] || die 'malformed filesystem.tsv' ;;
            blocks_total|blocks_used|inodes_total|inodes_used)
                [[ "$value" =~ ^[0-9]+$ ]] || die 'malformed filesystem.tsv'
                ;;
            *) die "unknown filesystem metric: $key" ;;
        esac
        if [[ "$key" == "$wanted" ]]; then
            [[ -z "$found" ]] || die "duplicate metric: $wanted"
            found=$value
        fi
    done < "$scenario/filesystem.tsv"
    [[ -n "$found" ]] || die "missing metric: $wanted"
    printf '%s\n' "$found"
}

valid_virtual_path() {
    local path=$1
    [[ "$path" == /* && "$path" != *$'\t'* && "$path" != *$'\n'* ]] || return 1
    case "/${path#/}/" in
        *'/../'*|*'/./'*|*'//'*) return 1 ;;
    esac
}

inventory_record() {
    local wanted=$1 bytes blocks inodes path extra
    valid_virtual_path "$wanted" || die "invalid virtual path: $wanted"
    while IFS=$'\t' read -r bytes blocks inodes path extra ||
          [[ -n "${bytes:-}${blocks:-}${inodes:-}${path:-}${extra:-}" ]]; do
        [[ -z "${bytes:-}" || "${bytes:0:1}" == '#' ]] && continue
        [[ -z "${extra:-}" && "$bytes" =~ ^[0-9]+$ && "$blocks" =~ ^[0-9]+$ && "$inodes" =~ ^[0-9]+$ ]] ||
            die 'malformed inventory.tsv'
        valid_virtual_path "$path" || die "invalid inventory path: $path"
        if [[ "$path" == "$wanted" ]]; then
            printf '%s\t%s\t%s\t%s\n' "$path" "$bytes" "$blocks" "$inodes"
            return 0
        fi
    done < "$scenario/inventory.tsv"
    return 1
}

removed_totals() {
    local bytes blocks inodes path extra marker
    local removed_blocks=0 removed_inodes=0
    while IFS=$'\t' read -r bytes blocks inodes path extra ||
          [[ -n "${bytes:-}${blocks:-}${inodes:-}${path:-}${extra:-}" ]]; do
        [[ -z "${bytes:-}" || "${bytes:0:1}" == '#' ]] && continue
        [[ -z "${extra:-}" && "$bytes" =~ ^[0-9]+$ && "$blocks" =~ ^[0-9]+$ && "$inodes" =~ ^[0-9]+$ ]] ||
            die 'malformed inventory.tsv'
        valid_virtual_path "$path" || die "invalid inventory path: $path"
        marker=$scenario/fs$path
        if [[ ! -e "$marker" && ! -L "$marker" ]]; then
            removed_blocks=$((removed_blocks + blocks))
            removed_inodes=$((removed_inodes + inodes))
        fi
    done < "$scenario/inventory.tsv"
    printf '%s\t%s\n' "$removed_blocks" "$removed_inodes"
}

emit_blocks() {
    local total initial_used mount removed_blocks removed_inodes used available percent
    total=$(metric blocks_total)
    initial_used=$(metric blocks_used)
    mount=$(awk -F '\t' '$1 == "mount" { print $2 }' "$scenario/filesystem.tsv")
    [[ -n "$mount" ]] || die 'missing metric: mount'
    IFS=$'\t' read -r removed_blocks removed_inodes < <(removed_totals)
    used=$((initial_used - removed_blocks))
    (( used >= 0 && used <= total )) || die 'invalid computed block usage'
    available=$((total - used))
    percent=$(((used * 100 + total - 1) / total))
    printf 'Filesystem 1K-blocks Used Available Use%% Mounted_on\n'
    printf 'fixturefs %s %s %s %s%% %s\n' "$total" "$used" "$available" "$percent" "$mount"
}

emit_inodes() {
    local total initial_used mount removed_blocks removed_inodes used free percent
    total=$(metric inodes_total)
    initial_used=$(metric inodes_used)
    mount=$(awk -F '\t' '$1 == "mount" { print $2 }' "$scenario/filesystem.tsv")
    [[ -n "$mount" ]] || die 'missing metric: mount'
    IFS=$'\t' read -r removed_blocks removed_inodes < <(removed_totals)
    used=$((initial_used - removed_inodes))
    (( used >= 0 && used <= total )) || die 'invalid computed inode usage'
    free=$((total - used))
    percent=$(((used * 100 + total - 1) / total))
    printf 'Filesystem Inodes IUsed IFree IUse%% Mounted_on\n'
    printf 'fixturefs %s %s %s %s%% %s\n' "$total" "$used" "$free" "$percent" "$mount"
}

case "$command" in
    blocks)
        (( $# == 0 )) || usage
        emit_blocks
        ;;
    inodes)
        (( $# == 0 )) || usage
        emit_inodes
        ;;
    du)
        (( $# == 0 )) || usage
        cat "$scenario/du.tsv"
        ;;
    logs)
        (( $# == 0 )) || usage
        cat "$scenario/system.log"
        ;;
    approved)
        (( $# == 0 )) || usage
        cat "$scenario/approved-caches.txt"
        ;;
    candidates)
        (( $# == 0 )) || usage
        cat "$scenario/cleanup-candidates.txt"
        ;;
    stat)
        (( $# == 1 )) || usage
        record=$(inventory_record "$1") || die "path is not inventoried: $1"
        marker=$scenario/fs$1
        [[ -e "$marker" || -L "$marker" ]] || die "path is already absent: $1"
        printf '%s\n' "$record"
        ;;
    remove)
        (( $# == 1 )) || usage
        record=$(inventory_record "$1") || die "path is not inventoried: $1"
        marker=$scenario/fs$1
        [[ -f "$marker" && ! -L "$marker" ]] || die "refusing to remove non-file or absent path: $1"
        rm -- "$marker"
        printf '%s\n' "$record"
        ;;
    exists)
        (( $# == 1 )) || usage
        valid_virtual_path "$1" || die "invalid virtual path: $1"
        marker=$scenario/fs$1
        [[ -e "$marker" || -L "$marker" ]]
        ;;
    *) usage ;;
esac
