import fs from "node:fs"
import path from "node:path"
import process from "node:process"

const ROOT_DIR = process.cwd()

function loadDotEnv(filePath = path.join(ROOT_DIR, ".env")) {
  if (!fs.existsSync(filePath)) {
    return
  }

  const content = fs.readFileSync(filePath, "utf8")

  for (const line of content.split(/\r?\n/)) {
    const trimmed = line.trim()

    if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) {
      continue
    }

    const index = trimmed.indexOf("=")
    const key = trimmed.slice(0, index).trim()
    let value = trimmed.slice(index + 1).trim()

    if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
      value = value.slice(1, -1)
    }

    if (!process.env[key]) {
      process.env[key] = value
    }
  }
}

function requireEnv(name) {
  const value = process.env[name]

  if (!value) {
    throw new Error(`Variable d'environnement manquante : ${name}`)
  }

  return value
}

async function getAccessToken() {
  const tenantId = requireEnv("MICROSOFT_TENANT_ID")
  const clientId = requireEnv("MICROSOFT_CLIENT_ID")
  const clientSecret = requireEnv("MICROSOFT_CLIENT_SECRET")
  const body = new URLSearchParams({
    client_id: clientId,
    client_secret: clientSecret,
    scope: "https://graph.microsoft.com/.default",
    grant_type: "client_credentials",
  })
  const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body,
  })

  if (!response.ok) {
    throw new Error(`Erreur token Microsoft Graph (${response.status}) : ${await response.text()}`)
  }

  const data = await response.json()
  return data.access_token
}

function decodeJwtPayload(token) {
  const [, payload] = token.split(".")

  if (!payload) {
    return {}
  }

  const normalizedPayload = payload.replaceAll("-", "+").replaceAll("_", "/")
  const paddedPayload = normalizedPayload.padEnd(normalizedPayload.length + ((4 - normalizedPayload.length % 4) % 4), "=")

  return JSON.parse(Buffer.from(paddedPayload, "base64").toString("utf8"))
}

function parseSharePointSiteUrl(rawUrl) {
  const url = new URL(rawUrl)
  const normalizedPath = url.pathname.replace(/\/+$/, "")

  if (!normalizedPath) {
    throw new Error("L'URL SharePoint doit contenir le chemin du site, par exemple https://tenant.sharepoint.com/sites/nom-du-site")
  }

  return {
    hostname: url.hostname,
    sitePath: normalizedPath,
  }
}

async function graphGet(token, url) {
  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  })

  if (!response.ok) {
    const details = await response.text()
    const hint = response.status === 401
      ? "\nVérifie que l'application Entra ID a des permissions Microsoft Graph de type Application, par exemple Sites.Read.All ou Sites.ReadWrite.All, avec Grant admin consent. Vérifie aussi que le tenant correspond bien au SharePoint."
      : ""
    const error = new Error(`Erreur Microsoft Graph (${response.status}) : ${details}${hint}`)
    error.status = response.status
    error.details = details

    throw error
  }

  return response.json()
}

async function main() {
  loadDotEnv()

  const sharePointUrl = process.argv[2] || process.env.SHAREPOINT_SITE_URL

  if (!sharePointUrl) {
    throw new Error("Usage : npm run graph:find-drive -- https://tenant.sharepoint.com/sites/nom-du-site")
  }

  const token = await getAccessToken()
  const claims = decodeJwtPayload(token)

  console.log(`Token Microsoft Graph obtenu pour tenant=${claims.tid || "inconnu"} app=${claims.appid || claims.azp || "inconnue"}`)
  console.log(`Permissions applicatives dans le token : ${(claims.roles || []).join(", ") || "aucune"}`)
  console.log("")

  const { hostname, sitePath } = parseSharePointSiteUrl(sharePointUrl)
  let site

  try {
    site = await graphGet(
      token,
      `https://graph.microsoft.com/v1.0/sites/${hostname}:${sitePath}`
    )
  } catch (error) {
    if (error.status !== 404) {
      throw error
    }

    const searchTerm = sitePath.split("/").filter(Boolean).at(-1)
    console.log(`Site introuvable avec le chemin ${sitePath}. Recherche des sites contenant "${searchTerm}"...`)
    console.log("")

    const searchResults = await graphGet(
      token,
      `https://graph.microsoft.com/v1.0/sites?search=${encodeURIComponent(searchTerm)}`
    )

    if (!searchResults.value?.length) {
      throw error
    }

    console.log("Sites trouvés :")

    for (const result of searchResults.value) {
      console.log(`- ${result.displayName || result.name}`)
      console.log(`  SITE_ID=${result.id}`)
      console.log(`  webUrl=${result.webUrl}`)
    }

    console.log("")
    console.log("Relance ensuite la commande avec l'URL webUrl exacte du bon site.")
    return
  }

  const drives = await graphGet(
    token,
    `https://graph.microsoft.com/v1.0/sites/${encodeURIComponent(site.id)}/drives`
  )

  console.log(`SITE_ID=${site.id}`)
  console.log("")
  console.log("Bibliothèques SharePoint trouvées :")

  for (const drive of drives.value || []) {
    console.log(`- ${drive.name}`)
    console.log(`  GRAPH_DRIVE_ID=${drive.id}`)
    console.log(`  webUrl=${drive.webUrl}`)
  }
}

main().catch((error) => {
  console.error(error)
  process.exit(1)
})
