#!/usr/bin/env bash
# ============================================================
# azure-setup-wizard — configure Azure / SharePoint credentials
# for file2sharepoint
#
# Usage:
#   azure-setup-wizard [/path/to/.env]
#
# When no path is given the .env in the project root is used.
# ============================================================

set -euo pipefail
IFS=$'\n\t'

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${1:-${SCRIPT_DIR}/../.env}"
ENV_FILE="$(realpath -m "${ENV_FILE}")"

# ── ANSI colours ─────────────────────────────────────────────
RED='\033[0;31m'
GRN='\033[0;32m'
YLW='\033[1;33m'
BLU='\033[0;34m'
CYN='\033[0;36m'
BLD='\033[1m'
NC='\033[0m'

info()    { echo -e "${CYN}[info]${NC}  $*"; }
ok()      { echo -e "${GRN}[ ok ]${NC}  $*"; }
warn()    { echo -e "${YLW}[warn]${NC}  $*"; }
err()     { echo -e "${RED}[err ]${NC}  $*" >&2; }
header()  { echo -e "\n${BLD}${BLU}─── $* ───${NC}\n"; }
die()     { err "$*"; exit 1; }

# ── Helpers ───────────────────────────────────────────────────
prompt() {
    # prompt VAR_NAME  "Text"  [default]
    local _var="$1" _txt="$2" _def="${3:-}" _val
    if [[ -n "$_def" ]]; then
        read -rp "  ${_txt} [${_def}]: " _val
        _val="${_val:-$_def}"
    else
        while true; do
            read -rp "  ${_txt}: " _val
            [[ -n "$_val" ]] && break
            warn "Value required."
        done
    fi
    printf -v "$_var" '%s' "$_val"
}

prompt_secret() {
    local _var="$1" _txt="$2" _val
    while true; do
        read -rsp "  ${_txt}: " _val; echo
        [[ -n "$_val" ]] && break
        warn "Value required."
    done
    printf -v "$_var" '%s' "$_val"
}

ask_yn() {
    # ask_yn "Question?"  [y|n]  → returns 0 (yes) or 1 (no)
    local _ans _def="${2:-y}"
    if [[ "$_def" == "y" ]]; then
        read -rp "  $1 [Y/n]: " _ans; _ans="${_ans:-y}"
    else
        read -rp "  $1 [y/N]: " _ans; _ans="${_ans:-n}"
    fi
    [[ "${_ans,,}" == "y" ]]
}

require_az() {
    if ! command -v az &>/dev/null; then
        warn "'az' (Azure CLI) is not installed."
        echo
        echo "  Install on Debian/Ubuntu:"
        echo "    curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash"
        echo
        if ask_yn "Continue without Azure CLI (enter credentials manually)?" y; then
            AZ_AVAILABLE=0
        else
            die "Please install Azure CLI and re-run this wizard."
        fi
    else
        AZ_AVAILABLE=1
    fi
}

# ── Load existing .env values as defaults ─────────────────────
load_env_defaults() {
    [[ -f "$ENV_FILE" ]] || return
    while IFS='=' read -r key value; do
        [[ "$key" =~ ^[[:space:]]*# ]] && continue
        [[ -z "$key" ]] && continue
        key="${key// /}"
        value="${value%%#*}"          # strip inline comments
        value="${value#"${value%%[![:space:]]*}"}"  # ltrim
        value="${value%"${value##*[![:space:]]}"}"  # rtrim
        case "$key" in
            OFFICE365_TENANT)    DEF_TENANT="$value"    ;;
            OFFICE365_SITE)      DEF_SITE="$value"      ;;
            SHAREPOINT_LIBRARY)  DEF_LIBRARY="$value"   ;;
            OFFICE365_USERNAME)  DEF_USERNAME="$value"  ;;
            OFFICE365_CLIENTID)  DEF_CLIENTID="$value"  ;;
        esac
    done < "$ENV_FILE"
}

