#!/bin/sh

set -e

export NOTIFY_LONG=vault
export NOTIFY_SHORT="${0##*/}"

sanitize()
{
	s="${1#"${1%%[![:space:]]*}"}"
	s="${s%"${s##*[![:space:]]}"}"
	if [ "${#s}" -eq 0 ]
	then
		notify 'Invalid Input' 'Empty strings do not constitute valid input'
		exit 1
	fi
}

usage()
{
	cat <<-EOF >&2
	Usage: ${0##*/} add [-c]
	       ${0##*/} edit [-c]
	       ${0##*/} get
	       ${0##*/} rm [-c]
	EOF
	exit 1
}

xecho()
{
	printf '%s' "$@"
}

prompt()
{
	if [ -t 2 ]
	then
		printf '%s: ' "$1"
		read -r s
	else
		s="$(fuzzel -d -l0 -P0 --prompt="$1: ")"
	fi
	sanitize "$s"
}

xprompt()
{
	if [ -t 2 ]
	then
		printf '%s: ' "$1"
		stty -echo
		trap 'stty echo' INT
		read -r s
		stty echo
		trap - INT
		echo
	else
		s="$(fuzzel -d -l0 -P0 --password --prompt="$1: ")"
	fi
	sanitize "$s"
}

add()
{
	shift

	while getopts 'c' opt
	do
		case $opt in
		c)
			add_c
			exit 0
			;;
		*)
			usage
			;;
		esac
	done

	readonly data="$(enchive extract <"$VAULT")"

	c="$(xecho "$data" | jq -r 'keys | .[]' | osel)"

	prompt 'Password name'
	n="$s"

	xecho "$data" \
	| jq -e --arg c "$c" --arg n "$n" '.[$c] | has($n) | not' >/dev/null || {
		notify 'Failed To Add Password' "The password ‘$n’ already exists"
		exit 1
	}

	if [ "$VAULT_2FA" = "$c" ]
	then
		xprompt 'Secret key'; k="$s"
		prompt 'Digits'; d="$s"
		prompt 'Period'

		xecho "$data" \
		| jq --arg c "$c" \
			 --arg n "$n" \
			 --arg s "$k" \
			 --arg d "$d" \
			 --arg p "$s" \
			'.[$c] += {($n): {
				"secret": $s,
				"digits": ($d | tonumber),
				"period": ($p | tonumber)
			}}' \
		| enchive archive >"$VAULT"
		[ ! -t 2 ] && notify '2FA Key Added' \
			"The 2FA key ‘$n’ was added with the digit length ‘$d’ and period ‘$p’"
	else
		xprompt 'Password'

		xecho "$data" \
		| jq --arg c "$c" --arg n "$n" --arg s "$s" '.[$c] += {($n): $s}' \
		| enchive archive >"$VAULT"
		[ ! -t 2 ] && notify 'Password Added' \
			"The password ‘$n’ was added to the category ‘$c’"
	fi
}

add_c()
{
	readonly data="$(enchive extract <"$VAULT")"

	prompt 'Category to create'

	xecho "$data" | jq -e --arg s "$s" 'has($s) | not' >/dev/null || {
		notify 'Failed To Create Category' "The category ‘$s’ already exists"
		exit 1
	}

	xecho "$data" \
	| jq --arg s "$s" '. + {($s): {}}' \
	| enchive archive >"$VAULT"
	[ ! -t 2 ] && \
		notify 'Category Created' "The password category ‘$s’ was created"
}

get()
{
	readonly data="$(enchive extract <"$VAULT")"

	c="$(xecho "$data" | jq -r 'keys | .[]' | osel)"
	o="$(xecho "$data" | jq -r --arg c "$c" '.[$c] | keys | .[]' | osel)"

	xecho "$data" | if [ "$VAULT_2FA" = "$c" ]
	then
		eval "$(jq -r --arg c "$c" --arg o "$o" '
		   .[$c]
		   | .[$o]
		   | "totp -d"
		   + (.digits | tostring)
		   + " -p"
		   + (.period | tostring)
		   + " "
		   + .secret
		')" \
		| wl-copy -no \
		&& [ ! -t 2 ] \
		&& notify '2FA Code Copied To The Clipboard' \
		"The 2FA code for ‘$o’ was copied to the clipboard"
	else
		jq -r --arg c "$c" --arg o "$o" '.[$c] | .[$o]' \
		| wl-copy -no \
		&& [ ! -t 2 ] \
		&& notify 'Password Copied To The Clipboard' \
		"The password for ‘$o’ was copied to the clipboard"
	fi
}

rm_()
{
	shift

	while getopts 'c' opt
	do
		case $opt in
		c)
			rm_c
			exit 0
			;;
		*)
			usage
			;;
		esac
	done

	readonly data="$(enchive extract <"$VAULT")"

	c="$(xecho "$data" | jq -r 'keys | .[]' | osel)"
	n="$(xecho "$data" | jq -r --arg c "$c" '.[$c] | keys | .[]' | osel)"

	xecho "$data" \
	| jq --arg c "$c" --arg n "$n" 'del(.[$c] | .[$n])' \
	| enchive archive >"$VAULT"
	[ ! -t 2 ] && notify 'Removed Password' \
		"The password ‘$n’ was removed from the category ‘$c’"
}

