import apiClient from "./apiClient";
import {
  applyServerStepsDelta,
  getInterventionDetailById,
  getInterventionsSnapshot,
  saveInterventionDetail,
  saveInterventionsSnapshot,
} from "../core/localDatabase";
import { requireEnvironmentBaseUrl } from "../core/environmentService";
import { readSession } from "./tokenStorage";
import { enqueueOfflineAction } from "./syncService";
import type { ApiEnvelope } from "../types/auth";
import type {
  InterventionsPayload,
  MobileInterventionDetails,
  MobilePrecheckPayload,
  MobilePrecheckState,
  MobileStep,
} from "../types/intervention";

type InterventionsQuery = {
  date_debut?: string;
  date_fin?: string;
};

export async function getMyInterventions(
  query: InterventionsQuery = {}
): Promise<InterventionsPayload> {
  try {
    const response = await apiClient.get<ApiEnvelope<InterventionsPayload>>(
      "/mobile/interventions.php",
      {
        params: query,
      }
    );

    if (!response.data?.success || !response.data.data) {
      throw new Error(response.data?.message || "Unable to fetch interventions");
    }

    const payload = response.data.data;
    const items = Array.isArray(payload.items) ? payload.items : [];
    await saveInterventionsSnapshot(items);

    return {
      items,
      count: Number.isFinite(payload.count)
        ? Number(payload.count)
        : items.length,
      technicien_id: Number(payload.technicien_id || 0),
      date_debut: String(payload.date_debut || ""),
      date_fin: String(payload.date_fin || ""),
    };
  } catch (error) {
    const local = await getInterventionsSnapshot();
    if (local.items.length > 0) {
      return {
        items: local.items,
        count: local.count,
        technicien_id: 0,
        date_debut: "",
        date_fin: "",
      };
    }
    throw error;
  }
}

export async function getInterventionById(
  id: number
): Promise<MobileInterventionDetails> {
  try {
    const response = await apiClient.get<
      ApiEnvelope<{ item: MobileInterventionDetails }>
    >("/mobile/intervention.php", {
      params: { id },
    });

    if (!response.data?.success || !response.data.data?.item) {
      throw new Error(response.data?.message || "Unable to fetch intervention");
    }

    const item = response.data.data.item;
    await saveInterventionDetail(item);

    // Prefetch des steps dans la foulee : le WorkflowInterventionScreen
    // lit depuis la base locale, donc il faut que les steps soient synces
    // ici sinon l'ecran affiche "aucune etape disponible". On n'echoue pas
    // le detail si le fetch steps plante (l'utilisateur verra la mission
    // sans les etapes, ce qui est mieux que rien).
    try {
      await fetchAndStoreInterventionSteps(id);
    } catch {
      // best-effort: l'utilisateur peut re-ouvrir l'ecran plus tard
    }

    return item;
  } catch (error) {
    const local = await getInterventionDetailById(id);
    if (local) {
      return local.item;
    }
    throw error;
  }
}

/**
 * Fetch les etapes workflow d'une intervention depuis le backend et les
 * persiste en base locale. Peut etre appelee ponctuellement (apres
 * getInterventionById, apres start, ou via pull-to-refresh).
 *
 * Retourne aussi la version + status du workflow : la version est requise
 * pour appeler complete_workflow (verrou optimiste serveur).
 */
export async function fetchAndStoreInterventionSteps(
  interventionId: number
): Promise<{ count: number; workflowVersion: number; workflowStatus: string }> {
  const response = await apiClient.get<
    ApiEnvelope<{
      items: MobileStep[];
      workflow?: { version?: number; status?: string };
    }>
  >("/mobile/intervention_steps.php", {
    params: { id: interventionId },
  });

  if (!response.data?.success || !response.data.data?.items) {
    return { count: 0, workflowVersion: 0, workflowStatus: "unknown" };
  }

  const steps = response.data.data.items;
  const wf = response.data.data.workflow ?? {};
  const count = steps.length > 0 ? await applyServerStepsDelta(steps) : 0;

  return {
    count,
    workflowVersion: Number(wf.version ?? 0),
    workflowStatus: String(wf.status ?? "in_progress"),
  };
}

/**
 * Precharge les workflows d'une liste d'interventions, pour garantir le mode
 * hors-ligne. Deux choses manquent au cache tant qu'une intervention n'a pas
 * ete ouverte au moins une fois EN LIGNE :
 *   1. le DETAIL (table `interventions`) : la liste (getMyInterventions) ne
 *      stocke qu'un resume ; MissionDetailScreen lit le detail complet via
 *      getInterventionById -> sans cache, l'ouverture offline echoue avec une
 *      erreur reseau ("Erreur chargement intervention").
 *   2. les ETAPES (table `steps`) : elles ne sont PAS dans la liste et, cote
 *      serveur, sont materialisees paresseusement a la 1ere lecture
 *      (getWorkflowState -> createWorkflow). Sans prechargement, le tech voit
 *      "aucune etape".
 *
 * On passe donc par getInterventionById : il persiste le detail ET enchaine
 * fetchAndStoreInterventionSteps dans la foulee -> les deux caches sont
 * remplis en une seule passe.
 *
 * Appelee au login et au boot (online). Best-effort et sequentiel : une
 * intervention qui echoue (ou un passage offline en cours de boucle)
 * n'interrompt pas les autres. Retourne le nombre d'interventions prechargees.
 */
