jest.mock("../../src/core/localDatabase", () => ({
  getStepsByInterventionId: jest.fn(),
  updateStepValueLocally: jest.fn(),
  enqueueSyncAction: jest.fn(),
}));

import * as localDatabase from "../../src/core/localDatabase";
import {
  allRequiredStepsDone,
  completeStepLocally,
  loadStepsLocally,
  validateStepValue,
} from "../../src/core/stepRepository";
import type { MobileStep } from "../../src/types/intervention";

const getStepsByInterventionId = localDatabase.getStepsByInterventionId as jest.Mock;
const updateStepValueLocally = localDatabase.updateStepValueLocally as jest.Mock;
const enqueueSyncAction = localDatabase.enqueueSyncAction as jest.Mock;

const baseStep = (over: Partial<MobileStep> = {}): MobileStep => ({
  id: "s",
  intervention_id: 1,
  order_index: 0,
  type: "text",
  label: "",
  required: false,
  status: "todo",
  value: null,
  version: 1,
  updated_at: "2026-01-01",
  ...over,
});

describe("stepRepository", () => {
  beforeEach(() => jest.clearAllMocks());

  describe("loadStepsLocally", () => {
    it("delegue a getStepsByInterventionId", async () => {
      getStepsByInterventionId.mockResolvedValue([baseStep()]);
      const res = await loadStepsLocally(5);
      expect(getStepsByInterventionId).toHaveBeenCalledWith(5);
      expect(res).toHaveLength(1);
    });
  });

  describe("validateStepValue — measurement", () => {
    it("valeur NaN -> erreur", () => {
      expect(validateStepValue(baseStep({ type: "measurement" }), "abc")).toBe(
        "Valeur numerique requise"
      );
    });

    it("valeur < min", () => {
      expect(
        validateStepValue(
          baseStep({ type: "measurement", constraints: { min: 5 } }),
          3
        )
      ).toBe("Minimum: 5");
    });

    it("valeur > max", () => {
      expect(
        validateStepValue(
          baseStep({ type: "measurement", constraints: { max: 10 } }),
          12
        )
      ).toBe("Maximum: 10");
    });

    it("valeur dans intervalle -> null", () => {
      expect(
        validateStepValue(
          baseStep({ type: "measurement", constraints: { min: 1, max: 10 } }),
          5
        )
      ).toBeNull();
    });

    it("sans constraints -> null", () => {
      expect(validateStepValue(baseStep({ type: "measurement" }), 42)).toBeNull();
    });
  });

  describe("validateStepValue — text", () => {
    it("trop long -> erreur", () => {
      expect(
        validateStepValue(
          baseStep({ type: "text", constraints: { max_length: 3 } }),
          "abcd"
        )
      ).toBe("Maximum 3 caracteres");
    });

    it("dans limite -> null", () => {
      expect(
        validateStepValue(
          baseStep({ type: "text", constraints: { max_length: 5 } }),
          "ab"
        )
      ).toBeNull();
    });

    it("value null stringifie en ''", () => {
      expect(
        validateStepValue(
          baseStep({ type: "text", constraints: { max_length: 3 } }),
          null
        )
      ).toBeNull();
    });

    it("sans max_length -> null", () => {
      expect(validateStepValue(baseStep({ type: "text" }), "long texte")).toBeNull();
    });
  });

  describe("validateStepValue — photo", () => {
    it("required + array vide -> erreur", () => {
      expect(validateStepValue(baseStep({ type: "photo", required: true }), [])).toBe(
        "Au moins une photo requise"
      );
    });

    it("required + non-array -> erreur", () => {
      expect(
        validateStepValue(baseStep({ type: "photo", required: true }), null)
      ).toBe("Au moins une photo requise");
    });

    it("required + photos -> null", () => {
      expect(
        validateStepValue(baseStep({ type: "photo", required: true }), [
          { id: "1", uri: "u" },
        ])
      ).toBeNull();
    });

    it("non required + rien -> null", () => {
      expect(validateStepValue(baseStep({ type: "photo", required: false }), null)).toBeNull();
    });
  });

  describe("validateStepValue — signature", () => {
    it("required + non signe -> erreur", () => {
      expect(
        validateStepValue(baseStep({ type: "signature", required: true }), {
          signed: false,
          signed_by: "technicien",
          uri: "x",
        })
      ).toBe("Signature requise");
    });

    it("required + pas d'uri -> erreur", () => {
      expect(
        validateStepValue(baseStep({ type: "signature", required: true }), {
          signed: true,
          signed_by: "technicien",
          uri: "",
        })
      ).toBe("Signature requise");
    });

    it("required + null -> erreur", () => {
      expect(validateStepValue(baseStep({ type: "signature", required: true }), null)).toBe(
        "Signature requise"
      );
    });

    it("required + signe -> null", () => {
      expect(
        validateStepValue(baseStep({ type: "signature", required: true }), {
          signed: true,
          signed_by: "technicien",
          uri: "u",
        })
      ).toBeNull();
    });

    it("non required + null -> null", () => {
      expect(validateStepValue(baseStep({ type: "signature", required: false }), null)).toBeNull();
    });
  });

  describe("validateStepValue — checklist (aucune regle)", () => {
    it("retourne null", () => {
      expect(validateStepValue(baseStep({ type: "checklist" }), "whatever")).toBeNull();
    });
  });

  describe("allRequiredStepsDone", () => {
    it("true si toutes les required sont done", () => {
      expect(
        allRequiredStepsDone([
          baseStep({ required: true, status: "done" }),
          baseStep({ required: false, status: "todo" }),
        ])
      ).toBe(true);
    });

    it("false si une required est todo", () => {
      expect(
        allRequiredStepsDone([
          baseStep({ required: true, status: "done" }),
          baseStep({ required: true, status: "todo" }),
        ])
      ).toBe(false);
    });

    it("true si aucun required (vacuously true)", () => {
      expect(
        allRequiredStepsDone([baseStep({ required: false, status: "todo" })])
      ).toBe(true);
    });
  });

  describe("completeStepLocally", () => {
    it("met a jour la base locale et retourne le step done, SANS enqueue", async () => {
      const step = baseStep({ type: "text", version: 2 });
      const res = await completeStepLocally(step, "nouvelle valeur");
      expect(updateStepValueLocally).toHaveBeenCalledWith(
        1,
        "s",
        expect.objectContaining({ status: "done", value: "nouvelle valeur", version: 3 })
      );
      expect(res.status).toBe("done");
      expect(res.version).toBe(3);
      // La valeur est syncee par persistSingleStep (save_step) ; l'ancien
      // enqueue vers /missions/.../complete (404 fantome) a ete retire.
      expect(enqueueSyncAction).not.toHaveBeenCalled();
    });

    it("photos/signatures : met a jour la valeur locale sans enqueue", async () => {
      const step = baseStep({ type: "photo" });
      const photos = [{ id: "p1", uri: "file:///a.jpg", name: "a.jpg" }];
      await completeStepLocally(step, photos);
      expect(updateStepValueLocally).toHaveBeenCalledWith(
        1,
        "s",
        expect.objectContaining({ status: "done", value: photos })
      );
      expect(enqueueSyncAction).not.toHaveBeenCalled();
    });

    it("version defaut 1 si absente", async () => {
      const step = baseStep({ type: "text" });
      delete (step as Partial<MobileStep>).version;
      const res = await completeStepLocally(step as MobileStep, "v");
      expect(res.version).toBe(2);
    });
  });
});
