import React from "react";
import { render, fireEvent, waitFor, act } from "@testing-library/react-native";

// Etats de mock partages avec les factories jest.mock (prefixe mock*)
const mockCameraState = {
  granted: true,
  requestResult: { granted: true } as { granted: boolean },
  takePicture: jest.fn(),
};
const mockFsState = {
  savePhotoToPermanentFile: jest.fn(),
};
const mockApi = {
  uploadWorkflowStepPhoto: jest.fn(),
  enqueueWorkflowStepPhoto: jest.fn(),
};

jest.mock("expo-camera", () => {
  const ReactLocal = require("react");
  const { View } = require("react-native");
  const CameraView = ReactLocal.forwardRef(function CameraView(
    props: unknown,
    ref: React.Ref<{ takePictureAsync: jest.Mock }>
  ) {
    ReactLocal.useImperativeHandle(ref, () => ({
      takePictureAsync: mockCameraState.takePicture,
    }));
    return ReactLocal.createElement(View, props);
  });
  return {
    CameraView,
    useCameraPermissions: () => [
      { granted: mockCameraState.granted },
      jest.fn().mockResolvedValue(mockCameraState.requestResult),
    ],
  };
});

jest.mock("../../src/core/fileStorage", () => ({
  savePhotoToPermanentFile: (...args: unknown[]) =>
    mockFsState.savePhotoToPermanentFile(...args),
}));

jest.mock("../../src/services/interventionsApi", () => ({
  uploadWorkflowStepPhoto: (...args: unknown[]) =>
    mockApi.uploadWorkflowStepPhoto(...args),
  enqueueWorkflowStepPhoto: (...args: unknown[]) =>
    mockApi.enqueueWorkflowStepPhoto(...args),
}));

jest.mock("../../src/shared/workflowUtils", () => ({
  uuidv4: () => "fixed-uuid",
}));

import CameraCapture from "../../src/shared/CameraCapture";