export async function prefetchWorkflows(ids: number[]): Promise<number> {
  let prefetched = 0;
  for (const id of ids) {
    try {
      // getInterventionById echoue (throw) si offline ET pas de cache local ;
      // le catch ci-dessous absorbe et on continue avec les suivantes.
      await getInterventionById(id);
      prefetched += 1;
    } catch {
      // best-effort : reseau coupe en cours de boucle / intervention sans
      // workflow -> on continue avec les suivantes.
    }
  }
  return prefetched;
}

/**
 * Upload d'une photo pour une etape workflow. Le backend
 * /mobile/intervention_workflow_files.php attend un multipart/form-data :
 *   - workflow_step_id (INT, l'id DB de la step, pas le step_key UUID)
 *   - idempotency_key (UUID)
 *   - file (le binaire image)
 *
 * Retourne les infos du fichier cree (dont l'id serveur).
 */
export async function uploadWorkflowStepPhoto(args: {
  workflowStepId: number;
  photoUri: string;
  photoName: string;
  mimeType: string;
  idempotencyKey: string;
}): Promise<{ id?: number } | null> {
  const baseUrl = await requireEnvironmentBaseUrl();
  const session = await readSession();
  if (!session.accessToken) {
    throw new Error("Session expiree : reconnexion requise");
  }

  const form = new FormData();
  form.append("workflow_step_id", String(args.workflowStepId));
  form.append("idempotency_key", args.idempotencyKey);
  // React Native FormData : pour un fichier local, on passe un objet
  // { uri, name, type }. Pas de Blob / File en RN.
  form.append("file", {
    uri: args.photoUri,
    name: args.photoName,
    type: args.mimeType,
  } as unknown as Blob);

  const response = await fetch(`${baseUrl}/mobile/intervention_workflow_files.php`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      // PAS de Content-Type : fetch met automatiquement le multipart boundary.
    },
    body: form as unknown as BodyInit,
  });

  let payload: { success?: boolean; message?: string; data?: { id?: number } } | null = null;
  try {
    payload = await response.json();
  } catch {
    payload = null;
  }

  if (!response.ok || !payload?.success) {
    throw new Error(payload?.message || `Upload photo echoue (HTTP ${response.status})`);
  }

  return payload.data ?? null;
}

/**
 * Met une photo d'etape en file de synchronisation hors-ligne (replay au
 * reconnect). Appelee quand l'upload immediat echoue (reseau coupe) : sans ca
 * la photo restait un fichier local jamais rejoue, et la finalisation echouait
 * ("Nombre de photos insuffisant", activeFileCount=0 cote DB).
 *
 * Le payload suit le format multipart de la sync_queue (syncService :
 * isMultipartQueuePayload / postMultipartFromQueue) : `__multipart`,
 * `upload_files` (le binaire, field "file"), et les champs texte
 * (workflow_step_id, idempotency_key) appliques tels quels en form-data.
 *
 * IMPORTANT : l'idempotency_key DOIT etre le MEME que celui de la tentative
 * immediate. Si l'upload immediat avait en fait atteint le serveur mais que la
 * reponse s'est perdue (timeout), le replay sera dedoublonne cote backend
 * (workflow_idempotency_keys, TTL 24h) au lieu de creer un doublon.
 */
export async function enqueueWorkflowStepPhoto(args: {
  workflowStepId: number;
  photoUri: string;
  photoName: string;
  mimeType: string;
  idempotencyKey: string;
}): Promise<void> {
  await enqueueOfflineAction({
    endpoint: "/mobile/intervention_workflow_files.php",
    method: "POST",
    payload: {
      __multipart: true,
      workflow_step_id: args.workflowStepId,
      idempotency_key: args.idempotencyKey,
      upload_files: [
        {
          field: "file",
          uri: args.photoUri,
          name: args.photoName,
          type: args.mimeType,
        },
      ],
    },
    entityType: "workflow_photo",
    entityId: String(args.workflowStepId),
  });
}

/**
 * Sauvegarde une etape via action save_step sur le workflow endpoint.
 * Cote serveur, InterventionWorkflowService::saveStep persiste value +
 * comment dans intervention_workflow_steps + recompute la visibilite +
 * applique les calculs automatiques. Retourne le workflow state a jour
 * (version incrementee).
 *
 * Le idempotency_key permet aux tentatives multiples (retry offline) de
 * ne pas dupliquer.
 */
