/*
  Passe une facture client dans une autre devise, sans toucher à sa contre-valeur
  en euros.

  POURQUOI CE CHEMIN EXISTE. `PUT /invoices/{id}` ACCEPTE les champs
  `multicurrency_code` et `multicurrency_tx` et répond 200 — mais ne les
  applique pas : `Facture::update()` ne les traite pas. La facture reste en EUR
  et rien ne le signale. Seule l'interface expose les actions
  `editmulticurrencycode` et `editmulticurrencyrate`.

  POURQUOI LE SCRIPT REMET AUSSI LA LIGNE. Changer la devise ABÎME la ligne :
  Dolibarr recalcule `montant_devise = euros × taux_du_dictionnaire`, puis le
  changement de taux fige la devise et recalcule les euros. La contre-valeur
  euro se met donc à dériver. Le seul moyen de la ramener est de réécrire la
  ligne en donnant les DEUX prix — `subprice` en euros, `multicurrency_subprice`
  en devise. Les deux opérations sont inséparables ; les séparer laisserait la
  facture dans un état faux entre les deux.

  Et `PUT /invoices/{id}/lines/{lid}` n'est pas un PATCH : les champs absents du
  corps sont remis à zéro — `desc` effacé, `product_type` ramené de service à
  produit. Le script relit donc la ligne et la renvoie ENTIÈRE.

  CE QUI NE DOIT PAS BOUGER : la contre-valeur en euros. Les livres sont tenus
  en euros (C. com. art. L.123-22) ; libeller la facture en devise ne doit pas y
  déplacer un centime. Le script le vérifie et refuse si l'euro a dérivé.

  RAPPEL DE DROIT. Une facture peut être libellée dans toute monnaie
  (CGI art. 289, II — directive 2006/112, art. 230) ; seule la TVA à payer doit
  être déterminée en euros. Sans TVA française — autoliquidation par un preneur
  hors UE, CGI art. 259-1° et 283-2 — la contrainte ne s'applique pas.

  Usage :
    deno run -A test/setInvoiceCurrency.ts --id 15 --code USD \
      --rate 1.164992 --mc-amount 3000.00 [--dry-run]
*/
import "load_dotenv";
import { chromium } from "playwright";
import login from "./scripts/login.ts";
import { assertSandbox } from "./scripts/guard.ts";

const argv = Deno.args;
const pick = (f: string, d = "") => (argv.includes(f) ? argv[argv.indexOf(f) + 1] : d);

const id = pick("--id");
const code = pick("--code", "USD");
const rate = pick("--rate");
const mcAmount = pick("--mc-amount");   // prix unitaire dans la devise
const dryRun = argv.includes("--dry-run");

if (!id || !rate || !mcAmount) {
  console.error("--id, --rate et --mc-amount sont requis");
  Deno.exit(2);
}

const dolibarrAddress = assertSandbox();
const apiUrl = Deno.env.get("DOLIBARR_URL") || dolibarrAddress;
const apiKey = Deno.env.get("DOLIBARR_API_KEY") || "";

console.log(`cible   : ${dolibarrAddress}`);
console.log(`facture : id=${id} → ${mcAmount} ${code} au taux ${rate}`);

/** Relit la facture par l'API : la page de retour ne prouve rien. */
async function etat(): Promise<Record<string, unknown>> {
  const r = await fetch(`${apiUrl}/api/index.php/invoices/${id}`, {
    headers: { DOLAPIKEY: apiKey, Accept: "application/json" },
  });
  return await r.json();
}

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ locale: "fr-FR" });
const page = await context.newPage();