# ── Write .env ─────────────────────────────────────────────────
write_env() {
    if [[ -f "$ENV_FILE" ]]; then
        cp "${ENV_FILE}" "${ENV_FILE}.bak"
        info "Existing .env backed up → ${ENV_FILE}.bak"
    fi

    {
        echo "# file2sharepoint configuration"
        echo "# Generated by azure-setup-wizard on $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
        echo ""
        echo "OFFICE365_TENANT=${OFFICE365_TENANT}"
        echo "OFFICE365_SITE=${OFFICE365_SITE}"
        echo "SHAREPOINT_LIBRARY=${SHAREPOINT_LIBRARY}"
        echo ""
        if [[ "${AUTH_MODE}" == "user" ]]; then
            echo "OFFICE365_USERNAME=${OFFICE365_USERNAME}"
            echo "OFFICE365_PASSWORD=${OFFICE365_PASSWORD}"
        else
            echo "OFFICE365_CLIENTID=${OFFICE365_CLIENTID}"
            echo "OFFICE365_CLSECRET=${OFFICE365_CLSECRET}"
        fi
    } > "${ENV_FILE}"

    chmod 600 "${ENV_FILE}"
    ok "Configuration written to ${ENV_FILE} (permissions: 600)"
}

# ── Azure App Registration setup ──────────────────────────────
setup_app_registration() {
    header "Azure App Registration"

    # ── Login ──────────────────────────────────────────────────
    info "Logging in to Azure (a browser window may open) …"
    az login --output none || die "Azure login failed."
    ok "Authenticated with Azure"

    # ── Select subscription / show tenant ─────────────────────
    TENANT_ID=$(az account show --query tenantId -o tsv)
    SUBSCRIPTION=$(az account show --query name -o tsv)
    info "Subscription : ${SUBSCRIPTION}"
    info "Tenant ID    : ${TENANT_ID}"

    # ── App name ───────────────────────────────────────────────
    DEFAULT_APPNAME="file2sharepoint-${OFFICE365_SITE}"
    prompt APP_DISPLAY_NAME "App registration display name" "${DEFAULT_APPNAME}"

    # ── Check for existing app ─────────────────────────────────
    EXISTING_ID=$(az ad app list --display-name "${APP_DISPLAY_NAME}" --query "[0].appId" -o tsv 2>/dev/null || true)
    if [[ -n "$EXISTING_ID" && "$EXISTING_ID" != "None" ]]; then
        warn "App '${APP_DISPLAY_NAME}' already exists (ID: ${EXISTING_ID})."
        if ask_yn "Re-use existing app (a new secret will be generated)?" y; then
            APP_ID="$EXISTING_ID"
            REUSE=1
        else
            prompt APP_DISPLAY_NAME "New app display name"
            REUSE=0
        fi
    else
        REUSE=0
    fi

    # ── Create app if needed ───────────────────────────────────
    if [[ "${REUSE}" -eq 0 ]]; then
        info "Creating app registration '${APP_DISPLAY_NAME}' …"
        APP_ID=$(az ad app create \
            --display-name "${APP_DISPLAY_NAME}" \
            --query appId -o tsv)
        ok "App created: ${APP_ID}"

        info "Creating service principal …"
        az ad sp create --id "${APP_ID}" --output none
        ok "Service principal created"
    fi

    # ── Microsoft Graph permission ─────────────────────────────
    # Microsoft fully retired the classic "SharePoint" API's app-only
    # permission model (the AllSites.Write Role this used to request here,
    # under resource 00000003-0000-0ff1-ce00-000000000000) on 2026-04-02 -
    # it relied on Azure ACS token issuance, and SharePoint Online now
    # rejects that permission model's tokens on the real REST call
    # regardless of credential correctness, even when a token is still
    # syntactically issued. Confirmed by hand: SharePoint responds
    # "Unsupported app only token." even with a valid Entra ID v2 token and
    # AllSites.Write assigned this way.
    #
    # The supported replacement is the Microsoft Graph "Sites.Selected"
    # application permission plus an explicit, per-site grant via Graph
    # (POST /sites/{siteId}/permissions) - see
    # https://learn.microsoft.com/sharepoint/dev/sp-add-ins/retirement-announcement-for-azure-acs
    GRAPH_RESOURCE_ID="00000003-0000-0000-c000-000000000000"
    GRAPH_SITES_SELECTED="883ea226-0bf2-4a8f-9f9d-92c9162a727d"

    info "Adding Microsoft Graph 'Sites.Selected' application permission …"
    az ad app permission add \
        --id "${APP_ID}" \
        --api "${GRAPH_RESOURCE_ID}" \
        --api-permissions "${GRAPH_SITES_SELECTED}=Role" \
        --output none 2>/dev/null || warn "Permission may already be assigned — continuing."
    ok "Permission added"

    # ── Admin consent ─────────────────────────────────────────
    info "Granting admin consent …"
    if az ad app permission admin-consent --id "${APP_ID}" --output none 2>/dev/null; then
        ok "Admin consent granted"
    else
        warn "Could not grant admin consent automatically (Global Admin role required)."
        echo
        echo "  Grant consent manually in the Azure Portal:"
        echo "  https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/${APP_ID}"
        echo
        read -rp "  Press Enter once admin consent has been granted …"
    fi

    # ── Per-site grant via Graph ────────────────────────────────
    # Sites.Selected only opens the *door* - the app still has access to no
    # site until explicitly granted one. This uses the wizard operator's own
    # az login session (needs at least Sites.FullControl.All delegated
    # access on the target site, e.g. SharePoint/Global admin), not the
    # app's own credentials.
    info "Looking up SharePoint site ${OFFICE365_TENANT}.sharepoint.com/sites/${OFFICE365_SITE} …"
    SITE_GRAPH_ID=$(az rest --method get \
        --url "https://graph.microsoft.com/v1.0/sites/${OFFICE365_TENANT}.sharepoint.com:/sites/${OFFICE365_SITE}" \
        --query id -o tsv 2>/dev/null || true)

    if [[ -z "${SITE_GRAPH_ID}" || "${SITE_GRAPH_ID}" == "None" ]]; then
        warn "Could not resolve the site via Graph - grant access manually:"
        echo
        echo "  POST https://graph.microsoft.com/v1.0/sites/{siteId}/permissions"
        echo '  { "roles": ["write"], "grantedToIdentities": [ { "application": {'
        echo "      \"id\": \"${APP_ID}\", \"displayName\": \"${APP_DISPLAY_NAME}\" } } ] }"
        echo
    else
        info "Granting the app 'write' access to this site via Graph …"
        GRANT_BODY=$(printf '{"roles":["write"],"grantedToIdentities":[{"application":{"id":"%s","displayName":"%s"}}]}' \
            "${APP_ID}" "${APP_DISPLAY_NAME}")
        if az rest --method post \
            --url "https://graph.microsoft.com/v1.0/sites/${SITE_GRAPH_ID}/permissions" \
            --body "${GRANT_BODY}" \
            --headers "Content-Type=application/json" \
            --output none 2>/dev/null; then
            ok "Site permission granted"
        else
            warn "Could not grant the site permission automatically - grant it manually:"
            echo
            echo "  POST https://graph.microsoft.com/v1.0/sites/${SITE_GRAPH_ID}/permissions"
            echo "  ${GRANT_BODY}"
            echo
            read -rp "  Press Enter once the site permission has been granted …"
        fi
    fi

    # ── Create client secret ───────────────────────────────────
    info "Generating client secret (valid for 2 years) …"
    # Reset credentials to get a fresh secret
    CLIENT_SECRET=$(az ad app credential reset \
        --id "${APP_ID}" \
        --years 2 \
        --query password -o tsv)
    ok "Client secret created"

    OFFICE365_CLIENTID="${APP_ID}"
    OFFICE365_CLSECRET="${CLIENT_SECRET}"
    OFFICE365_USERNAME=""
    OFFICE365_PASSWORD=""

    echo
    echo -e "  ${BLD}App name  :${NC} ${APP_DISPLAY_NAME}"
    echo -e "  ${BLD}Client ID :${NC} ${APP_ID}"
    echo -e "  ${BLD}Tenant    :${NC} ${OFFICE365_TENANT}.onmicrosoft.com"
    warn "The client secret is shown only once and will be saved to the .env file."
    echo
}