export async function saveStepToServer(args: {
  interventionId: number;
  stepKey: string;
  value: unknown;
  comment: string | null;
  version: number;
  idempotencyKey: string;
}): Promise<{ version: number; status: string }> {
  const response = await apiClient.post<
    ApiEnvelope<{
      workflow?: { version?: number; status?: string };
    }>
  >("/mobile/intervention_workflow.php", {
    action: "save_step",
    intervention_id: args.interventionId,
    step_key: args.stepKey,
    value: args.value,
    comment: args.comment,
    version: args.version,
    idempotency_key: args.idempotencyKey,
  });

  if (!response.data?.success) {
    throw new Error(response.data?.message || "Echec save_step");
  }

  const wf = response.data.data?.workflow ?? {};
  return {
    version: Number(wf.version ?? args.version),
    status: String(wf.status ?? "in_progress"),
  };
}

/**
 * Finalise une intervention via action complete_workflow. Cote serveur,
 * InterventionWorkflowService::completeWorkflow se charge de :
 *   - Marquer l'intervention comme terminee (statut + date_fin)
 *   - Stocker les signatures
 *   - Generer automatiquement le rapport PDF
 *   - Creer une demande de devis si problemes/pannes signales
 *   - Creer les prelevements huile si renseignes
 *
 * Donc cote mobile, un seul appel suffit pour declencher tout le pipeline.
 */
/**
 * Upload une photo de la machine liee a l'intervention.
 * La photo est sauvegardee en base64 dans machines.photos (backend)
 * et sera reutilisee sur tous les futurs rapports de cette machine.
 */
export async function uploadMachinePhoto(
  interventionId: number,
  photoBase64: string
): Promise<boolean> {
  const response = await apiClient.post<ApiEnvelope<null>>(
    "/mobile/intervention_workflow.php",
    {
      action: "upload_machine_photo",
      intervention_id: interventionId,
      photo: photoBase64,
    }
  );
  return response.data?.success === true;
}

export async function completeMobileWorkflow(
  interventionId: number,
  workflowVersion: number,
  signatures: {
    signature_technicien: string;
    signature_client: string;
    client_present: boolean;
  }
): Promise<void> {
  const response = await apiClient.post<
    ApiEnvelope<unknown> & {
      errors?: Array<{ step_key?: string; item?: string; errors?: string[] }>;
    }
  >("/mobile/intervention_workflow.php", {
    action: "complete_workflow",
    intervention_id: interventionId,
    version: workflowVersion,
    signature_technicien: signatures.signature_technicien,
    signature_client: signatures.signature_client,
    client_present: signatures.client_present,
  });

  if (!response.data?.success) {
    const baseMsg = response.data?.message || "Echec de la finalisation";
    // Le serveur peut inclure 'errors' : tableau d'etapes invalides avec
    // leur item + messages. On les remonte au user pour qu'il sache
    // quelles etapes completer.
    const errs = (
      response.data as {
        errors?: Array<{ item?: string; step_key?: string; errors?: string[] }>;
      } | undefined
    )?.errors;
    if (Array.isArray(errs) && errs.length > 0) {
      const detail = errs
        .slice(0, 5)
        .map((e) => {
          const label = e.item ?? e.step_key ?? "?";
          const msgs = Array.isArray(e.errors) ? e.errors.join(", ") : "invalide";
          return `• ${label}: ${msgs}`;
        })
        .join("\n");
      const extra = errs.length > 5 ? `\n... et ${errs.length - 5} autre(s)` : "";
      throw new Error(`${baseMsg}\n${detail}${extra}`);
    }
    throw new Error(baseMsg);
  }
}

export async function getInterventionPrecheck(
  interventionId: number
): Promise<MobilePrecheckState> {
  const response = await apiClient.get<ApiEnvelope<MobilePrecheckState>>(
    "/mobile/intervention_precheck.php",
    {
      params: { id: interventionId },
    }
  );

  if (!response.data?.success || !response.data.data) {
    throw new Error(response.data?.message || "Unable to fetch precheck");
  }

  return response.data.data;
}

async function postPrecheckOrEnqueue(
  endpoint: string,
  entityType: string,
  errorMsg: string,
  payload: MobilePrecheckPayload
): Promise<MobilePrecheckState> {
  try {
    const response = await apiClient.post<ApiEnvelope<MobilePrecheckState>>(endpoint, payload);
    if (!response.data?.success || !response.data.data) {
      throw new Error(response.data?.message || errorMsg);
    }
    return response.data.data;
  } catch (error) {
    await enqueueOfflineAction({
      endpoint,
      method: "POST",
      payload: payload as unknown as Record<string, unknown>,
      entityType,
      entityId: String(payload.intervention_id),
    });
    throw error;
  }
}

export async function saveInterventionPrecheck(
  payload: MobilePrecheckPayload
): Promise<MobilePrecheckState> {
  return postPrecheckOrEnqueue(
    "/mobile/intervention_precheck.php",
    "precheck",
    "Unable to save precheck",
    payload
  );
}

export async function startIntervention(
  payload: MobilePrecheckPayload
): Promise<MobilePrecheckState> {
  return postPrecheckOrEnqueue(
    "/mobile/intervention_start.php",
    "intervention_start",
    "Unable to start intervention",
    payload
  );
}
