import type { QueryClient } from '@tanstack/react-query';
import { api } from '@/api/client';
import { lireJeton } from '@/api/jeton';
import { API_URL } from '@/api/client';
import { lireFile, majSaisie, retirer, type Saisie } from './store';

/** Rejeu de la file (D1/D2) : dans l'ordre, une saisie à la fois.
 *  - succès → la saisie sort de la file ;
 *  - coupure réseau → tout reste EN_ATTENTE, on réessaiera ;
 *  - refus (409 verrou, garde métier) → la saisie passe en CONFLIT et la
 *    file S'ARRÊTE LÀ : l'humain tranche à l'écran Synchro. */

export type ResultatEnvoi =
  | { ok: true; nouvelleVersion?: string }
  | { ok: false; reseau: boolean; message: string };

export async function envoyerSaisie(s: Saisie): Promise<ResultatEnvoi> {
  try {
    let status: number;
    let message = '';
    let nouvelleVersion: string | undefined;
    if (s.type === 'TRANSITION') {
      const res = await api.POST('/work-orders/{id}/transition', {
        params: { path: { id: s.otId } },
        body: { to: s.payload.to, baseUpdatedAt: s.baseUpdatedAt },
      });
      status = res.response.status;
      message = messageDe(res.error);
      nouvelleVersion = res.data?.updatedAt;
    } else if (s.type === 'COCHE') {
      const res = await api.PATCH('/work-orders/{id}/checklist/{itemId}', {
        params: { path: { id: s.otId, itemId: s.payload.itemId } },
        body: { state: s.payload.state, baseUpdatedAt: s.baseUpdatedAt },
      });
      status = res.response.status;
      message = messageDe(res.error);
    } else if (s.type === 'BILAN') {
      const res = await api.PUT('/work-orders/{id}/report', {
        params: { path: { id: s.otId } },
        body: { ...s.payload, baseUpdatedAt: s.baseUpdatedAt },
      });
      status = res.response.status;
      message = messageDe(res.error);
      nouvelleVersion = res.data?.updatedAt;
    } else {
      // PHOTO — multipart hors client typé (D5) : fichier compressé en file.
      const form = new FormData();
      form.append('kind', 'PHOTO');
      form.append('workOrderId', s.otId);
      if (s.payload.uri.startsWith('http') || s.payload.uri.startsWith('blob:') || s.payload.uri.startsWith('data:')) {
        const blob = await (await fetch(s.payload.uri)).blob();
        form.append('file', new File([blob], s.payload.nom, { type: s.payload.mime }));
      } else {
        // URI de fichier natif : React Native sait téléverser {uri, name, type}
        form.append('file', {
          uri: s.payload.uri,
          name: s.payload.nom,
          type: s.payload.mime,
        } as unknown as Blob);
      }
      const jeton = await lireJeton();
      const res = await fetch(`${API_URL}/documents`, {
        method: 'POST',
        headers: jeton ? { Authorization: `Bearer ${jeton}` } : undefined,
        body: form,
      });
      status = res.status;
      if (!res.ok) {
        const corps = (await res.json().catch(() => null)) as { message?: string } | null;
        message = corps?.message ?? `Téléversement refusé (${res.status})`;
      }
    }
    if (status < 400) {
      // Coche/photo ne renvoient pas le détail : on relit la version pour
      // que les saisies suivantes du lot ne se heurtent pas à NOS écritures.
      if (!nouvelleVersion) {
        const frais = await api.GET('/work-orders/{id}', { params: { path: { id: s.otId } } });
        nouvelleVersion = frais.data?.updatedAt;
      }
      return { ok: true, nouvelleVersion };
    }
    return { ok: false, reseau: false, message: message || `Refus (${status})` };
  } catch {
    return { ok: false, reseau: true, message: 'Réseau indisponible' };
  }
}

function messageDe(erreur: unknown): string {
  return erreur && typeof erreur === 'object' && 'message' in erreur
    ? String((erreur as { message: unknown }).message)
    : '';
}

let enCours = false;

export async function rejouer(
  queryClient: QueryClient,
  envoyer: (s: Saisie) => Promise<ResultatEnvoi> = envoyerSaisie,
): Promise<void> {
  if (enCours) return;
  enCours = true;
  try {
    for (;;) {
      const suivante = lireFile().find((s) => s.statut === 'EN_ATTENTE');
      if (!suivante) break;
      // Un conflit plus ancien barre la route : l'ordre est la promesse D1.
      const conflitAvant = lireFile().some(
        (s) => s.statut === 'CONFLIT' && s.creeA <= suivante.creeA,
      );
      if (conflitAvant) break;

      majSaisie(suivante.id, { statut: 'ENVOI' });
      const resultat = await envoyer(suivante);
      if (resultat.ok) {
        retirer(suivante.id);
        // Notre propre écriture a fait avancer la version : les saisies
        // restantes du même OT repartent de là (un écart ÉTRANGER ultérieur
        // restera détecté par le verrou).
        if (resultat.nouvelleVersion) {
          for (const s of lireFile()) {
            if (s.otId === suivante.otId && s.statut === 'EN_ATTENTE') {
              majSaisie(s.id, { baseUpdatedAt: resultat.nouvelleVersion });
            }
          }
        }
        await queryClient.invalidateQueries({ queryKey: ['work-orders'] });
        await queryClient.invalidateQueries({ queryKey: ['documents'] });
      } else if (resultat.reseau) {
        majSaisie(suivante.id, { statut: 'EN_ATTENTE' });
        break; // le réseau reviendra — rien n'est perdu
      } else {
        majSaisie(suivante.id, { statut: 'CONFLIT', erreur: resultat.message });
        break; // l'humain tranche (D2)
      }
    }
  } finally {
    enCours = false;
  }
}

/** Résolutions du conflit (maquette écran 7). */
export async function rejouerSurVersionAJour(
  saisieId: string,
  queryClient: QueryClient,
): Promise<void> {
  const saisie = lireFile().find((s) => s.id === saisieId);
  if (!saisie) return;
  const frais = await api.GET('/work-orders/{id}', { params: { path: { id: saisie.otId } } });
  if (frais.data) {
    majSaisie(saisieId, { statut: 'EN_ATTENTE', baseUpdatedAt: frais.data.updatedAt, erreur: undefined });
    await rejouer(queryClient);
  }
}

export async function abandonnerSaisie(saisieId: string, queryClient: QueryClient): Promise<void> {
  retirer(saisieId);
  // On recharge la vérité serveur : le patch optimiste local est annulé.
  await queryClient.invalidateQueries({ queryKey: ['work-orders'] });
  await rejouer(queryClient);
}