rm_c()
{
	readonly data="$(enchive extract <"$VAULT")"

	c="$(xecho "$data" | jq -r 'keys | .[]' | osel)"

	xecho "$data" \
	| jq -e --arg c "$c" '.[$c] | length == 0' >/dev/null || {
		notify 'Failed To Remove Category' "The category ‘$c’ is not empty"
		exit 1
	}

	xecho "$data" \
	| jq --arg c "$c" 'del(.[$c])' \
	| enchive archive >"$VAULT"
	[ ! -t 2 ] && notify 'Removed Category' "The category ‘$c’ was removed"
}

edit()
{
	shift

	while getopts 'c' opt
	do
		case $opt in
		c)
			edit_c
			exit 0
			;;
		*)
			usage
			;;
		esac
	done

	readonly data="$(enchive extract <"$VAULT")"

	c="$(xecho "$data" | jq -r 'keys | .[]' | osel)"
	n="$(xecho "$data" | jq -r --arg c "$c" '.[$c] | keys | .[]' | osel)"

	if [ "$VAULT_2FA" = "$c" ]
	then
		xprompt 'Secret key'; k="$s"
		prompt 'Digits'; d="$s"
		prompt 'Period'

		xecho "$data" \
		| jq --arg c "$c" \
			 --arg n "$n" \
			 --arg s "$k" \
			 --arg d "$d" \
			 --arg p "$s" \
			'.[$c] += {($n): {
				"secret": $s,
				"digits": ($d | tonumber),
				"period": ($p | tonumber)
			}}' \
		| enchive archive >"$VAULT"
		[ ! -t 2 ] && notify '2FA Key Added' \
			"The 2FA key ‘$n’ was added with the digit length ‘$d’ and period ‘$p’"
	else
		xprompt 'Password'
		xecho "$data" \
		| jq --arg c "$c" --arg n "$n" --arg s "$s" '.[$c] += {($n): $s}' \
		| enchive archive >"$VAULT"
		[ ! -t 2 ] && notify 'Password Edit' \
			"The password ‘$n’ in the category ‘$c’ was changed"
	fi
}

edit_c()
{
	readonly data="$(enchive extract <"$VAULT")"

	c="$(xecho "$data" | jq -r 'keys | .[]' | osel)"
	prompt 'Category name'
	xecho "$data" \
	| jq --arg o "$c" --arg n "$s" \
		 'with_entries(if .key == $o then .key = $n else . end)' \
	| enchive archive >"$VAULT"
	[ ! -t 2 ] && notify 'Category Edit' "The category ‘$c’ was renamed"
}

raw()
{
	shift
	enchive extract <"$VAULT" \
	| jq --arg c "$1" --arg n "$2" -r '.[$c] | .[$n]'
}

: ${VAULT_2FA:="2fa"}
: ${VAULT_HOME:=${XDG_DATA_HOME:-$HOME/.local/share}/vault}
readonly VAULT="${VAULT_HOME}/vault.sec"

[ $# -eq 0 ] && usage

case "$1" in
add)
	add "$@"
	;;
edit)
	edit "$@"
	;;
get)
	get
	;;
raw)
	raw "$@"
	;;
rm)
	rm_ "$@"
	;;
*)
	usage
	;;
esac