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

import {
  render,
  cleanup,
  screen,
  fireEvent,
  waitFor,
} from "@testing-library/react";
import { FleetProvider, useFleetContext } from "./FleetContext";
import { StatusProvider, useStatusContext } from "./StatusContext";

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

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

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

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

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

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

        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()} />
          </>
        );
      };
      render(
        <FleetProvider>
          <TestComp />
        </FleetProvider>
      );
    });

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

    it("initial state", () => {
      checkFleetsResults("", "false", "[]");
    });

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

  describe("getFleet", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { busy, error, fleet, getFleet } = useFleetContext();

        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <div data-testid="fleet">{JSON.stringify(fleet)}</div>
            <button
              data-testid="getFleet"
              onClick={() => getFleet("US-WEST")}
            />
          </>
        );
      };
      render(
        <FleetProvider>
          <TestComp />
        </FleetProvider>
      );
    });

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

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

    it("getFleet", async () => {
      fireEvent.click(screen.getByTestId("getFleet"));
      await waitFor(() =>
        expect(screen.getByTestId("fleet").innerHTML).not.toBe("{}")
      );
      checkFleetResults("", "false", JSON.stringify(expectedFleetData));
    });
  });

  describe("addFleet", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, addFleet } = useFleetContext();
        const { message, setMessage } = useStatusContext();
        const add = async (fleet) => {
          try {
            await addFleet(fleet);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button data-testid="addFleetNull" onClick={() => add(null)} />
            <button data-testid="addFleetNoName" onClick={() => add({})} />
            <button
              data-testid="addFleet"
              onClick={() =>
                add({
                  name: "EU-WEST",
                  log_level: "warning",
                  canbus: { enabled: false },
                })
              }
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <FleetProvider>
            <TestComp />
          </FleetProvider>
        </StatusProvider>
      );
    });

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

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

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

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

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

  describe("updateFleet", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, updateFleet } = useFleetContext();
        const { message, setMessage } = useStatusContext();
        const update = async (data) => {
          try {
            await updateFleet("EU-WEST", data);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="updateFleetNull"
              onClick={() => update(null)}
            />
            <button
              data-testid="updateFleetNoName"
              onClick={() => update({})}
            />
            <button
              data-testid="updateFleet"
              onClick={() =>
                update({
                  name: "EU-WEST",
                  log_level: "warning",
                  canbus: { enabled: false },
                })
              }
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <FleetProvider>
            <TestComp />
          </FleetProvider>
        </StatusProvider>
      );
    });

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

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

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

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

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

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

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="deleteFleetNull"
              onClick={() => deleteF(null)}
            />
            <button
              data-testid="deleteFleetNonexistent"
              onClick={() => deleteF("INVALID")}
            />
            <button
              data-testid="deleteFleet"
              onClick={() => deleteF("US-WEST")}
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <FleetProvider>
            <TestComp />
          </FleetProvider>
        </StatusProvider>
      );
    });

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

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

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

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

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

  describe("getFleetVehicles", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { busy, error, fleetVehicles, getFleetVehicles } =
          useFleetContext();

        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <div data-testid="fleet-vehicles">
              {JSON.stringify(fleetVehicles)}
            </div>
            <button
              data-testid="getFleetVehicles"
              onClick={() => getFleetVehicles("US-WEST")}
            />
          </>
        );
      };
      render(
        <FleetProvider>
          <TestComp />
        </FleetProvider>
      );
    });

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

    it("initial state", () => {
      checkFleetVehicleResults("", "false", "[]");
    });

    it("getFleetVehicles", async () => {
      fireEvent.click(screen.getByTestId("getFleetVehicles"));
      await waitFor(() =>
        expect(screen.getByTestId("fleet-vehicles").innerHTML).not.toBe("[]")
      );
      checkFleetVehicleResults(
        "",
        "false",
        JSON.stringify(expectedFleetVehiclesData)
      );
    });
  });

  describe("addFleetVehicles", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, addFleetVehicles } = useFleetContext();
        const { message, setMessage } = useStatusContext();
        const add = async (name, vehicle) => {
          try {
            await addFleetVehicles(name, vehicle);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="addFleetVehicleNull"
              onClick={() => add(null)}
            />
            <button
              data-testid="addFleetVehicleNoName"
              onClick={() => add({})}
            />
            <button
              data-testid="addFleetVehicles"
              onClick={() => add("US-TEST", { vins: ["TESTVIN1234567890"] })}
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <FleetProvider>
            <TestComp />
          </FleetProvider>
        </StatusProvider>
      );
    });

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

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

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

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

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

  describe("deleteFleetVehicle", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, deleteFleetVehicle } = useFleetContext();
        const { message, setMessage } = useStatusContext();
        const deleteFV = async (name, vehicle) => {
          try {
            await deleteFleetVehicle(name, vehicle);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="deleteFleetVehicleNull"
              onClick={() => deleteFV("US-WEST", null)}
            />
            <button
              data-testid="deleteFleetVehicleInvalid"
              onClick={() => deleteFV("US-WEST", "INVALID")}
            />
            <button
              data-testid="deleteFleetVehicle"
              onClick={() => deleteFV("US-WEST", { vin: "USWESTVIN12345678" })}
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <FleetProvider>
            <TestComp />
          </FleetProvider>
        </StatusProvider>
      );
    });

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

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

    it("deleteFleetVehicleNull", async () => {
      fireEvent.click(screen.getByTestId("deleteFleetVehicleNull"));
      await waitFor(() =>
        expect(screen.getByTestId("busy").innerHTML).toEqual("false")
      );
      checkBaseResults(
        "Cannot read properties of null (reading 'vin')",
        "false"
      );
    });

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

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

  describe("getFleetCANFilters", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { busy, error, fleetCANFilters, getFleetCANFilters } =
          useFleetContext();

        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <div data-testid="fleet-filters">
              {JSON.stringify(fleetCANFilters)}
            </div>
            <button
              data-testid="getFleetCANFilters"
              onClick={() => getFleetCANFilters("US-TEST")}
            />
          </>
        );
      };
      render(
        <FleetProvider>
          <TestComp />
        </FleetProvider>
      );
    });

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

    it("initial state", () => {
      checkFleetCANFilterResults("", "false", "[]");
    });

    it("getFleetCANFilters", async () => {
      fireEvent.click(screen.getByTestId("getFleetCANFilters"));
      await waitFor(() =>
        expect(screen.getByTestId("fleet-filters").innerHTML).not.toBe("[]")
      );
      checkFleetCANFilterResults(
        "",
        "false",
        JSON.stringify(expectedFleetCANFiltersData)
      );
    });
  });

  describe("addFleetCANFilter", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, addFleetCANFilter } = useFleetContext();
        const { message, setMessage } = useStatusContext();
        const add = async (name, filter) => {
          try {
            await addFleetCANFilter(name, filter);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="addFleetCANFilterNull"
              onClick={() => add(null)}
            />
            <button
              data-testid="addFleetCANFilterNoName"
              onClick={() => add({})}
            />
            <button
              data-testid="addFleetCANFilter"
              onClick={() => add("US-TEST", { can_id: "111", interval: 222 })}
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <FleetProvider>
            <TestComp />
          </FleetProvider>
        </StatusProvider>
      );
    });

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

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

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

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

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

  describe("deleteFleetCANFilter", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { busy, deleteFleetCANFilter } = useFleetContext();
        const { message, setMessage } = useStatusContext();
        const deleteFF = async (name, can_id) => {
          try {
            await deleteFleetCANFilter(name, can_id);
          } catch (e) {
            setMessage(e.message);
          }
        };

        return (
          <>
            <div data-testid="error">{message}</div>
            <div data-testid="busy">{busy.toString()}</div>
            <button
              data-testid="deleteFleetCANFilterNull"
              onClick={() => deleteFF("US-WEST", null)}
            />
            <button
              data-testid="deleteFleetCANFilterInvalid"
              onClick={() => deleteFF("US-WEST", "INVALID")}
            />
            <button
              data-testid="deleteFleetCANFilter"
              onClick={() => deleteFF("US-WEST", "123-456")}
            />
          </>
        );
      };
      render(
        <StatusProvider>
          <FleetProvider>
            <TestComp />
          </FleetProvider>
        </StatusProvider>
      );
    });

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

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

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

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

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

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