# ═══════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════
echo
echo -e "${BLD}${BLU}╔══════════════════════════════════════════════════════╗${NC}"
echo -e "${BLD}${BLU}║   file2sharepoint — Azure / SharePoint Setup Wizard  ║${NC}"
echo -e "${BLD}${BLU}╚══════════════════════════════════════════════════════╝${NC}"
echo
echo "  This wizard will create / update: ${ENV_FILE}"
echo "  It configures credentials needed to upload files to SharePoint."
echo

# ── Load existing defaults ────────────────────────────────────
DEF_TENANT="" DEF_SITE="" DEF_LIBRARY="Documents" DEF_USERNAME="" DEF_CLIENTID=""
load_env_defaults

# ── SharePoint destination ────────────────────────────────────
header "SharePoint Destination"
prompt OFFICE365_TENANT  "Azure AD / SharePoint tenant name  (e.g. 'yourcompany')" "${DEF_TENANT}"
prompt OFFICE365_SITE    "SharePoint site name  (e.g. 'TeamSite')"                 "${DEF_SITE}"
prompt SHAREPOINT_LIBRARY "Destination library / folder path  (e.g. 'Documents')"  "${DEF_LIBRARY}"

# ── Auth method ───────────────────────────────────────────────
header "Authentication Method"
echo "  1) App registration  — Client ID + Secret  (recommended for automation)"
echo "  2) User credentials  — Username + Password  (legacy / interactive)"
echo
read -rp "  Choose [1/2] (default: 1): " AUTH_CHOICE
AUTH_CHOICE="${AUTH_CHOICE:-1}"

