import React, { useContext, useState } from "react";

import { logger } from "../../services/monitoring";
import api from "../../services/vehiclesAPI";
import { validateVIN } from "../../utils/validationSupplier";

const VehicleContext = React.createContext();
export const VehicleConsumer = VehicleContext.Consumer;

const validateAdd = (vehicle) => {
  if (vehicle == null) {
    throw new Error("No vehicle data");
  }

  if (!vehicle.vin || vehicle.vin.length === 0) {
    throw new Error("VIN required");
  }

  if (vehicle.vin.length > 17) {
    throw new Error("VIN cannot be larger than 17 characters");
  }
};

export const VehicleProvider = ({ children }) => {
  const [busy, setBusy] = useState(false);
  const [vehicle, setVehicle] = useState({});
  const [vehicles, setVehicles] = useState([]);
  const [totalVehicles, setTotalVehicles] = useState(0);
  const [fleets, setFleets] = useState([]);
  const [totalFleets, setTotalFleets] = useState(0);
  const [models, setModels] = useState([]);
  const [years, setYears] = useState([]);
  const [flashpacks, setFlashpacks] = useState([]);
  const [totalFlashpacks, setTotalFlashpacks] = useState(0);
  const [flashpackECUMappings, setFlashpackECUMappings] = useState([])
  const [totalFlashpackECUMappings, setTotalFlashpackECUMappings] = useState(0)
  const [osVersions, setOSVersions] = useState([{ "value": "", "label": "None" }])

  const addConnections = async (cars, token) => {
    try {
      if (cars.length === 0) return;
      const vins = cars.map((car) => car.vin);
      const result = await api.getConnections({ "VINs": vins }, token);

      if (result.error) {
        throw new Error(`Add connections error. ${result.message}`);
      }
      cars.forEach((car) => {
        car.connected = result[car.vin] || false;
        car.connectedHMI = result[`2:${car.vin}`] || false;
      });
    } catch (e) {
      logger.error(e.stack);
    }
  };

  const addVehicle = async (v, token) => {
    try {
      setBusy(true);
      validateAdd(v);
      const result = await api.addVehicle(v, token);
      if (result.error) throw new Error(`Add vehicle error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const addTags = async (vins, tags, token) => {
    try {
      setBusy(true);
      vins.forEach(vin => validateVIN(vin));
      const validateTags = tags.every(tag => typeof tag === "string");
      if (!validateTags)
        throw new Error("Invalid Tag");

      const result = await api.addTags(vins, tags, token)
      if (result.error)
        throw new Error(`Add tags error. ${result.message}`);
    } finally {
      setBusy(false)
    }
  }

  const getConnections = async (vins, token) => {
    try {
      setBusy(true);
      const result = await api.getConnections({ "VINs": vins }, token);
      if (result.error)
        throw new Error(`Get connections error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const getECUs = async (search, token) => {
    try {
      setBusy(true);
      const result = await api.getECUs(search, token);
      if (result.error) throw new Error(`Get ECUs error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const getLocations = async (token) => {
    try {
      setBusy(true);
      const result = await api.getLocations(token);
      if (result.error)
        throw new Error(`Get locations error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const getLocationsVehiclePaths = async (token, param, vins) => {
    try {
      setBusy(true);
      const result = await api.getLocationsVehiclePaths(token, param, vins);
      if (result.error)
        throw new Error(`Get locations vehicle paths error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const getModels = async (token) => {
    try {
      setBusy(true);
      const result = await api.getModels(token);
      if (result.error) throw new Error(`Get models error. ${result.message}`);
      setModels(result.data);
    } finally {
      setBusy(false);
    }
  };

  const getState = async (token, vin) => {
    try {
      setBusy(true);
      const result = await api.getState(token, vin);
      if (result.error) throw new Error(`Get state error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const getVehicle = async (vin, token) => {
    try {
      setBusy(true);
      validateVIN(vin);

      const result = await api.getVehicle(vin, token);
      if (result.error) throw new Error(`Get vehicle error. ${result.message}`);

      setVehicle(result ?? []);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const getVehicles = async (search, token) => {
    try {
      setBusy(true);
      const result = await api.getVehicles(search, token);
      if (result.error) {
        setVehicles([]);
        throw new Error(`Get vehicles error. ${result.message}`);
      }
      await addConnections(result.data, token);
      setVehicles(result.data ?? []);
      if (result.total) {
        setTotalVehicles(result.total);
      }
    } finally {
      setBusy(false);
    }
  };

  const getYears = async (token) => {
    try {
      setBusy(true);
      const result = await api.getYears(token);
      if (result.error) throw new Error(`Get years error. ${result.message}`);
      setYears(result.data);
    } finally {
      setBusy(false);
    }
  };

  const sendCommand = async (vins, command, token) => {
    try {
      setBusy(true);
      const result = await api.sendCommand(vins, command, token);
      if (result.error)
        throw new Error(`Send command error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const sendDiagnosticCommand = async (vins, command, token) => {
    try {
      setBusy(true);
      const result = await api.sendDiagnosticCommand(vins, command, token);
      if (result.error)
        throw new Error(`Send diagnostic command error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const updateVehicle = async (vin, v, token) => {
    try {
      setBusy(true);
      validateVIN(vin);
      validateVehicle(v);

      const result = await api.updateVehicle(vin, v, token);
      if (result.error)
        throw new Error(`Update vehicle error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const uploadConfig = async (vin, forced, token) => {
    try {
      setBusy(true);
      validateVIN(vin);

      const result = await api.updateConfig(vin, forced, token);
      if (result.error)
        throw new Error(`Update vehicle error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  }

  const getCANSignals = async (vin, token) => {
    try {
      setBusy(true);
      validateVIN(vin);

      const result = await api.getCANSignals(vin, token);
      if (result.error)
        throw new Error(`Get CAN signals error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const getFleets = async (vin, search, token) => {
    try {
      setBusy(true);
      validateVIN(vin);

      const result = await api.getFleets(vin, search, token);
      if (result.error) {
        setFleets([]);
        throw new Error(`Get Fleets of vehicle`)
      }
      setFleets(result.data ?? []);
      if (result.total) {
        setTotalFleets(result.total);
      }
    } finally {
      setBusy(false)
    }
  }

  const getVersionLog = async (search, token) => {
    try {
      setBusy(true);
      const result = await api.getVersionLog(search, token);
      if (result.error) throw new Error(`Get version log error. ${result.message}`);
      return result;
    } finally {
      setBusy(false);
    }
  };

  const getAllFlashpacks = async (model, trim, year, options, token) => {
    try {
      setBusy(true);

      const result = await api.getAllFlashpacks(model, trim, year, options, token);
      if (result.error) {
        throw new Error(`Get all flashpacks error. ${result.message}`);
      }

      setFlashpacks(result.data);
      if (options && options.offset === 0 && result.total) {
        setTotalFlashpacks(result.total);
      }

      return result;
    } finally {
      setBusy(false);
    }
  };

  const getFlashpackECUMappings = async (model, trim, year, flashpack, options, token) => {
    try {
      setBusy(true);

      const result = await api.getFlashpackECUMappings(model, trim, year, flashpack, options, token);
      if (result.error) {
        throw new Error(`Get flashpack ecu mappings error. ${result.message}`);
      }

      setFlashpackECUMappings(result.data);
      if (options && options.offset === 0 && result.total) {
        setTotalFlashpackECUMappings(result.total);
      }

      return result;
    } finally {
      setBusy(false);
    }
  };

  const addFlashpackVersion = async (model, trim, year, flashpack, osVersion, carFlashpackVersions, token) => {
    try {
      setBusy(true);

      const data = {
        "car_model": model,
        "car_trim": trim,
        "car_year": year,
        "flashpack": flashpack,
        "os_version": osVersion,
        "ecu_versions": carFlashpackVersions,
      }

      const result = await api.addFlashpackVersion(data, token)
      if (result.error) {
        throw new Error(`Add flashpack version error. ${result.message}`);
      }

      return result;
    } finally {
      setBusy(false)
    }
  }

  const addFlashpackVersionECUMapping = async (model, trim, year, flashpack, ecuName, ecuVersion, token) => {
    try {
      setBusy(true);

      const data = {
        "car_ecu_name": ecuName,
        "car_ecu_version": ecuVersion,
      }

      const result = await api.addFlashpackVersionECUMapping(model, trim, year, flashpack, data, token)
      if (result.error) {
        throw new Error(`Add flashpack version ECU mapping error. ${result.message}`);
      }

      return result;
    } finally {
      setBusy(false)
    }
  }

  const deleteFlashpackVersion = async (model, trim, year, flashpack, token) => {
    try {
      setBusy(true);

      const data = {
        "car_model": model,
        "car_trim": trim,
        "car_year": year,
        "flashpack": flashpack,
      }

      const result = await api.deleteFlashpackVersion(data, token);
      if (result.error) {
        throw new Error(`Delete flashpack version error. ${result.message}`);
      }

      return result;
    } finally {
      setBusy(false);
    }
  };

  const deleteFlashpackVersionECUMapping = async (model, trim, year, flashpack, ecuName, ecuVersion, token) => {
    try {
      setBusy(true);

      const result = await api.deleteFlashpackVersionECUMapping(model, trim, year, flashpack, ecuName, ecuVersion, token);
      if (result.error) {
        throw new Error(`Delete flashpack ecu mapping error. ${result.message}`);
      }

      return result;
    } finally {
      setBusy(false);
    }
  };

  const getCarFlashpackVersionInfo = async (vin, token) => {
    try {
      setBusy(true);

      const result = await api.getCarFlashpackVersionInfo(vin, token);
      if (result.error) {
        throw new Error(`Get car flashpack version info error. ${result.message}`);
      }

      return result;
    } finally {
      setBusy(false);
    }
  };

  const getOSVersions = async (token) => {
    try {
      setBusy(true);

      const result = await api.getOSVersions(token);
      if (result.error) {
        throw new Error(`Get OS versions error. ${result.message}`);
      }

      var data = [{ "value": "", "label": "None" }]

      for (let i = 0; i < result.data.length; i++) {
        data.push({
          "value": result.data[i],
          "label": result.data[i]
        });
      }

      setOSVersions(data);
    } finally {
      setBusy(false);
    }
  }

  return (
    <VehicleContext.Provider
      value={{
        busy,
        models,
        totalVehicles,
        vehicle,
        vehicles,
        years,
        fleets,
        totalFleets,
        addVehicle,
        getConnections,
        getCANSignals,
        getECUs,
        getLocations,
        getLocationsVehiclePaths,
        getModels,
        getState,
        getYears,
        getVehicle,
        getVehicles,
        sendCommand,
        sendDiagnosticCommand,
        updateVehicle,
        getFleets,
        getVersionLog,
        uploadConfig,
        addTags,
        flashpacks,
        totalFlashpacks,
        getAllFlashpacks,
        totalFlashpackECUMappings,
        flashpackECUMappings,
        getFlashpackECUMappings,
        addFlashpackVersion,
        addFlashpackVersionECUMapping,
        deleteFlashpackVersion,
        deleteFlashpackVersionECUMapping,
        getCarFlashpackVersionInfo,
        osVersions,
        getOSVersions,
      }}
    >
      {children}
    </VehicleContext.Provider>
  );
};

const validateVehicle = (v) => {
  if (v == null) {
    throw new Error("No vehicle data");
  }

  validateVIN(v.vin);
};

export const useVehicleContext = () => useContext(VehicleContext);