const expectedFleetData = {
  name: "US-WEST",
  log_level: "info",
  canbus: {
    enabled: true,
    data_logger_enabled: true,
    max_mem_buffer_size: 1,
    max_disk_buffer_size: 2,
    filters: expectedFilters,
  },
  vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"],
};

const expectedFleetsData = [
  {
    name: "US-WEST",
    log_level: "info",
    canbus: {
      enabled: true,
      data_logger_enabled: true,
      max_mem_buffer_size: 1,
      max_disk_buffer_size: 2,
      filters: expectedFilters,
    },
    vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"],
  },
  {
    name: "US-CENTRAL",
    log_level: "warning",
    canbus: {
      enabled: false,
      data_logger_enabled: false,
      max_mem_buffer_size: 0,
      max_disk_buffer_size: 0,
    },
    vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"],
  },
  {
    name: "US-EAST",
    log_level: "error",
    canbus: { enabled: true },
    vehicles: ["USEASTVIN12345678", "USEASTVIN12345679", "USEASTVIN12345670"],
  },
];

const expectedFleetVehiclesData = [
  {
    vin: "USWESTVIN12345678",
    connected: true,
    connectedHMI: false,
  },
  {
    vin: "USWESTVIN12345679",
    connected: true,
    connectedHMI: false,
  },
  {
    vin: "USWESTVIN12345670",
    connected: true,
    connectedHMI: false,
  },
];

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