#!/bin/bash
TopDir="/var/lib/backuppc"
LC_ALL=C

# Default values
INPUT_FILE=""
FULL_OUTPUT=0
TOTAL_CHECKED=0
CORRECT_REPORTS=0
SUB_PATH_MISMATCHES=0
SUB_HASH_MISMATCHES=0
SUB_NOT_FOUND=0

function _show_help() {
	echo "Usage: $0 [OPTIONS]"
	echo ""
	echo "Options:"
	echo "  -h, --help          Show this help message"
	echo "  -i, --input <file>  Specify the input file containing the message list"
	echo "  -f, --full          Output all test results (Default: only output discrepancies/errors)"
	echo ""
	exit 0
}

function _poolFile() {
	# input: $1=hash of poolfile
	local b1_val b1 b2_val b2
	b1_val=$(( (16#${1:0:2}) & 0xfe )); b1=$(printf "%02x" "$b1_val")
	b2_val=$(( (16#${1:2:2}) & 0xfe )); b2=$(printf "%02x" "$b2_val")
	
	if [ -f "$TopDir/pool/$b1/$b2/$1" ]; then echo "$TopDir/pool/$b1/$b2/$1"
	elif [ -f "$TopDir/cpool/$b1/$b2/$1" ]; then echo "$TopDir/cpool/$b1/$b2/$1"
	else echo ""; fi
} # from _poolFile

function _poolOut() {
	# input: $1=hash of poolfile
	local poolfile
	poolfile=$(_poolFile "$1")
	if [ -n "$poolfile" ]; then ls -lh "$poolfile" 2>/dev/null | sed 's/^/      -> /'
	else echo -e "      -> ${RED}poolfile not found${RESET}"; fi
} # from _poolOut

# Helper function to safely read and cache BackupPC v4 attributes
function _load_attrib_cache() {
	local target_dir="$1"
	if [ "$target_dir" != "$PREV_ATTRIB_DIR" ]; then
		CACHED_ATTRIB_CONTENT=$(sudo -u backuppc /usr/share/backuppc/bin/BackupPC_attribPrint "$target_dir"/attrib_[a-f0-9]* 2>/dev/null | tr -d '\0')
		PREV_ATTRIB_DIR="$target_dir"
	fi
} # from _load_attrib_cache
#=========================================
# M A I N
#=========================================
# Parse parameters
while [[ $# -gt 0 ]]; do
	case "$1" in
		-h|--help)	_show_help ;;
		-i|--input)	INPUT_FILE="$2"; shift 2 ;;
		-f|--full)	FULL_OUTPUT=1; shift ;;
		*)			echo "Unknown option: $1"
					_show_help
					;;
	esac
done

# Validate input
if [ -z "$INPUT_FILE" ]; then
	echo "Error: Input file must be specified with -i or --input."
	exit 1
fi
if [ ! -f "$INPUT_FILE" ]; then
	echo "Error: Input file '$INPUT_FILE' does not exist."
	exit 1
fi

# ANSI Escape Codes for Scannability
GREEN="\033[1;32m"
RED="\033[1;31m"
BROWN="\033[0;33m"
RESET="\033[0m"
DIV_LINE="--------------------------------------------------------------------------------"

# High-Speed Directory RAM Cache Variables
PREV_ATTRIB_DIR=""
CACHED_ATTRIB_CONTENT=""

echo "[*] Starting forensic verification engine..."

while read -r line; do
	[[ -z "$line" ]] && continue

	# Regex matching G.W. Haywood layout string exactly
	if [[ $line =~ Found\ \[([a-f0-9]{32})\]\ in\ host\ \[(.*?)\]\ backup\ number\ \[([0-9]+)\]\ share\ \[(.*?)\]\ file\ \[([^\]]*)\] ]]; then
		((TOTAL_CHECKED++))
		
		orig_hash="${BASH_REMATCH[1]}"
		host="${BASH_REMATCH[2]}"
		bnum="${BASH_REMATCH[3]}"
		share="${BASH_REMATCH[4]}"
		raw_filespec="${BASH_REMATCH[5]}"; raw_filespec="${raw_filespec%@}"

		# STEP 1: Strict byte-level sanitation. Converts any malformed/non-ASCII byte strictly to '?' while preserving all legal Windows/Linux syntax.
		safe_filespec=$(echo "$raw_filespec" | LC_ALL=C sed 's/[\x80-\xFF]/?/g')

		dir_part=$(dirname "$safe_filespec")
		file_part=$(basename "$safe_filespec")
		[ "$dir_part" = "." ] && dir_part=""

		# Reconstruct native v4 paths with standard 'f' prefixes
		if [ -n "$dir_part" ]; then
			f_dir_part=$(echo "$dir_part" | sed -E 's|([^/]+)|f\1|g')
			base_pattern="$TopDir/pc/$host/$bnum/f$share/$f_dir_part"
		else
			base_pattern="$TopDir/pc/$host/$bnum/f$share"
		fi

		# Resolve the absolute path of the containing directory via glob expansion
		resolved_dir=""
		for d in $base_pattern; do
			if [ -d "$d" ]; then
				resolved_dir="$d"
				break
			fi
		done

		DIRECT_MATCH=0
		ATTRIB_SIZE=-1
		POOL_SIZE=-1
		PRINT_OK=0
		OK_MSG=""

		# Command substitution ensures safe path assignment
		TARGET_POOL_FILE=$(_poolFile "$orig_hash")
		if [ -n "$TARGET_POOL_FILE" ]; then 
			POOL_SIZE=$(stat -c %s "$TARGET_POOL_FILE")
		fi

		# STEP 2: Check if the file is directly accessible at the specified location
		if [ -n "$resolved_dir" ]; then
			_load_attrib_cache "$resolved_dir"
			
			# Target file anchor extraction from attribute table
			file_block=$(echo "$CACHED_ATTRIB_CONTENT" | sed -n "/'$file_part' =>/,/},/p")
			if [ -n "$file_block" ]; then
				attrib_hash=$(echo "$file_block" | grep -oP "'digest' => '\K[a-f0-9]*")
				if [ "$attrib_hash" = "$orig_hash" ]; then
					DIRECT_MATCH=1
					ATTRIB_SIZE=$(echo "$file_block" | grep -oP "'size' => \K[0-9]+")
				fi
			fi
		fi
		echo -en "$TOTAL_CHECKED ${line:0:${#DIV_LINE}-10} ...\r"		# progress-bar
		# REPORTING LAYER A (A + B1 Combined): Direct match or valid 0-byte corruption found
		if [ "$DIRECT_MATCH" -eq 1 ] && [ "$POOL_SIZE" -eq 0 ] && [ "$ATTRIB_SIZE" -ne 0 ]; then
			PRINT_OK=1
			OK_MSG="${GREEN}OK: POOL FILE CORRUPT (0 BYTES) -> Verified via Layer A (Direct Match)${RESET}"
			((CORRECT_REPORTS++))
		else
			# REPORTING LAYER B: Symmetries broken -> Initiate intelligent Deep-Search recursion for B1, B2, B3
			clean_dir_spec=$(dirname "$safe_filespec")
			[ "$clean_dir_spec" = "." ] && clean_dir_spec=""

			if [ -n "$clean_dir_spec" ]; then
				search_dir_part=$(echo "$clean_dir_spec" | sed -E 's|([^/]+)|f\1*|g')
				search_base_pattern="$TopDir/pc/$host/$bnum/f$share/$search_dir_part*"
			else
				search_base_pattern="$TopDir/pc/$host/$bnum/f$share/*"
			fi

			DEEP_FOUND=0
			REAL_LOCATION_STRING=""

			for matched_folder in $search_base_pattern; do
				[ ! -d "$matched_folder" ] && continue
				while mapfile -t -n 50 dirs && ((${#dirs[@]})); do
					for sub_dir in "${dirs[@]}"; do
						_load_attrib_cache "$sub_dir"
						if echo "$CACHED_ATTRIB_CONTENT" | grep -q "'digest' => '$orig_hash'"; then
							local_block=$(echo "$CACHED_ATTRIB_CONTENT" | grep -B2 "'digest' => '$orig_hash'" | sed -n '1,/=>/p')
							discovered_name=$(echo "$local_block" | grep -oP "^\s*'\K[^']*(?='\s*=>)" | head -n 1)
							local_clean_dir="${sub_dir#$TopDir/pc/$host/$bnum/}"
							local_clean_dir=$(echo "$local_clean_dir" | sed 's|/f|/|g' | sed 's|^f||')
							REAL_LOCATION_STRING="$local_clean_dir/$discovered_name"
							DEEP_FOUND=1
							break 2
						fi
					done
				done < <(find "$matched_folder" -type d 2>/dev/null)
				[ "$DEEP_FOUND" -eq 1 ] && break
			done

			if [ "$DEEP_FOUND" -eq 0 ]; then
				if [ -n "$resolved_dir" ] && echo "$CACHED_ATTRIB_CONTENT" | grep -qP "^\s*'$file_part'\s*=>"; then
					echo $DIV_LINE
					echo "$line"
					actual_disk_hash=$(echo "$CACHED_ATTRIB_CONTENT" | sed -n "/'$file_part' =>/,/},/p" | grep -oP "'digest' => '\K[a-f0-9]*")
					[ -z "$actual_disk_hash" ] && actual_disk_hash="NO_DIGEST_OR_EMPTY"
					echo -e "${RED}FAILURE: FILE EXISTS WITH DIFFERENT HASH $actual_disk_hash${RESET}"
					_poolOut "$orig_hash"
					_poolOut "$actual_disk_hash"
					((SUB_HASH_MISMATCHES++))
				else
					echo $DIV_LINE
					echo "$line"
					echo -e "${RED}DISCREPANCY: Hash not found below [$share/${safe_filespec}*]${RESET}"
					((SUB_NOT_FOUND++))
				fi
			else
				clean_location_display="${REAL_LOCATION_STRING#$share/}"
				discovered_file_name=$(basename "$clean_location_display")
				orig_raw_filename=$(basename "$raw_filespec")

				[ "$ATTRIB_SIZE" -eq -1 ] && ATTRIB_SIZE=$(echo "$CACHED_ATTRIB_CONTENT" | sed -n "/'$discovered_file_name' =>/,/},/p" | grep -oP "'size' => \K[0-9]+")

				if [ "$discovered_file_name" = "$orig_raw_filename" ]; then
					if [ "$POOL_SIZE" -eq 0 ] && [ "$ATTRIB_SIZE" -ne 0 ]; then
						# B1 (Deep-Search 0-Byte Corrupt -> OK)
						PRINT_OK=1
						OK_MSG="${GREEN}OK: POOL FILE CORRUPT (0 BYTES) -> Verified via Deep Crawl (B1)${RESET}\n   Real location : $clean_location_display"
						((CORRECT_REPORTS++))
					else
						# B2 (False Positive - Healthy File)
						echo $DIV_LINE
						echo "$line"
						echo -e "${RED}FAILURE: ERROR REPORTED BUT POOL FILE IS HEALTHY (B2)${RESET}"
						echo "   Real location : $clean_location_display"
						echo "   Attrib-Size   : ${ATTRIB_SIZE} Bytes"
						echo "   Poolfile-Size : ${POOL_SIZE} Bytes"
						_poolOut "$orig_hash"
						((SUB_HASH_MISMATCHES++))
					fi
				else
					# B3 (False Positive - Truncated/Wrong path)
					echo $DIV_LINE
					echo "$line"
					echo -e "${BROWN}DISCREPANCY: Truncated or wrong path reported (B3)${RESET}"
					echo "   Real location : $clean_location_display"
					((SUB_PATH_MISMATCHES++))
					_poolOut "$orig_hash"
				fi
			fi
		fi
		if [ "$PRINT_OK" -eq 1 ] && [ "$FULL_OUTPUT" -eq 1 ]; then
			echo $DIV_LINE
			echo "$line"
			echo -e "$OK_MSG"
			_poolOut "$orig_hash"
		fi
	fi
done < "$INPUT_FILE"

FALSE_REPORTS=$((SUB_PATH_MISMATCHES + SUB_HASH_MISMATCHES + SUB_NOT_FOUND))
echo "================================================================================"
echo "   FINAL FORENSIC VALIDATION SUMMARY"
echo "================================================================================"
echo "   Total unique lines parsed       : $TOTAL_CHECKED"
echo -e "   Correct reports (Verified 0B)   : ${GREEN}$CORRECT_REPORTS${RESET}"
echo -e "   False reports (Total Mismatches): ${RED}$FALSE_REPORTS${RESET}"
echo "   ----------------------------------------------------------------------------"
echo -e "     -> Truncated / Mismatched Paths: $SUB_PATH_MISMATCHES"
echo -e "     -> Healthy Files (Wrong Hash)  : $SUB_HASH_MISMATCHES"
echo -e "     -> Completely Missing Hashes   : $SUB_NOT_FOUND"
echo "================================================================================"
