jest.mock("../../services/vehiclesAPI");

import {
  render,
  cleanup,
  screen,
  fireEvent,
  waitFor,
} from "@testing-library/react";
import { VehicleProvider, useVehicleContext } from "./VehicleContext";
import { StatusProvider, useStatusContext } from "./StatusContext";

const checkVehicleResult = (error, busy, vehicle) => {
  checkBaseResults(error, busy);
  expect(screen.getByTestId("vehicle").innerHTML).toEqual(vehicle);
};

const checkVehiclesResult = (error, busy, vehicles) => {
  checkBaseResults(error, busy);
  expect(screen.getByTestId("vehicles").innerHTML).toEqual(vehicles);
};

const checkFleetsResult = (error, busy, fleets) => {
  checkBaseResults(error, busy);
  expect(screen.getByTestId("fleets").innerHTML).toEqual(fleets);
}

const checkBaseResults = (error, busy) => {
  expect(screen.getByTestId("error").innerHTML).toEqual(error);
  expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
};

describe("VehicleContext", () => {
  describe("getFleets", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { busy, error, fleets, getFleets } = useVehicleContext();

        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <div data-testid="fleets">{JSON.stringify(fleets)}</div>
            <button
              data-testid="getFleets"
              onClick={() => getFleets("3C4PDCBG0ET127145")}
            />
          </>
        );
      };
      render(
        <VehicleProvider>
          <TestComp />
        </VehicleProvider>
      );
    });
    afterEach(() => {
      cleanup();
    });

    it("Initial state", () => {
      checkFleetsResult("", "false", "[]");
    });

    it("getFleets", async () => {
      fireEvent.click(screen.getByTestId("getFleets"));
      await waitFor(() =>
        expect(screen.getByTestId("fleets").innerHTML).not.toBe("[]")
      );
      checkFleetsResult("", "false", JSON.stringify(["fleet1", "fleet2"]));
    });
  })

  describe("getVehicles", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { busy, error, vehicles, getVehicles } = useVehicleContext();

        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <div data-testid="vehicles">{JSON.stringify(vehicles)}</div>
            <button
              data-testid="getVehicles"
              onClick={() => getVehicles(null)}
            />
          </>
        );
      };
      render(
        <VehicleProvider>
          <TestComp />
        </VehicleProvider>
      );
    });

    afterEach(() => {
      cleanup();
    });

    it("Initial state", () => {
      checkVehiclesResult("", "false", "[]");
    });

    it("getVehicles", async () => {
      fireEvent.click(screen.getByTestId("getVehicles"));
      await waitFor(() =>
        expect(screen.getByTestId("vehicles").innerHTML).not.toBe("[]")
      );
      checkVehiclesResult("", "false", JSON.stringify(expectedVehiclesData));
    });
  });

  describe("getVehicle", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { busy, error, vehicle, getVehicle } = useVehicleContext();

        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <div data-testid="vehicle">{JSON.stringify(vehicle)}</div>
            <button
              data-testid="getVehicle"
              onClick={() => getVehicle("3C4PDCBG0ET127145")}
            />
          </>
        );
      };
      render(
        <VehicleProvider>
          <TestComp />
        </VehicleProvider>
      );
    });

    afterEach(() => {
      cleanup();
    });

    it("Initial state", () => {
      checkVehicleResult("", "false", "{}");
    });

    it("getVehicle", async () => {
      fireEvent.click(screen.getByTestId("getVehicle"));
      await waitFor(() =>
        expect(screen.getByTestId("vehicle").innerHTML).not.toBe("{}")
      );
      checkVehicleResult("", "false", JSON.stringify(expectedVehicleData));
    });
  });

  describe("AddVehicles", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, addVehicle } = useVehicleContext();
        const { message, setMessage } = useStatusContext();
        const add = async (data) => {
          try {
            await addVehicle(data);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button data-testid="addVehiclesNull" onClick={() => add(null)} />
            <button data-testid="addVehiclesNoVIN" onClick={() => add({})} />
            <button
              data-testid="addVehicles"
              onClick={() =>
                add({ vin: "XXXXXXXXXXX", model: "Ocean", year: 3000 })
              }
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <VehicleProvider>
            <TestComp />
          </VehicleProvider>
        </StatusProvider>
      );
    });

    afterEach(() => {
      cleanup();
    });

    it("Initial state", () => {
      checkBaseResults("", "false");
    });

    it("addVehiclesNull", async () => {
      fireEvent.click(screen.getByTestId("addVehiclesNull"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("No vehicle data", "false");
    });

    it("addVehiclesNoVIN", async () => {
      fireEvent.click(screen.getByTestId("addVehiclesNoVIN"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("VIN required", "false");
    });

    it("addVehicles", async () => {
      fireEvent.click(screen.getByTestId("addVehicles"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("", "false");
    });
  });

  describe("updateVehicle", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, updateVehicle } = useVehicleContext();
        const { message, setMessage } = useStatusContext();
        const update = async (data) => {
          try {
            await updateVehicle("3C4PDCBG0ET127145", data);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="updateVehicleNull"
              onClick={() => update(null)}
            />
            <button
              data-testid="updateVehicleNoVIN"
              onClick={() => update({})}
            />
            <button
              data-testid="updateVehicle"
              onClick={() =>
                update({
                  vin: "3C4PDCBG0ET127145",
                  log_level: "warning",
                  canbus: { enabled: false },
                })
              }
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <VehicleProvider>
            <TestComp />
          </VehicleProvider>
        </StatusProvider>
      );
    });

    afterEach(() => {
      cleanup();
    });

    it("initial state", () => {
      checkBaseResults("", "false");
    });

    it("updateVehicleNull", async () => {
      fireEvent.click(screen.getByTestId("updateVehicleNull"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("No vehicle data", "false");
    });

    it("updateVehicleNoVIN", async () => {
      fireEvent.click(screen.getByTestId("updateVehicleNoVIN"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("Invalid VIN", "false");
    });

    it("updateVehicle", async () => {
      fireEvent.click(screen.getByTestId("updateVehicle"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("", "false");
    });
  });

  describe("deleteVehicle", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, deleteVehicle } = useVehicleContext();
        const { message, setMessage } = useStatusContext();
        const deleteV = async (name) => {
          try {
            await deleteVehicle(name);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="deleteVehicleNull"
              onClick={() => deleteV(null)}
            />
            <button
              data-testid="deleteVehicleNonexistent"
              onClick={() => deleteV("11111111111111111")}
            />
            <button
              data-testid="deleteVehicle"
              onClick={() => deleteV("3C4PDCBG0ET127145")}
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <VehicleProvider>
            <TestComp />
          </VehicleProvider>
        </StatusProvider>
      );
    });

    afterEach(() => {
      cleanup();
    });

    it("initial state", () => {
      checkBaseResults("", "false");
    });

    it("deleteVehicleNull", async () => {
      fireEvent.click(screen.getByTestId("deleteVehicleNull"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("Invalid VIN", "false");
    });

    it("deleteVehicleNonexistent", async () => {
      fireEvent.click(screen.getByTestId("deleteVehicleNonexistent"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("", "false");
    });

    it("deleteVehicle", async () => {
      fireEvent.click(screen.getByTestId("deleteVehicle"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults("", "false");
    });
  });

  describe("sendCommand", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, sendCommand } = useVehicleContext();
        const { message, setMessage } = useStatusContext();

        const sendC = async (vin, command) => {
          try {
            await sendCommand(vin, command);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="sendCommandNullVin"
              onClick={() => sendC(null, { command: "doors_lock" })}
            />
            <button
              data-testid="sendCommandNonexistentVin"
              onClick={() =>
                sendC(["11111111111111111"], { command: "doors_lock" })
              }
            />
            <button
              data-testid="sendCommandVin"
              onClick={() =>
                sendC("3C4PDCBG0ET127145", { command: "doors_lock" })
              }
            />
            <button
              data-testid="sendCommandWrongCommand"
              onClick={() =>
                sendC("3C4PDCBG0ET127145", { command: "noSuchCommand" })
              }
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <VehicleProvider>
            <TestComp />
          </VehicleProvider>
        </StatusProvider>
      );
    });

    afterEach(() => {
      cleanup();
    });

    it("initial state", () => {
      checkBaseResults("", "false");
    });
  });
});

const expectedFilters = [
  {
    can_id: "123-456",
    interval: 789,
  },
  {
    can_id: "1",
    interval: 1000,
  },
  {
    can_id: "1000",
    interval: 1,
  },
];

const expectedVehicleData = {
  vin: "3C4PDCBG0ET127145",
  year: 2021,
  model: "Ocean",
  trim: "Basic",
  ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
  log_level: "info",
  canbus: {
    enabled: true,
    data_logger_enabled: true,
    max_mem_buffer_size: 1,
    max_disk_buffer_size: 2,
    filters: expectedFilters,
  },
  connected: true,
  connectedHMI: false,
};

const expectedVehiclesData = [
  {
    vin: "3C4PDCBG0ET127145",
    year: 2021,
    model: "Ocean",
    trim: "Basic",
    ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
    log_level: "info",
    canbus: {
      enabled: true,
      data_logger_enabled: true,
      max_mem_buffer_size: 1,
      max_disk_buffer_size: 2,
      filters: expectedFilters,
    },
    connected: true,
    connectedHMI: false,
  },
  { vin: "1G1FP87S3GN100062", connected: true, connectedHMI: false },
  {
    vin: "1HGCG325XYA062256",
    year: 2021,
    connected: true,
    connectedHMI: false,
  },
  {
    vin: "1J4GZ78YXWC160024",
    year: 2021,
    model: "Ocean",
    connected: true,
    connectedHMI: false,
  },
  {
    vin: "2C3CCAAG8CH222800",
    model: "Ocean",
    trim: "Basic",
    connected: true,
    connectedHMI: false,
  },
  {
    vin: "KNADM4A39C6028108",
    year: 2021,
    model: "Ocean",
    trim: "Basic",
    connected: true,
    connectedHMI: false,
  },
  {
    vin: "1G11C5SL9FF153507",
    year: 2021,
    model: "Ocean",
    trim: "Basic",
    connected: true,
    connectedHMI: false,
  },
];