describe("CameraCapture", () => {
  beforeEach(() => {
    mockCameraState.granted = true;
    mockCameraState.requestResult = { granted: true };
    mockCameraState.takePicture.mockReset();
    mockFsState.savePhotoToPermanentFile.mockReset();
    mockApi.uploadWorkflowStepPhoto.mockReset();
    mockApi.enqueueWorkflowStepPhoto.mockReset();
  });

  const defaultProps = {
    interventionId: 1,
    stepId: "s1",
    workflowStepId: 10,
    brandColor: "#1E56A8",
    onCaptured: jest.fn(),
  };

  it("affiche le bouton 'Prendre une photo'", () => {
    const { getByText } = render(<CameraCapture {...defaultProps} onCaptured={jest.fn()} />);
    expect(getByText("Prendre une photo")).toBeTruthy();
  });

  it("ouvre le modal quand permission deja accordee", async () => {
    const { getByText } = render(<CameraCapture {...defaultProps} onCaptured={jest.fn()} />);
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    expect(getByText("Annuler")).toBeTruthy();
  });

  it("demande la permission si absente et ouvre si accordee", async () => {
    mockCameraState.granted = false;
    mockCameraState.requestResult = { granted: true };
    const { getByText } = render(<CameraCapture {...defaultProps} onCaptured={jest.fn()} />);
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    expect(getByText("Capturer")).toBeTruthy();
  });

  it("n'ouvre pas le modal si permission refusee", async () => {
    mockCameraState.granted = false;
    mockCameraState.requestResult = { granted: false };
    const { getByText, queryByText } = render(
      <CameraCapture {...defaultProps} onCaptured={jest.fn()} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    expect(queryByText("Capturer")).toBeNull();
  });

  it("capture + upload reussi -> appelle onCaptured et ferme", async () => {
    mockCameraState.takePicture.mockResolvedValue({ uri: "file:///tmp.jpg" });
    mockFsState.savePhotoToPermanentFile.mockResolvedValue("file:///perm.jpg");
    mockApi.uploadWorkflowStepPhoto.mockResolvedValue({ id: 1 });
    const onCaptured = jest.fn();
    const { getByText } = render(
      <CameraCapture {...defaultProps} onCaptured={onCaptured} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    await act(async () => {
      fireEvent.press(getByText("Capturer"));
    });
    await waitFor(() => expect(onCaptured).toHaveBeenCalledWith("file:///perm.jpg"));
    expect(mockApi.uploadWorkflowStepPhoto).toHaveBeenCalledWith(
      expect.objectContaining({
        workflowStepId: 10,
        photoUri: "file:///perm.jpg",
        mimeType: "image/jpeg",
        idempotencyKey: "fixed-uuid",
      })
    );
  });

  it("capture avec uri .png -> mime image/png", async () => {
    mockCameraState.takePicture.mockResolvedValue({ uri: "file:///t.jpg" });
    mockFsState.savePhotoToPermanentFile.mockResolvedValue("file:///perm.png");
    mockApi.uploadWorkflowStepPhoto.mockResolvedValue({ id: 1 });
    const { getByText } = render(
      <CameraCapture {...defaultProps} onCaptured={jest.fn()} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    await act(async () => {
      fireEvent.press(getByText("Capturer"));
    });
    await waitFor(() =>
      expect(mockApi.uploadWorkflowStepPhoto).toHaveBeenCalledWith(
        expect.objectContaining({ mimeType: "image/png" })
      )
    );
  });

  it("pas d'uri dans photo -> retourne sans upload", async () => {
    mockCameraState.takePicture.mockResolvedValue({ uri: null });
    const onCaptured = jest.fn();
    const { getByText } = render(
      <CameraCapture {...defaultProps} onCaptured={onCaptured} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    await act(async () => {
      fireEvent.press(getByText("Capturer"));
    });
    expect(mockFsState.savePhotoToPermanentFile).not.toHaveBeenCalled();
    expect(onCaptured).not.toHaveBeenCalled();
  });

  it("workflowStepId null -> message de warning local, pas d'upload", async () => {
    mockCameraState.takePicture.mockResolvedValue({ uri: "file:///t.jpg" });
    mockFsState.savePhotoToPermanentFile.mockResolvedValue("file:///perm.jpg");
    const { getByText, findByText } = render(
      <CameraCapture {...defaultProps} workflowStepId={null} onCaptured={jest.fn()} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    await act(async () => {
      fireEvent.press(getByText("Capturer"));
    });
    await findByText(/Workflow pas encore synchronise/);
    expect(mockApi.uploadWorkflowStepPhoto).not.toHaveBeenCalled();
  });

  it("upload echoue (offline) -> enqueue pour replay + message differe, meme idempotency_key", async () => {
    mockCameraState.takePicture.mockResolvedValue({ uri: "file:///t.jpg" });
    mockFsState.savePhotoToPermanentFile.mockResolvedValue("file:///perm.jpg");
    mockApi.uploadWorkflowStepPhoto.mockRejectedValue(new Error("Network error"));
    mockApi.enqueueWorkflowStepPhoto.mockResolvedValue(undefined);
    const onCaptured = jest.fn();
    const { getByText, findByText } = render(
      <CameraCapture {...defaultProps} onCaptured={onCaptured} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    await act(async () => {
      fireEvent.press(getByText("Capturer"));
    });
    await findByText(/synchronisee automatiquement/);
    // Le replay reutilise le MEME idempotency_key que la tentative immediate.
    expect(mockApi.enqueueWorkflowStepPhoto).toHaveBeenCalledWith(
      expect.objectContaining({
        workflowStepId: 10,
        photoUri: "file:///perm.jpg",
        mimeType: "image/jpeg",
        idempotencyKey: "fixed-uuid",
      })
    );
    expect(mockApi.uploadWorkflowStepPhoto).toHaveBeenCalledWith(
      expect.objectContaining({ idempotencyKey: "fixed-uuid" })
    );
    // La photo est quand meme affichee localement (preview offline).
    expect(onCaptured).toHaveBeenCalledWith("file:///perm.jpg");
  });

  it("upload echoue ET enqueue echoue -> message d'erreur d'origine", async () => {
    mockCameraState.takePicture.mockResolvedValue({ uri: "file:///t.jpg" });
    mockFsState.savePhotoToPermanentFile.mockResolvedValue("file:///perm.jpg");
    mockApi.uploadWorkflowStepPhoto.mockRejectedValue(new Error("boom"));
    mockApi.enqueueWorkflowStepPhoto.mockRejectedValue(new Error("sqlite ko"));
    const { getByText, findByText } = render(
      <CameraCapture {...defaultProps} onCaptured={jest.fn()} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    await act(async () => {
      fireEvent.press(getByText("Capturer"));
    });
    await findByText(/echec envoi: boom/);
  });

  it("fallback nom photo via Date.now quand split n'a pas de segment", async () => {
    mockCameraState.takePicture.mockResolvedValue({ uri: "file:///tmp.jpg" });
    mockFsState.savePhotoToPermanentFile.mockResolvedValue("/");
    mockApi.uploadWorkflowStepPhoto.mockResolvedValue({ id: 1 });
    jest.spyOn(Date, "now").mockReturnValue(42);
    const { getByText } = render(
      <CameraCapture {...defaultProps} onCaptured={jest.fn()} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    await act(async () => {
      fireEvent.press(getByText("Capturer"));
    });
    await waitFor(() =>
      expect(mockApi.uploadWorkflowStepPhoto).toHaveBeenCalledWith(
        expect.objectContaining({ photoName: "photo_42.jpg" })
      )
    );
  });

  it("Modal onRequestClose ferme", async () => {
    const { getByText, UNSAFE_getByType, queryByText } = render(
      <CameraCapture {...defaultProps} onCaptured={jest.fn()} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    const { Modal } = require("react-native");
    const modal = UNSAFE_getByType(Modal);
    await act(async () => {
      modal.props.onRequestClose();
    });
    expect(queryByText("Capturer")).toBeNull();
  });

  it("Annuler ferme le modal", async () => {
    const { getByText, queryByText } = render(
      <CameraCapture {...defaultProps} onCaptured={jest.fn()} />
    );
    await act(async () => {
      fireEvent.press(getByText("Prendre une photo"));
    });
    await act(async () => {
      fireEvent.press(getByText("Annuler"));
    });
    expect(queryByText("Capturer")).toBeNull();
  });
});
