import type { MessageDIpa, MessagePourIpa } from "@/messages.js";

import { client, types, mandataire } from "@constl/ipa";
import { faisRien } from "@constl/utils-ipa";
import { attente, sfip } from "@constl/utils-tests";
import { isValidAddress } from "@orbitdb/core";
import {
  générerMandataire,
  Mandatairifiable,
  MandataireConstellation,
  type ErreurMandataire,
} from "@/mandataire.js";
import { isNode, isElectronMain } from "wherearewe";

import { expect, chai, chaiAsPromised } from "aegir/chai";
chai.use(chaiAsPromised);

class Mandataire extends Mandatairifiable {
  ipa: mandataire.EnveloppeIpa;
  constructor({ opts }: { opts: client.optsConstellation }) {
    super();
    this.ipa = new mandataire.EnveloppeIpa(
      (m: MessageDIpa) => {
        this.recevoirMessageDIpa(m);
      },
      (e: ErreurMandataire) => {
        this.recevoirMessageDIpa({
          type: "erreur",
          erreur: e.erreur,
          codeErreur: e.code,
          idRequête: e.idRequête,
        });
      },
      opts,
    );
  }

  envoyerMessageÀIpa(message: MessagePourIpa): void {
    this.ipa.gérerMessage(message);
  }
}

describe("Mandataire Constellation", () => {
  let mnd: MandataireConstellation<client.Constellation>;
  let fOublierConstellation: types.schémaFonctionOublier;

  const attendreNoms = new attente.AttendreRésultat<{
    [clef: string]: string;
  }>();
  const attendreMC = new attente.AttendreRésultat<
    types.résultatRecherche<types.infoRésultatTexte>[]
  >();

  before(async () => {
    let dossierTempo: string | undefined = undefined;
    let dossierSFIP: string | undefined = undefined;

    if (isNode || isElectronMain) {
      const fs = await import("fs");
      const path = await import("path");
      const os = await import("os");
      dossierTempo = fs.mkdtempSync(path.join(os.tmpdir(), "constl-ipa"));
      dossierSFIP = path.join(dossierTempo, "sfip");
    }

    const sfip1 = await sfip.créerHéliaTest({ dossier: dossierSFIP });

    mnd = générerMandataire(
      new Mandataire({
        opts: {
          dossier: dossierTempo,
          orbite: {
            ipfs: sfip1,
          },
        },
      }),
    );
    fOublierConstellation = async () => {
      await mnd.fermer();
      await sfip1.stop();
      if (isNode || isElectronMain) {
        const rimraf = await import("rimraf");
        if (dossierTempo) rimraf.sync(dossierTempo);
      }
    };
  });

  after(async () => {
    attendreNoms.toutAnnuler();
    attendreMC.toutAnnuler();
    if (fOublierConstellation) await fOublierConstellation();
  });

  it("Action", async () => {
    const idCompte = await mnd.obtIdCompte();
    expect(isValidAddress(idCompte)).to.be.true();
  });

  it("Action avec arguments", async () => {
    const idVariable = await mnd.variables.créerVariable({
      catégorie: "audio",
    });
    expect(isValidAddress(idVariable)).to.be.true();
  });

  it("Suivi", async () => {
    const oublierNoms = await mnd.profil!.suivreNoms({
      f: (n) => attendreNoms.mettreÀJour(n),
    });

    const val = await attendreNoms.attendreExiste();
    expect(Object.keys(val).length).to.eq(0);

    await mnd.profil!.sauvegarderNom({
      langue: "fr",
      nom: "Julien Jean Malard-Adam",
    });
    const val2 = await attendreNoms.attendreQue(
      (x) => Object.keys(x).length > 0,
    );
    expect(val2).to.deep.equal({ fr: "Julien Jean Malard-Adam" });

    await oublierNoms();

    await mnd.profil!.sauvegarderNom({
      langue: "es",
      nom: "Julien Jean Malard-Adam",
    });
    expect(attendreNoms.val).to.deep.equal({ fr: "Julien Jean Malard-Adam" });
  });

  it("Recherche", async () => {
    // Eléments détectés
    const { fOublier, fChangerN } =
      await mnd.recherche.rechercherMotsClefsSelonNom({
        nomMotClef: "Météo Montréal",
        f: (x) => attendreMC.mettreÀJour(x),
        nRésultatsDésirés: 1,
      });

    const idMotClef1 = await mnd.motsClefs.créerMotClef();
    await mnd.motsClefs.sauvegarderNomsMotClef({
      idMotClef: idMotClef1,
      noms: { fr: "Météo à Montréal" },
    });

    const idMotClef2 = await mnd.motsClefs.créerMotClef();
    await mnd.motsClefs.sauvegarderNomsMotClef({
      idMotClef: idMotClef2,
      noms: { fr: "Météo Montréal" },
    });

    const val = await attendreMC.attendreQue(
      (x) => x.length > 0 && x[0].id === idMotClef2,
    );
    expect(val.map((r) => r.id)).to.deep.equal([idMotClef2]);

    // Augmenter N résultats désirés
    await fChangerN(2);
    const val2 = await attendreMC.attendreQue((x) => x.length > 1);
    expect(val2.map((r) => r.id)).to.have.members([idMotClef1, idMotClef2]);

    // Diminuer N
    await fChangerN(1);
    const val3 = await attendreMC.attendreQue((x) => x.length <= 1);
    expect(val3.map((r) => r.id)).to.deep.equal([idMotClef2]);

    await fOublier();
  });

  it("Erreur fonction suivi inexistante", async () => {
    await expect(
      // @ts-expect-error on fait exprès
      mnd.jeNeSuisPasUneFonction(),
    ).to.be.rejectedWith("n'existe pas ou n'est pas une fonction");
  });

  it("Erreur action inexistante", async () => {
    await expect(
      // @ts-expect-error on fait exprès
      mnd.jeNeSuisPasUnAtribut.ouUneFonction(),
    ).to.be.rejectedWith("n'existe pas ou n'est pas une fonction");
  });

  it("Erreur suivi trop de fonctions", async () => {
    await expect(
      // @ts-expect-error on fait exprès
      mnd.profil.suivreNoms({ f: faisRien, f2: faisRien }),
    ).to.be.rejectedWith("Plus d'un argument pour");
  });

  it("Erreur suivi sans fonction", async () => {
    await expect(
      // @ts-expect-error on fait exprès
      mnd.profil.suivreNoms({}),
    ).to.be.rejectedWith("Aucun argument n'est une fonction");
  });

  it("Erreur suivi fonction n'est pas une fonction", async () => {
    await expect(
      // @ts-expect-error on fait exprès
      mnd.profil.suivreNoms({ f: 123 }),
    ).to.be.rejectedWith("Aucun argument n'est une fonction");
  });

  it("Erreur format paramètres", async () => {
    expect(() =>
      // @ts-expect-error on fait exprès
      mnd.profil.suivreNoms(faisRien),
    ).to.throw(
      "doit être appelée avec un seul argument en format d'objet (dictionnaire)",
    );
  });

  it("Erreur interne dans suivi", async () => {
    const fOublier = await mnd.profil.suivreNoms({
      f: faisRien,
      idCompte: "je ne suis pas un compte valide",
    });
    await expect(fOublier()).to.be.rejectedWith(
      "je ne suis pas un compte valide",
    );
  });

  it("Erreur interne dans action", async () => {
    await expect(
      mnd.bds.ajouterMotsClefsBd({
        idBd: "je ne suis pas une adresse bd valide",
        idsMotsClefs: [],
      }),
    ).to.be.rejectedWith("non valide");
  });
});