if [[ "$AUTH_CHOICE" == "2" ]]; then
    # ── User credentials ─────────────────────────────────────
    header "User Credentials"
    warn "Note: legacy username/password auth may be blocked by Conditional Access policies."
    prompt        OFFICE365_USERNAME "Office 365 username  (e.g. user@${OFFICE365_TENANT}.onmicrosoft.com)" "${DEF_USERNAME}"
    prompt_secret OFFICE365_PASSWORD "Office 365 password"
    OFFICE365_CLIENTID=""
    OFFICE365_CLSECRET=""
    AUTH_MODE="user"
else
    # ── Client credentials ────────────────────────────────────
    AUTH_MODE="client"
    require_az

    if [[ "${AZ_AVAILABLE}" -eq 1 ]]; then
        setup_app_registration
    else
        # Manual entry
        header "Manual Client Credentials"
        info "Enter the credentials from an existing Azure App Registration."
        echo
        echo "  Required Microsoft Graph application permission:"
        echo "    Resource : Microsoft Graph  (00000003-0000-0000-c000-000000000000)"
        echo "    Role     : Sites.Selected"
        echo "  Plus a per-site grant (POST /sites/{siteId}/permissions) - the"
        echo "  legacy SharePoint 'AllSites.Write' role no longer works: Microsoft"
        echo "  retired Azure ACS app-only auth for all tenants on 2026-04-02."
        echo
        prompt        OFFICE365_CLIENTID "Client ID (Application ID)"  "${DEF_CLIENTID}"
        prompt_secret OFFICE365_CLSECRET "Client Secret"
        OFFICE365_USERNAME=""
        OFFICE365_PASSWORD=""
    fi
fi

# ── Write .env ────────────────────────────────────────────────
header "Writing .env"
write_env

# ── Test hint ─────────────────────────────────────────────────
header "Setup Complete"
ok "Configuration saved to: ${ENV_FILE}"
echo
echo "  Test with:"
echo "    file2sharepoint '/path/to/files/*' '${SHAREPOINT_LIBRARY}' '${ENV_FILE}'"
echo
if [[ "${AUTH_MODE}" == "client" && -n "${OFFICE365_CLIENTID:-}" ]]; then
    echo "  Azure Portal (App Registration):"
    echo "    https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/${OFFICE365_CLIENTID}"
    echo
fi
