#!/usr/bin/env bash
set -euo pipefail

readonly DBUS_SERVICE="org.anduinos.BtrfsSnapshotsManager"
readonly DBUS_PATH="/org/anduinos/BtrfsSnapshotsManager"
readonly DBUS_INTERFACE="org.anduinos.BtrfsSnapshotsManager.Helper"

usage() {
    cat <<'EOF'
Disk Snapshots Manager CLI

Usage: anduinos-btrfs-snapshots-manager-cli <command> [arguments]

System snapshots:
  status [--json]                      Show layout and restore transaction state
  create [--json] <title> [description]
                                        Create a System snapshot
  list [--json]                        List trusted local system snapshots
  show <deployment-uuid> [--json]      Show one local system snapshot
  verify <deployment-uuid> [--json]    Verify local system snapshot integrity
  preview <deployment-uuid> [--json]   Preview a system restore
  restore <deployment-uuid>            Prepare one-shot system restore
  cancel-restore                       Cancel a restore before reboot
  delete <deployment-uuid>             Delete an unprotected system snapshot
  apply-retention                      Apply schedule-owned retention

Personal Files history:
  personal-create [--json] <title> [description]
                                        Snapshot the independent @home history
  personal-list [--json]                List local Personal Files history
  personal-files <uuid> [relative-path] [--json]
                                        Browse your own historical home
  personal-delete <uuid>                Delete an unprotected snapshot

Mutating and full-verification operations require Polkit authentication.
EOF
}

require_jq() {
    command -v jq >/dev/null 2>&1 || {
        echo "Error: jq is required by anduinos-btrfs-snapshots-manager-cli" >&2
        exit 1
    }
}

check_service() {
    busctl call --system \
        "$DBUS_SERVICE" \
        "$DBUS_PATH" \
        org.freedesktop.DBus.Peer \
        Ping >/dev/null 2>&1 || {
        echo "Error: Disk Snapshots Manager D-Bus service is unavailable" >&2
        exit 1
    }
}

call_raw() {
    local method="$1"
    shift
    busctl --system --json=short call \
        "$DBUS_SERVICE" \
        "$DBUS_PATH" \
        "$DBUS_INTERFACE" \
        "$method" \
        "$@"
}

single_string() {
    local response="$1"
    jq -er '.data[0] | strings' <<<"$response"
}

pair_payload() {
    local response="$1"
    local success
    # `jq -e` treats the valid JSON value false as a command failure. Convert
    # the already type-checked boolean to text before `-e` evaluates it so the
    # helper's error payload can be printed instead of being lost to `set -e`.
    success=$(jq -er '.data[0] | booleans | tostring' <<<"$response")
    local payload
    payload=$(jq -er '.data[1] | strings' <<<"$response")
    if [[ "$success" != "true" ]]; then
        echo "Error: $payload" >&2
        return 1
    fi
    printf '%s\n' "$payload"
}