try {
  const avant = await etat();
  console.log(`avant   : ${avant.multicurrency_code} taux ${avant.multicurrency_tx} — ` +
    `HT ${avant.total_ht} EUR, ${avant.multicurrency_total_ht} en devise`);
  if (avant.multicurrency_code === code && Number(avant.multicurrency_tx) === Number(rate)) {
    console.log("déjà dans cette devise à ce taux, rien à faire.");
    Deno.exit(0);
  }
  // La devise ne se change que sur un brouillon. On y passe nous-même plutôt
  // que d'exiger de l'appelant une étape séparée : la remise en brouillon fait
  // partie de l'opération, pas de sa préparation.
  if (String(avant.status) !== "0") {
    if (String(avant.paye) === "1") {
      console.error("facture RÉGLÉE — refus. Changer la devise d'une facture encaissée " +
        "déplacerait un montant déjà rapproché de la banque.");
      Deno.exit(1);
    }
    console.log(`statut ${avant.status} → remise en brouillon`);
    if (!dryRun) {
      const r = await fetch(`${apiUrl}/api/index.php/invoices/${id}/settodraft`, {
        method: "POST",
        headers: { DOLAPIKEY: apiKey, "Content-Type": "application/json" },
        body: JSON.stringify({ idwarehouse: 0 }),
      });
      if (!r.ok) { console.error(`settodraft a échoué : HTTP ${r.status}`); Deno.exit(1); }
    }
  }
  if (dryRun) {
    console.log("\n--dry-run : rien n'est soumis.");
    Deno.exit(0);
  }

  await login.doAdminLogin({
    page,
    dolibarrAddress,
    adminCredentials: {
      username: Deno.env.get("DOLI_ADMIN_LOGIN") || "undefined",
      password: Deno.env.get("DOLI_ADMIN_PASSWORD") || "undefined",
    },
  });

  /** Ouvre une action d'édition en ligne et soumet le formulaire qui la porte. */
  async function editer(action: string, remplir: () => Promise<void>): Promise<void> {
    await page.goto(`${dolibarrAddress}/compta/facture/card.php?facid=${id}`);
    const href = await page.locator(`a[href*="${action}"]`).first().getAttribute("href");
    if (!href) throw new Error(`action ${action} absente de la fiche — droits, ou module inactif`);
    await page.goto(new URL(href, dolibarrAddress).toString());
    await remplir();
    await page.waitForLoadState("networkidle");
  }

  await editer("editmulticurrencycode", async () => {
    // Le select n'a pas de nom stable d'une version à l'autre : on le trouve
    // par l'option qu'il contient.
    const sel = page.locator(`select:has(option[value="${code}"])`).first();
    await sel.selectOption(code);
    await sel.locator("xpath=ancestor::form").locator('input[type="submit"]').first().click();
  });

  await editer("editmulticurrencyrate", async () => {
    const inp = page.locator('input[name="multicurrency_tx"], input[name="rate"]').first();
    await inp.fill(rate);
    await inp.locator("xpath=ancestor::form").locator('input[type="submit"]').first().click();
  });

  // Réparation de la ligne, indissociable de ce qui précède. On relit d'abord :
  // le PUT n'est pas un PATCH, tout champ omis serait remis à zéro.
  const ligne = ((await etat()).lines as Record<string, string>[])[0];
  const eurAvant = String(avant.total_ht);
  const puEur = (Number(eurAvant) / Number(ligne.qty || 1)).toFixed(2);
  const rep = await fetch(`${apiUrl}/api/index.php/invoices/${id}/lines/${ligne.id}`, {
    method: "PUT",
    headers: { DOLAPIKEY: apiKey, "Content-Type": "application/json" },
    body: JSON.stringify({
      desc: ligne.desc, description: ligne.desc,
      subprice: puEur, pu_ht: puEur, multicurrency_subprice: mcAmount,
      qty: ligne.qty, product_type: "1", tva_tx: ligne.tva_tx,
      remise_percent: ligne.remise_percent,
      localtax1_tx: ligne.localtax1_tx, localtax2_tx: ligne.localtax2_tx,
      info_bits: ligne.info_bits, special_code: ligne.special_code, rang: ligne.rang,
      situation_percent: ligne.situation_percent ?? "100",
      fk_warehouse: ligne.fk_warehouse ?? "0", pa_ht: ligne.pa_ht ?? "0",
    }),
  });
  if (!rep.ok) { console.error(`réécriture de la ligne : HTTP ${rep.status}`); Deno.exit(1); }

  const apres = await etat();
  console.log(`après   : ${apres.multicurrency_code} taux ${apres.multicurrency_tx} — ` +
    `HT ${apres.total_ht} EUR, ${apres.multicurrency_total_ht} en devise`);

  if (apres.multicurrency_code !== code) {
    console.error("ÉCHEC — la devise relue n'est pas celle demandée.");
    Deno.exit(1);
  }
  if (Number(apres.total_ht) !== Number(avant.total_ht)) {
    console.error(`ÉCHEC — la contre-valeur euro a bougé : ${avant.total_ht} -> ${apres.total_ht}. ` +
      "Le passage en devise ne doit RIEN déplacer dans les livres.");
    Deno.exit(1);
  }
  if (Math.abs(Number(apres.multicurrency_total_ht) - Number(mcAmount)) > 0.01) {
    console.error(`ÉCHEC — montant en devise ${apres.multicurrency_total_ht}, attendu ${mcAmount}.`);
    Deno.exit(1);
  }
  const lf = (apres.lines as Record<string, string>[])[0];
  if (!(lf.desc || "").trim() || lf.product_type !== "1") {
    console.error(`ÉCHEC — la ligne a été abîmée : desc=${lf.desc ? "ok" : "VIDE"}, ` +
      `product_type=${lf.product_type} (attendu 1).`);
    Deno.exit(1);
  }
  console.log("ok — devise posée, contre-valeur euro inchangée, ligne intacte, relu par l'API.");
} finally {
  await context.close();
  await browser.close();
}
