#!/bin/bash

# Set -euo pipefail is important for script reliability
set -euo pipefail

# --- Logging ---
LOG_FILE="scrut_gcp_integration_$(date +%Y%m%d_%H%M%S).log"
exec &> >(tee -a "$LOG_FILE")
echo "Starting Scrut GCP integration. Logging to $LOG_FILE"

# --- Error Handling Functions ---
error_exit() {
    echo "ERROR: $1" >&2
    exit 1
}

# --- Constants ---
readonly SCRUT_SA="scrut-central-scanner-sa@scrut-central-scanner-id.iam.gserviceaccount.com"
readonly APIS=(
    "iam.googleapis.com"
    "iamcredentials.googleapis.com"
    "cloudresourcemanager.googleapis.com"
)

# Global Variables
declare org_id
declare billing_account_id
declare -a projects
declare excluded_project_id="scrut-central-scanner-id"
declare delay_in_seconds=60

# ---------------------------------------------------------------------------
# Handle Interactive Selection (From scrut_gcp_integration.sh)
# ---------------------------------------------------------------------------

# Step 1: List all organizations
echo "Fetching all organizations..."
organizations=$(gcloud organizations list --format="value(displayName)")

if [ -z "$organizations" ]; then
    error_exit "No organizations found."
fi

# Display organizations
echo "Please select an organization from the list:"
options=("${organizations[@]}" "Quit")
PS3="Enter the number of the organization you want: "
select org in "${options[@]}"; do
    if [ "$REPLY" = $((${#options[@]})) ]; then # Check for "Quit" option
        echo "Exiting."
        exit 0
    elif [ -n "$org" ]; then
        echo "You selected organization: $org"
        echo # Add a newline here
        break
    else
        echo "Invalid selection, please try again."
    fi
done

# Set the org ID right after selecting organization
org_id=$(gcloud organizations list --format="value(ID)" --filter="displayName=$org")

# Step 1.5: Fetch and select billing account
echo "Fetching available billing accounts..."
billing_accounts_raw=$(gcloud beta billing accounts list --format="json")

if [ -z "$billing_accounts_raw" ] || ! jq -e . <<<"$billing_accounts_raw" >/dev/null; then
    error_exit "No billing accounts found or invalid JSON. Ensure you have access to at least one billing account."
fi

# Parse the JSON output to extract name and displayName
billing_accounts=()
while IFS= read -r line; do
    name=$(echo "$line" | jq -r '.name')
    display_name=$(echo "$line" | jq -r '.displayName')
    if [[ -n "$name" && -n "$display_name" ]]; then
        # Remove the billingAccounts/ prefix
        formatted_name=$(echo "$name" | sed 's|^billingAccounts/||')
        billing_accounts+=("$formatted_name - $display_name")
    fi
done < <(echo "$billing_accounts_raw" | jq -c '.[]')

# Display billing accounts
echo "Please select a billing account from the list:"
options=("${billing_accounts[@]}" "Quit")
PS3="Enter the number of the billing account you want: "
select billing_account_full in "${options[@]}"; do
    if [ "$REPLY" = $((${#options[@]})) ]; then # Check for "Quit" option
        echo "Exiting."
        exit 0
    elif [ -n "$billing_account_full" ]; then
        billing_account_id=$(echo "$billing_account_full" | cut -d' ' -f1)
        billing_account_name=$(echo "$billing_account_full" | cut -d' ' -f3-)
        echo "You selected billing account: $billing_account_name ($billing_account_id)"
        echo # Add a newline here
        break
    else
        echo "Invalid selection, please try again."
    fi
done

# Step 2: Select Projects - Modified.
echo "Fetching projects linked to billing account ${billing_account_id}..."
all_projects_raw=$(gcloud beta billing projects list --billing-account="$billing_account_id" --format="value(projectId)")

if [ -z "$all_projects_raw" ]; then
    echo "WARNING: No projects found linked to the selected billing account." >&2
    projects=()
else
    # Filter out the excluded project and linked with billing account
    all_projects=()
    projects=() # Clear before populating
    project_counter=1
    while IFS= read -r project_id; do
        if [[ "$project_id" != "$excluded_project_id" ]]; then
            all_projects+=("$project_id")
            echo "$project_counter) $project_id"
            ((project_counter++))
        fi
    done < <(echo "$all_projects_raw")

    # Check if all_projects is empty after filtering
    if [[ "${#all_projects[@]}" -eq 0 ]]; then
        error_exit "No eligible projects found for the selected billing account after excluding project ID '$excluded_project_id'."
    fi

    # Ask the user which projects to include
    echo "Please select the projects to be scanned (space-separated project numbers, 'all', or 'q' to quit): "
    options=("${all_projects[@]}" "Quit")
    PS3="Enter project numbers (space-separated), 'all', or 'q' to quit: "
    read -a selections

    # Handle "all" selection
    if [[ " ${selections[@]} " == *" all "* ]]; then
        projects=("${all_projects[@]}")
    else
        # Setup PROJECTS depending on input
        for selection in "${selections[@]}"; do
            if [[ "$selection" == "q" ]]; then
                echo "Exiting project selection."
                exit 0
            elif [[ "$selection" =~ ^[0-9]+$ ]]; then
                index=$((selection - 1)) # Adjust for 0-based indexing
                if [[ "$index" -ge 0 ]] && [[ "$index" -lt "${#all_projects[@]}" ]]; then
                    projects+=("${all_projects[$index]}")
                else
                    echo "Invalid project number: $selection" >&2
                fi
            else
                echo "Invalid selection: $selection, please input only numbers, 'all', or 'q'." >&2
            fi
        done
    fi

    if [[ "${#projects[@]}" -eq 0 ]]; then
        error_exit "No projects selected."
    fi

    echo "Selected projects for scanning: ${projects[*]}"
fi

# ---------------------------------------------------------------------------
# Core Impersonation Setup Loop
# ---------------------------------------------------------------------------

for PROJECT_ID in "${projects[@]}"; do

    echo "--------------------------------------------------"
    echo "Processing project: $PROJECT_ID"
    echo "--------------------------------------------------"

    # --- Define Service Account Details ---
    SA_NAME="scrut-gcp-scanner"
    SA_DISPLAY_NAME="Scrut GCP Scanner Service Account"
    SA_EMAIL="$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com"

    # --- Switch to the project ---
    echo "Switching to project: $PROJECT_ID"
    gcloud config set project "$PROJECT_ID" || error_exit "Failed to switch to project: $PROJECT_ID"

    # --- Create Service Account (if it doesn't exist) ---
    echo "Checking if service account $SA_EMAIL exists..."
    if gcloud iam service-accounts describe "$SA_EMAIL" >/dev/null 2>&1; then
        echo "Service account $SA_EMAIL already exists."
    else
        echo "Creating service account $SA_EMAIL..."
        gcloud iam service-accounts create "$SA_NAME" --display-name="$SA_DISPLAY_NAME" || error_exit "Failed to create service account $SA_NAME"
        echo "Service account $SA_EMAIL created."
    fi

    # --- Add Roles to the Service Account ---
    ROLES=(
        "roles/viewer"
        "roles/iam.securityReviewer"
        "roles/monitoring.viewer"
        "roles/iam.serviceAccountTokenCreator"
    )

    echo "Adding roles to target service account $SA_EMAIL..."
    for role in "${ROLES[@]}"; do
        echo "Adding role: $role"
        gcloud projects add-iam-policy-binding "$PROJECT_ID" --member "serviceAccount:$SA_EMAIL" --role "$role" || error_exit "Failed to add role $role to target service account $SA_EMAIL"
    done
    echo "Roles added to service account $SA_EMAIL."

    # --- Grant the `iam.serviceAccounts.getAccessToken` permission FIRST ---
    echo "Granting Scrut SA the iam.serviceAccounts.getAccessToken permission on the target service account..."
    gcloud iam service-accounts add-iam-policy-binding "$SA_EMAIL" \
        --member "serviceAccount:$SCRUT_SA" \
        --role "roles/iam.serviceAccountUser" || error_exit "Failed to grant Scrut SA the iam.serviceAccounts.getAccessToken permission."
    echo "Scrut's service account ($SCRUT_SA) now has the iam.serviceAccounts.getAccessToken permission on your service account ($SA_EMAIL)."

    # --- Add a small delay to allow permissions to propagate ---
    echo "Sleeping for $delay_in_seconds seconds to allow permissions to propagate..."
    sleep $delay_in_seconds

    # --- Enable Scrut to Impersonate Your Service Account ---
    echo "Enabling Scrut to impersonate your service account..."
    gcloud iam service-accounts add-iam-policy-binding "$SA_EMAIL" \
        --member "serviceAccount:$SCRUT_SA" \
        --role "roles/iam.serviceAccountTokenCreator" || error_exit "Failed to allow Scrut to impersonate your service account."
    echo "Scrut's service account ($SCRUT_SA) can now impersonate your service account ($SA_EMAIL)."

    # --- Add a small delay to allow permissions to propagate ---
    echo "Sleeping for $delay_in_seconds seconds to allow permissions to propagate..."
    sleep $delay_in_seconds

    echo "--------------------------------------------------"
    echo "Impersonation setup complete for project: $PROJECT_ID"
    echo "--------------------------------------------------"

done

# --- Final Output ---
echo "--------------------------------------------------"
echo "Integration complete for all selected projects!"
echo "Please provide the following information to Scrut:"
echo "  Scrut Service Account: $SCRUT_SA"
echo "  Selected Projects and their Service Accounts:"
for PROJECT_ID in "${projects[@]}"; do
    SA_EMAIL="scrut-gcp-scanner@$PROJECT_ID.iam.gserviceaccount.com"
    echo "    - Project: $PROJECT_ID, Service Account: $SA_EMAIL"
done
echo "--------------------------------------------------"

# ---------------------------------------------------------------------------
#  Create JSON Output File
# ---------------------------------------------------------------------------

# Initialize JSON data
json_data='{'
json_data+='"central_service_account": "'"$SCRUT_SA"'",'
json_data+='"target_projects": ['

# Add project information to the JSON string
first=true
for project_id in "${projects[@]}"; do
    if $first; then
        first=false
    else
        json_data+=','
    fi
    sa_email="scrut-gcp-scanner@$project_id.iam.gserviceaccount.com"
    json_data+='{"project_id": "'"$project_id"'", "service_account_email": "'"$sa_email"'"}'
done

json_data+=']}' # Close target_projects array

# Define the output file name
output_file="scrut_config.json"

echo "$json_data"

# Write the JSON data to the file
echo "$json_data" >"$output_file"

echo "[ Scrut ] Configuration saved to: $output_file"

# Compress the JSON data
echo "[ Scrut ] Compressing configuration file..."
gzip "${output_file}"

# ---------------------------------------------------------------------------
# Hybrid Encryption (AES-256-CBC + RSA-2048)
#  - RSA can only encrypt ~245 bytes (2048-bit key with PKCS#1 padding)
#  - Solution: Encrypt data with AES, encrypt AES key with RSA
# ---------------------------------------------------------------------------

echo "[ Scrut ] Starting hybrid encryption (AES-256-CBC + RSA-2048)..."

# Public Key (RSA-2048)
PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ElXV9bWSv4mHrvQeSL5
zPkNYpayycMUtK62sysEjERpefrP6O8ThDQwhyWS8k48r5sGuguGD7F8tSjGusQX
P3OqcZOQ+RcLLr06eCI9PzaUaR6YgUhBZWcTk3r6T0CHV3Dyji76+OMD7aUHKCd0
GEU5et92Zy5bQjmY6v+RVac0HVY67GzXz/Kp/kT6JANEtzL0fX/PyW0lSt150cT/
AqhWCwttfVXVHjXdAAktBXufRuAPtkuSGRQURYG2hxrOay2jwZIEUtMzXyMiTuG5
1MSwE+UIYQFts7zbMPPYyAOU/fF/cYIb2905YHk0i+IoUhSF+FiAFBIid4ChIMKQ
0QIDAQAB
-----END PUBLIC KEY-----"

# Temporary files
TEMP_PUBLIC_KEY_FILE="./public_key.pem"
TEMP_AES_KEY_FILE="./aes_key.bin"
TEMP_AES_IV_FILE="./aes_iv.bin"

# Write the public key to a file
echo "$PUBLIC_KEY" > "${TEMP_PUBLIC_KEY_FILE}"
if [ ! -f "${TEMP_PUBLIC_KEY_FILE}" ]; then
    error_exit "Failed to create temporary public key file."
fi

# Step 1: Generate random AES-256 key (32 bytes) and IV (16 bytes)
echo "[ Scrut ] Generating random AES-256 key and IV..."
openssl rand -out "${TEMP_AES_KEY_FILE}" 32
openssl rand -out "${TEMP_AES_IV_FILE}" 16

# Read key and IV as hex for OpenSSL encryption
# Using od which is available on macOS by default (xxd requires vim)
AES_KEY_HEX=$(od -An -tx1 "${TEMP_AES_KEY_FILE}" | tr -d ' \n')
AES_IV_HEX=$(od -An -tx1 "${TEMP_AES_IV_FILE}" | tr -d ' \n')

# Step 2: Encrypt the compressed data with AES-256-CBC
echo "[ Scrut ] Encrypting data with AES-256-CBC..."
openssl enc -aes-256-cbc -in "${output_file}.gz" -out "${output_file}.gz.aes" \
    -K "${AES_KEY_HEX}" -iv "${AES_IV_HEX}"

# Step 3: Combine AES key + IV (48 bytes total) and encrypt with RSA
echo "[ Scrut ] Encrypting AES key with RSA-2048..."
cat "${TEMP_AES_KEY_FILE}" "${TEMP_AES_IV_FILE}" > "./aes_key_iv.bin"
openssl pkeyutl -encrypt -pubin -inkey "${TEMP_PUBLIC_KEY_FILE}" \
    -in "./aes_key_iv.bin" -out "${output_file}.key.enc"

# Step 4: Create final encrypted package (key + data)
echo "[ Scrut ] Creating final encrypted package..."
FINAL_OUTPUT="${output_file}.enc"

# Package format: [4 bytes: key length][encrypted key][encrypted data]
KEY_SIZE=$(wc -c < "${output_file}.key.enc" | tr -d ' ')
# Write 4-byte big-endian key size (using printf instead of xxd)
printf "$(printf '\\x%02x\\x%02x\\x%02x\\x%02x' \
    $(( (KEY_SIZE >> 24) & 0xFF )) \
    $(( (KEY_SIZE >> 16) & 0xFF )) \
    $(( (KEY_SIZE >> 8) & 0xFF )) \
    $(( KEY_SIZE & 0xFF )))" > "${FINAL_OUTPUT}"
cat "${output_file}.key.enc" >> "${FINAL_OUTPUT}"
cat "${output_file}.gz.aes" >> "${FINAL_OUTPUT}"

# Cleanup temporary files
echo "[ Scrut ] Cleaning up temporary files..."
rm -f "${TEMP_PUBLIC_KEY_FILE}" "${TEMP_AES_KEY_FILE}" "${TEMP_AES_IV_FILE}"
rm -f "./aes_key_iv.bin" "${output_file}.gz" "${output_file}.gz.aes" "${output_file}.key.enc"

# Final output
FINAL_SIZE=$(wc -c < "${FINAL_OUTPUT}" | tr -d ' ')
echo "[ Scrut ] =================================================="
echo "[ Scrut ] Encryption complete!"
echo "[ Scrut ] Output file: ${FINAL_OUTPUT}"
echo "[ Scrut ] Final encrypted size: ${FINAL_SIZE} bytes"
echo "[ Scrut ] =================================================="
echo "[ Scrut ] Please provide the following file:"
echo "[ Scrut ]   - ${FINAL_OUTPUT} (Encrypted configuration)"
echo "[ Scrut ] Script completed. See $LOG_FILE for details."

exit 0