validate_uuid() {
    local value="$1"
    [[ "$value" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] || {
        echo "Error: expected a canonical lowercase UUID, got '$value'" >&2
        return 1
    }
}

confirm() {
    local prompt="$1"
    local reply
    read -r -p "$prompt [y/N] " reply
    [[ "$reply" =~ ^[Yy]$ ]]
}

print_json_or_summary() {
    local json="$1"
    local json_mode="$2"
    local filter="$3"
    if [[ "$json_mode" == "true" ]]; then
        jq . <<<"$json"
    else
        jq -r "$filter" <<<"$json"
    fi
}

cmd_create() {
    local json_mode=false
    if [[ "${1:-}" == "--json" ]]; then
        json_mode=true
        shift
    fi
    local title="${1:-}"
    local description="${2:-Created from anduinos-btrfs-snapshots-manager-cli}"
    [[ -n "$title" ]] || {
        echo "Usage: anduinos-btrfs-snapshots-manager-cli create [--json] <title> [description]" >&2
        exit 1
    }
    local payload
    payload=$(pair_payload "$(call_raw CreateDeployment ssb "$title" "$description" false)")
    if [[ "$json_mode" == "true" ]]; then
        jq . <<<"$payload"
    else
        jq -r '"Created system snapshot \(.id): \(.title)"' <<<"$payload"
    fi
}

# Private scheduler contract. Deliberately omitted from user-facing help: the
# root-owned systemd service is its only supported caller.
cmd_create_scheduled() {
    local schedule_id="${1:-}"
    local title="${2:-}"
    local description="${3:-}"
    [[ -n "$schedule_id" && -n "$title" && -n "$description" ]] || {
        echo "Error: internal create-scheduled requires a schedule ID, title and description" >&2
        exit 1
    }
    pair_payload "$(call_raw CreateScheduledDeployment sss "$schedule_id" "$title" "$description")" >/dev/null
}

# Private scheduler contract for the independent Personal Files history.
cmd_personal_create_scheduled() {
    local schedule_id="${1:-}"
    local title="${2:-}"
    local description="${3:-}"
    [[ -n "$schedule_id" && -n "$title" && -n "$description" ]] || {
        echo "Error: internal personal-create-scheduled requires a schedule ID, title and description" >&2
        exit 1
    }
    pair_payload "$(call_raw CreateScheduledPersonalSnapshot sss "$schedule_id" "$title" "$description")" >/dev/null
}

cmd_notify_automatic_event() {
    local event="${1:-}"
    local scope="${2:-}"
    [[ "$event" =~ ^(starting|failed)$ && "$scope" =~ ^(system|personal)$ ]] || exit 2
    pair_payload "$(call_raw NotifyAutomaticSnapshotEvent ss "$event" "$scope")" >/dev/null
}

cmd_personal_create() {
    local json_mode=false
    if [[ "${1:-}" == "--json" ]]; then
        json_mode=true
        shift
    fi
    local title="${1:-}"
    local description="${2:-Created from anduinos-btrfs-snapshots-manager-cli}"
    [[ -n "$title" ]] || {
        echo "Usage: anduinos-btrfs-snapshots-manager-cli personal-create [--json] <title> [description]" >&2
        exit 1
    }
    local payload
    payload=$(pair_payload "$(call_raw CreatePersonalSnapshot ssb "$title" "$description" false)")
    print_json_or_summary "$payload" "$json_mode" '"Created Home snapshot \(.id): \(.title)"'
}

cmd_personal_list() {
    local json_mode=false
    [[ "${1:-}" == "--json" ]] && json_mode=true
    local status
    status=$(public_recovery_status)
    if [[ "$json_mode" == "true" ]]; then
        jq '.personal_snapshots // []' <<<"$status"
    else
        jq -r '
            if ((.personal_snapshots // []) | length) == 0 then
              "No Home snapshots"
            else
              .personal_snapshots[] |
              "\(.id)  \(.created_at)  [\(.kind)/\(.state)]  \(.title)"
            end
        ' <<<"$status"
    fi
}

cmd_personal_files() {
    local id="${1:-}"
    validate_uuid "$id"
    local path="${2:-}"
    local json_mode=false
    [[ "${2:-}" == "--json" ]] && { path=""; json_mode=true; }
    [[ "${3:-}" == "--json" ]] && json_mode=true
    [[ "$path" != /* && "$path" != *".."* ]] || {
        echo "Error: expected a safe relative path" >&2
        exit 1
    }
    local payload
    payload=$(pair_payload "$(call_raw ListPersonalFiles ss "$id" "$path")")
    print_json_or_summary "$payload" "$json_mode" '
        if length == 0 then "Empty directory" else
          .[] | "\(if .kind == "directory" then "DIR " else "FILE" end)  \(.name)  \(.size)"
        end
    '
}

cmd_personal_delete() {
    local id="${1:-}"
    validate_uuid "$id"
    confirm "Delete Home snapshot $id?" || exit 0
    pair_payload "$(call_raw DeletePersonalSnapshot s "$id")" >/dev/null
    echo "Home snapshot deleted"
}

public_recovery_status() {
    single_string "$(call_raw GetRecoveryEngineStatus)"
}

privileged_recovery_status() {
    single_string "$(call_raw GetPrivilegedRecoveryEngineStatus)"
}

cmd_status() {
    local json_mode=false
    [[ "${1:-}" == "--json" ]] && json_mode=true
    local status
    status=$(privileged_recovery_status)
    if [[ "$json_mode" == "true" ]]; then
        jq . <<<"$status"
    else
        jq -r '
            "Available:       \(.available)\n" +
            "Layout:          \(.layout.support // "unavailable")\n" +
            "Root source:     \(.layout.root_source // "unknown")\n" +
            "System snapshots: \(.deployment_count // 0)\n" +
            (if .pending == null then
               "Pending restore: none"
             else
               "Pending restore: \(.pending.id) [\(.pending.phase)]\n" +
               "Target:          \(.pending.target_deployment_id)\n" +
               "Fallback:        \(.pending.fallback_deployment_id)\n" +
               "Protocol:        \(.pending.recovery_protocol_version)\n" +
               "Initramfs tries: \(.pending.initramfs_attempts)\n" +
               "Apply attempts:  \(.pending.apply_attempts)\n" +
               "Checkpoint:      \(.pending.checkpoint // "not entered")" +
               (if .pending.failure == null then "" else
                  "\nFailure:         \(.pending.failure)"
                end)
             end)
        ' <<<"$status"
    fi
}

cmd_list() {
    local json_mode=false
    [[ "${1:-}" == "--json" ]] && json_mode=true
    local status
    status=$(privileged_recovery_status)
    if [[ "$json_mode" == "true" ]]; then
        jq '.deployments' <<<"$status"
    else
        jq -r '
            if (.deployments | length) == 0 then
              "No local system snapshots"
            else
              .deployments[] |
              "\(.id)  \(.created_at)  [\(.kind)/\(.state)]  \(.title)"
            end
        ' <<<"$status"
    fi
}

cmd_show() {
    local id="${1:-}"
    validate_uuid "$id"
    local json_mode=false
    [[ "${2:-}" == "--json" ]] && json_mode=true
    local record
    record=$(privileged_recovery_status | jq -ec --arg id "$id" '.deployments[] | select(.id == $id)') || {
        echo "Error: system snapshot not found" >&2
        exit 1
    }
    print_json_or_summary "$record" "$json_mode" '
        "ID:          \(.id)\n" +
        "Title:       \(.title)\n" +
        "Created:     \(.created_at)\n" +
        "Kind/state:  \(.kind) / \(.state)\n" +
        "Kernel:      \(.kernel_release // "unknown")\n" +
        "Protected:   \(.pinned)\n" +
        "Description: \(.reason)"
    '
}

cmd_verify() {
    local id="${1:-}"
    validate_uuid "$id"
    local json_mode=false
    [[ "${2:-}" == "--json" ]] && json_mode=true
    local result
    result=$(single_string "$(call_raw VerifySnapshot s "$id")")
    if [[ "$json_mode" == "true" ]]; then
        jq . <<<"$result"
    else
        jq -r '
            if .is_valid then
              "System snapshot integrity verified" +
              (if (.warnings | length) > 0 then
                 "\nWarnings:\n" + (.warnings | map("  - " + .) | join("\n"))
               else "" end)
            else
              "Verification failed:\n" + (.errors | map("  - " + .) | join("\n"))
            end
        ' <<<"$result"
        jq -e '.is_valid' <<<"$result" >/dev/null
    fi
}

cmd_preview() {
    local id="${1:-}"
    validate_uuid "$id"
    local json_mode=false
    [[ "${2:-}" == "--json" ]] && json_mode=true
    local verification status preview
    verification=$(single_string "$(call_raw VerifySnapshot s "$id")")
    jq -e '.is_valid == true' <<<"$verification" >/dev/null || {
        jq -r '"Error: system snapshot is not usable: " + (.errors | join("; "))' \
            <<<"$verification" >&2
        exit 1
    }
    status=$(privileged_recovery_status)
    preview=$(jq -ec --arg id "$id" '
        .deployments[] | select(.id == $id) |
        {
          recovery_point: .title,
          created_at: .created_at,
          target_kernel: (.kernel_release // "unknown"),
          system_files_and_packages_return_to_target: true,
          personal_files_unchanged: true,
          current_system_preserved_as_fallback: true,
          restart_required: true
        }
    ' <<<"$status") || {
        echo "Error: system snapshot not found" >&2
        exit 1
    }
    print_json_or_summary "$preview" "$json_mode" '
        "System snapshot: \(.recovery_point)\n" +
        "Created: \(.created_at)\n" +
        "Target kernel: \(.target_kernel)\n" +
        "System files and packages return to this point: true\n" +
        "Personal Files remain unchanged: true\n" +
        "Current system retained as fallback: true\n" +
        "Restart required: true"
    '
}

cmd_restore() {
    local id="${1:-}"
    validate_uuid "$id"
    confirm "Prepare a one-shot System restore to $id?" || exit 0
    pair_payload "$(call_raw ScheduleDeploymentRestore s "$id")" | jq .
    echo "Restore prepared. Reboot when ready; the current system is retained as fallback."
}

cmd_cancel_restore() {
    pair_payload "$(call_raw CancelDeploymentRestore)" >/dev/null
    echo "Pending restore cancelled"
}

cmd_delete() {
    local id="${1:-}"
    validate_uuid "$id"
    confirm "Delete local system snapshot $id?" || exit 0
    pair_payload "$(call_raw DeleteDeployment s "$id")" >/dev/null
    echo "System snapshot deleted"
}

cmd_apply_retention() {
    pair_payload "$(call_raw ApplyScheduleRetention)"
}

require_jq
if (($# == 0)); then
    usage
    exit 0
fi

command_name="$1"
shift
case "$command_name" in
    help|-h|--help) usage; exit 0 ;;
esac
check_service

case "$command_name" in
    status) cmd_status "$@" ;;
    create) cmd_create "$@" ;;
    create-scheduled) cmd_create_scheduled "$@" ;;
    notify-automatic-event) cmd_notify_automatic_event "$@" ;;
    personal-create-scheduled) cmd_personal_create_scheduled "$@" ;;
    list|ls) cmd_list "$@" ;;
    show) cmd_show "$@" ;;
    verify) cmd_verify "$@" ;;
    preview|preview-restore) cmd_preview "$@" ;;
    restore|rollback) cmd_restore "$@" ;;
    cancel-restore) cmd_cancel_restore "$@" ;;
    delete) cmd_delete "$@" ;;
    apply-retention) cmd_apply_retention "$@" ;;
    personal-create) cmd_personal_create "$@" ;;
    personal-list) cmd_personal_list "$@" ;;
    personal-files) cmd_personal_files "$@" ;;
    personal-delete) cmd_personal_delete "$@" ;;
    *)
        echo "Error: unknown command '$command_name'" >&2
        usage >&2
        exit 1
        ;;
esac
