import { Box, Grid2 as Grid, Skeleton, Theme, Tooltip, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { NextMeasurementChip } from "common/NextMeasurementChip";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
// import { getTableIcons } from "common/ResponsiveTable";
import SmartBreadcrumb from "common/SmartBreadcrumb";
import PendingChangesChip from "common/PendingChangesChip";
import { GetDefaultDateRange } from "common/time/DateRange";
import ComponentActions from "component/ComponentActions";
import ComponentChart from "component/ComponentChart";
import ComponentTypeChip from "component/ComponentTypeChip";
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
// import MeasurementSummary from "component/MeasurementSummary";
// import UnitMeasurementSummary from "component/UnitMeasurementSummary";
import {
  useComponentAPI,
  useDeviceAPI,
  useGroupAPI,
  useHTTP,
  useInteractionsAPI,
  usePendingChanges,
  usePrevious,
  useSnackbar,
  useUserAPI
} from "hooks";
import { useWebSocket } from "hooks/useWebSocket";
import InteractionChip from "interactions/InteractionChip";
import InteractionSettings from "interactions/InteractionSettings";
import { cloneDeep } from "lodash";
// import MaterialTable from "material-table";
import { Component, Device, deviceScope, Group, Interaction } from "models";
import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement";
import moment, { Moment } from "moment";
// import { MatchParams } from "navigation/Routes";
import PageContainer from "pages/PageContainer";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import {
  DeviceAvailabilityMap,
  FindAvailablePositions,
  OffsetAvailabilityMap
} from "pbHelpers/DeviceAvailability";
import { getDefaultInteraction } from "pbHelpers/Interaction";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
import { useTeamAPI } from "providers/pond/teamAPI";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useParams } from "react-router-dom";
// import { useRouteMatch } from "react-router";
import { isStaleStatusUpdate } from "utils/syncStatus";
import { or } from "utils/types";

const useStyles = makeStyles((theme: Theme) => {
  return ({
    toolbar: {
      marginTop: theme.spacing(1),
      paddingRight: theme.spacing(1),
      paddingLeft: theme.spacing(1),
      [theme.breakpoints.up("sm")]: {
        paddingRight: theme.spacing(1),
        paddingLeft: theme.spacing(2)
      }
    },
    toolbarTitle: {
      whiteSpace: "nowrap",
      overflow: "hidden",
      textOverflow: "ellipsis"
    },
    contentContainer: {
      padding: theme.spacing(1),
      [theme.breakpoints.up("sm")]: {
        paddingRight: theme.spacing(2)
      },
      [theme.breakpoints.up("lg")]: {
        paddingRight: theme.spacing(3)
      }
    },
    overviewContainer: {
      position: "relative",
      maxWidth: "100%",
      overflowX: "auto",
      marginBottom: theme.spacing(2)
    },
    verticalSpacing: {
      marginBottom: theme.spacing(2)
    },
    tablePagination: {
      overflow: "auto"
    }
  })
});

export default function DeviceComponent() {
  const classes = useStyles();
  // const match = useRouteMatch<MatchParams>();
  const deviceAPI = useDeviceAPI();
  const componentAPI = useComponentAPI();
  const { error } = useSnackbar();
  const groupAPI = useGroupAPI();
  const interactionsAPI = useInteractionsAPI();
  const userAPI = useUserAPI();
  const teamAPI = useTeamAPI();
  // const deviceID = parseInt(match.params.deviceID, 10);
  // const componentID = match.params.componentID;
  // const groupID = or(parseInt(match.params.groupID, 10), undefined);
  const deviceID = +(useParams<{ deviceID: string }>()?.deviceID ?? "0");
  const componentID = useParams<{ componentID: string }>()?.componentID ?? "";
  const groupID = +(useParams<{ groupID: string }>()?.groupID ?? "");
  const [isFirstRender, setIsFirstRender] = useState<boolean>(true);
  const [loadingInitial, setLoadingInitial] = useState<boolean>(false);
  const defaultDateRange = GetDefaultDateRange();
  const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
  const prevStartDate = usePrevious(startDate);
  const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
  const prevEndDate = usePrevious(endDate);
  const live = useRef(defaultDateRange.live);
  const [device, setDevice] = useState<Device>(Device.create());
  const [group, setGroup] = useState<Group | undefined>(Group.create());
  const [component, setComponent] = useState<Component>(Component.create());
  const [components, setComponents] = useState<Component[]>([]);
  const [sampling, setSampling] = useState(false);
  const liveSamples = useRef<pond.Measurement[]>([]);
  const [samples, setSamples] = useState<pond.Measurement[]>([]);
  const [permissions, setPermissions] = useState<pond.Permission[]>([]);
  const [interactions, setInteractions] = useState<Interaction[]>([]);
  const [selectedInteraction, setSelectedInteraction] = useState<Interaction>(
    getDefaultInteraction()
  );
  const [pageSize, setPageSize] = useState<number>(5);
  const [page, setPage] = useState(0)
  const [order, setOrder] = useState("desc")
  const [rows, setRows] = useState<convertedUnitMeasurement[]>([])
  const [total, setTotal] = useState<number>(0)
  const prevRowsPerPage = usePrevious(pageSize);
  const [availablePositions, setAvailablePositions] = useState<DeviceAvailabilityMap>(new Map());
  const [availableOffsets, setAvailableOffsets] = useState<OffsetAvailabilityMap>(new Map());
  const tableRef: any = useRef();
  let isInteractionSettingsOpen =
    JSON.stringify(or(selectedInteraction, getDefaultInteraction())) !==
    JSON.stringify(getDefaultInteraction());
  const [{ user, as, showErrors }] = useGlobalState();
  const [unitMeasurements, setUnitMeasurements] = useState<UnitMeasurement[]>([]);
  const [rm, setRM] = useState<pond.Measurement>(pond.Measurement.create());
  const [recentUnitMeasurement, setRecentUnitMeasurement] = useState<UnitMeasurement[]>([]);
  const [recentGoodUnitMeasurement, setRecentGoodUnitMeasurement] = useState<UnitMeasurement[]>([]);
  const sampleLimit = 200;
  const [deviceComponentPrefs, setDeviceComponentPrefs] = useState<
    pond.DeviceComponentPreferences
  >();
  const { token } = useHTTP();
  const componentPending = usePendingChanges(
    component.status.synced,
    !loadingInitial && component.key() !== ""
  );

  const liveContextKeys = useMemo(() => getContextKeys(), []);
  const liveContextTypes = useMemo(() => getContextTypes(), []);
  /** Matches the REST calls: omit ?as= when first context type is "team". */
  const liveAs =
    as && !(liveContextTypes.length > 0 && liveContextTypes[0] === "team")
      ? as
      : undefined;

  // --- Real-time component updates ---
  // Streams component changes for this device, including status changes
  // when the device accepts pending component updates.
  useWebSocket<{ key: string; component: Component } | null>({
    path: `/v1/live/devices/${deviceID}/components`,
    parse: (e) => {
      try {
        const raw = JSON.parse(e.data);
        const comp = Component.any(raw);
        if (!comp?.settings) {
          console.warn("[ws] received component without settings:", raw);
          return null;
        }
        return { key: comp.key(), component: comp };
      } catch (err) {
        console.warn("[ws] failed to parse component:", err);
        return null;
      }
    },
    onMessage: (data) => {
      if (!data || data.key !== componentID) return;
      setComponent((prev) => {
        if (prev.key() !== data.key) return prev;
        if (isStaleStatusUpdate(prev.status, data.component.status)) {
          return prev;
        }
        return data.component;
      });
    },
    keys: liveContextKeys,
    types: liveContextTypes,
    as: liveAs,
    token,
    enabled: !!deviceID && componentID !== "",
  });

  const setDefaultState = () => {
    setDevice(Device.create());
    setGroup(undefined);
    setInteractions([]);
    setSelectedInteraction(getDefaultInteraction());
    setComponents([]);
    setComponent(Component.create());
    setPermissions([]);
    setAvailablePositions(new Map());
    setAvailableOffsets(new Map());
  };

  const loadUnitMeasurements = useCallback(() => {
    setUnitMeasurements([]);
    setSampling(true);
    componentAPI
      .sampleUnitMeasurements(
        deviceID,
        componentID,
        startDate,
        endDate,
        sampleLimit,
        false,
        getContextKeys(),
        getContextTypes(),
        showErrors,
        showErrors,
        as,
      )
      .then(resp => {
        if (resp.data.measurements) {
          let newUnitMeasurements = resp.data.measurements.map((um: any) =>
            UnitMeasurement.any(um, user)
          );
          setUnitMeasurements([...newUnitMeasurements]);
        }
      })
      .finally(() => {
        setSampling(false);
      });
  }, [componentAPI, deviceID, componentID, user, startDate, endDate, showErrors, as]);

  useEffect(() => {
    if (live.current && liveSamples) {
      setSamples(liveSamples.current);
    } else {
      loadUnitMeasurements();
    }
  }, [loadUnitMeasurements]);

  useEffect(() => {
    if (component.key() !== "") {
      interactionsAPI
        .listInteractionsByComponent(deviceID, component.location(), undefined, as)
        .then((rInteractions: Interaction[]) => {
          setInteractions(rInteractions);
        })
        .catch((err: any) => {
          console.log(err);
        });
    }
  }, [component, deviceID, interactionsAPI]);

  const loadState = useCallback(() => {
    if (user.settings.id === "") return;
    setLoadingInitial(true);
    deviceAPI
      .get(deviceID, false, getContextKeys().slice(0, -1), getContextTypes().slice(0, -1), as)
      .then((response: any) => {
        const rDevice = Device.any(response.data);
        setDevice(rDevice);
        let componentsPromise = componentAPI.list(
          deviceID,
          false,
          getContextKeys(),
          getContextTypes(),
          true,
          as
        );
        let userPromise = userAPI.getUser(user.settings.id, deviceScope(deviceID.toString()));
        let prefPromise = deviceAPI.listDeviceComponentPreferences(deviceID, getContextKeys().slice(0, -1), getContextTypes().slice(0, -1), as);
        Promise.all([componentsPromise, userPromise, prefPromise])
          .then((responses: any) => {
            const rawComponents: Array<any> = or(responses[0].data.components, []).sort(
              (a: any, b: any) => {
                if (or(a.settings.name, "").toLowerCase() < or(b.settings.name, "").toLowerCase()) {
                  return -1;
                } else if (
                  or(a.settings.name, "").toLowerCase() > or(b.settings.name, "").toLowerCase()
                ) {
                  return 1;
                } else {
                  return 0;
                }
              }
            );
            let rComponents: Component[] = [];
            rawComponents.forEach((rawComponent: any) => {
              rComponents.push(Component.any(rawComponent));
            });
            setComponents(rComponents);
            let rComponent = rComponents.find(c => componentID === c.key());
            setComponent(rComponent ? rComponent : Component.create());
            if (rComponent?.status.lastMeasurement) {
              setRM(rComponent.status.lastMeasurement);
            }
            if (rComponent?.status.measurement) {
              let measurements = rComponent.status.measurement.map(um =>
                UnitMeasurement.any(um, user)
              );
              setRecentUnitMeasurement(measurements);
            }
            if (rComponent?.status?.lastGoodMeasurement) {
              let measurements = rComponent.status.lastGoodMeasurement.map(um =>
                UnitMeasurement.any(um, user)
              );
              setRecentGoodUnitMeasurement(measurements);
            }

            setPermissions(responses[1].permissions);
            let available = FindAvailablePositions(rComponents, rDevice.settings.product);
            setAvailablePositions(available.GetAvailability());
            setAvailableOffsets(available.offsetAvailability);

            let allPrefs = responses[2].data.preferences;
            Object.keys(allPrefs).forEach(compoundKey => {
              let componentKey = compoundKey.split(":")[1]
                ? compoundKey.split(":")[1]
                : compoundKey;
              if (componentID === componentKey) {
                setDeviceComponentPrefs(
                  pond.DeviceComponentPreferences.fromObject(allPrefs[compoundKey])
                );
              }
            });
          })
          .catch(err => {
            console.log(err);
            setDefaultState();
          })
          .finally(() => {
            setLoadingInitial(false);
            if (groupID > 0) {
              groupAPI
                .getGroup(groupID)
                .then((response: any) => {
                  setGroup(Group.any(response.data));
                })
                .catch((error: any) => {
                  setGroup(undefined);
                });
            }
            if (as) {
              teamAPI.getTeam(as, deviceScope(deviceID.toString())).then(resp => {
                //if (resp.permissions.length > permissions.length) {
                setPermissions(resp.permissions);
                //}
              });
            }
          });
      })
      .catch((err: any) => {
        setDefaultState();
        console.log(err);
      });
  }, [
    componentAPI,
    componentID,
    deviceAPI,
    deviceID,
    groupAPI,
    groupID,
    userAPI,
    as,
    teamAPI,
    user
  ]);

  useEffect(() => {
    if (isFirstRender) {
      loadState();
      setIsFirstRender(false);
    } else if (prevStartDate !== startDate || prevEndDate !== endDate) {
      if (tableRef && tableRef.current) {
        tableRef.current.onChangePage(undefined, 0);
      }
    }
  }, [
    component,
    endDate,
    isFirstRender,
    loadState,
    prevEndDate,
    prevRowsPerPage,
    prevStartDate,
    pageSize,
    startDate,
    tableRef
  ]);

  const interactionChips = (
    component: Component,
    interactions: Interaction[],
    components: Component[]
  ) => {
    if (!component || !interactions || !components || interactions.length <= 0) {
      return null;
    }
    let chips: any = [];
    interactions.forEach((interaction: Interaction) => {
      let rawInteraction = Interaction.clone(interaction);
      let key =
        JSON.stringify(rawInteraction.settings.source) +
        JSON.stringify(rawInteraction.settings.sink) +
        rawInteraction.settings.instance;
      chips.push(
        <Grid key={key}>
          <InteractionChip
            interaction={interaction}
            component={component}
            components={components}
            onClick={() => selectInteraction(rawInteraction)}
          />
        </Grid>
      );
    });
    return chips;
  };

  const status = () => {
    if (loadingInitial)
      return (
        <Box width={1} margin={1}>
          <Skeleton variant="text" width="50%" />
        </Box>
      );

    return (
      <Grid
        container
        spacing={1}
        flex-direction="row"
        justifyContent="flex-start"
        alignItems="center"
        wrap="nowrap"
        className={classes.overviewContainer}>
        {(componentPending.pending || componentPending.accepted) && (
          <Grid >
            <PendingChangesChip
              pending={componentPending.pending}
              accepted={componentPending.accepted}
            />
          </Grid>
        )}
        <Grid >
          {/* TODO: use the unit measurement timestamp */}
          <NextMeasurementChip
            lastMeasurement={or(rm, { timestamp: "" }).timestamp}
            periodMs={component.settings.reportPeriodMs}
          />
        </Grid>
        <Grid >
          <ComponentTypeChip component={component} />
        </Grid>
        {interactionChips(component, interactions, components)}
      </Grid>
    );
  };

  const selectInteraction = (newInteraction: Interaction) => {
    setSelectedInteraction(newInteraction);
  };

  // This triggers table rerender when the show errors variable is flipped
  useEffect(() => {
    if (tableRef && tableRef.current) {
      tableRef.current.onChangePage(undefined, 0);
    }
  }, [showErrors, tableRef]);

  const load = () => {
    componentAPI.listUnitMeasurements(
      deviceID,
      componentID,
      startDate,
      endDate,
      pageSize,
      page * pageSize,
      order,
      undefined,
      getContextKeys(),
      getContextTypes(),
      showErrors,
      showErrors,
      as
    )
    .then(response => {
      let total: number = response.data.total ? response.data.total : 0;
      let rows = UnitMeasurement.convertMeasurements(
        response.data.measurements.map(um => UnitMeasurement.any(um, user))
      );
      // let maxPage = total / pageSize;
      // if (page > maxPage) {
      //   tableRef.current.isOutsidePageNumbers();
      // }
      setRows(rows)
      setTotal(total)
      // resolve({
      //   data: rows,
      //   page: page,
      //   totalCount: total
      // });
    })
    .catch((err: any) => {
      // resolve({
      //   data: [],
      //   page: 0,
      //   totalCount: 0
      // });
    });
  }

  useEffect(() => {
    load()
  }, [page, pageSize, as])

  const columns = (): Column<convertedUnitMeasurement>[] => {
    return [
      {
        title: "Timestamp",
        cellStyle: {width: "50%"},
        render: row => {
          return (
            <Tooltip title={moment(row.timestamp).calendar()}>
              <Box>
                <Typography>{moment(row.timestamp).fromNow()}</Typography>
              </Box>
            </Tooltip>
          )
        }
      },
      {
        title: "Measurement",
        cellStyle: {width: "50%"},
        render: row => {
          return (
            <UnitMeasurementSummary
              component={component}
              reading={row}
              tableCell={true}
            />
          )
        }
      }
    ]
  }

  // const unitMeasurementsTable = () => {
  //   interface Row {
  //     timestamp: string;
  //     measurementsFor: MeasurementsFor[];
  //   }
  //   return (
  //     <Grid item xs={12} className={classes.verticalSpacing}>
  //       <MaterialTable
  //         key="unitMeasurements"
  //         tableRef={tableRef}
  //         // icons={getTableIcons()}
  //         columns={[
  //           {
  //             title: "Timestamp",
  //             type: "datetime",
  //             defaultSort: "desc",
  //             render: (row: Row) => {
  //               return (
  //                 <Tooltip title={moment(row.timestamp).calendar()}>
  //                   <span>{moment(row.timestamp).fromNow()}</span>
  //                 </Tooltip>
  //               );
  //             }
  //           },
  //           {
  //             title: "Measurement",
  //             sorting: false,
  //             field: "measurement",
  //             render: (row: Row) => (
  //               <UnitMeasurementSummary
  //                 component={component}
  //                 reading={row}
  //                 tableCell={true}
  //                 excludedNodes={component.settings.excludedNodes}
  //               />
  //             )
  //           }
  //         ]}
  //         options={{
  //           paging: true,
  //           pageSize: pageSize,
  //           pageSizeOptions: [5, 10, 20],
  //           padding: "default",
  //           showTitle: true,
  //           toolbar: true,
  //           sorting: !live.current,
  //           search: false,
  //           headerStyle: { position: "inherit", borderBottom: "1px solid rgba(81, 81, 81, 1)" }
  //         }}
  //         localization={{
  //           body: { emptyDataSourceMessage: "No measurements found" }
  //         }}
  //         data={query =>
  //           new Promise(resolve => {
  //             let pageSize = query.pageSize;
  //             let page = query.page;
  //             let orderBy =
  //               query.orderBy && query.orderBy.field ? query.orderBy.field.toString() : "timestamp";
  //             componentAPI
  //               .listUnitMeasurements(
  //                 deviceID,
  //                 componentID,
  //                 startDate,
  //                 endDate,
  //                 pageSize,
  //                 page * pageSize,
  //                 orderBy,
  //                 undefined,
  //                 getContextKeys(),
  //                 getContextTypes(),
  //                 showErrors
  //               )
  //               .then(response => {
  //                 let total: number = response.data.total ? response.data.total : 0;
  //                 let rows = UnitMeasurement.convertMeasurements(
  //                   response.data.measurements.map(um => UnitMeasurement.any(um, user))
  //                 );
  //                 let maxPage = total / pageSize;
  //                 if (page > maxPage) {
  //                   tableRef.current.isOutsidePageNumbers();
  //                 }
  //                 resolve({
  //                   data: rows,
  //                   page: page,
  //                   totalCount: total
  //                 });
  //               })
  //               .catch((err: any) => {
  //                 resolve({
  //                   data: [],
  //                   page: 0,
  //                   totalCount: 0
  //                 });
  //               });
  //           })
  //         }
  //       />
  //     </Grid>
  //   );
  // };

  const handleRowsPerPageChange = (event: any) => {
    const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value
    setPageSize(newRowsPerPage);
    setPage(0)
  }

  const measurementsTable = () => {
    return (
      <Grid size={{ xs: 12 }} className={classes.verticalSpacing}>
        <ResponsiveTable
          rows={rows}
          columns={columns()}
          total={total}
          page={page}
          pageSize={pageSize}
          setPage={setPage}
          handleRowsPerPageChange={handleRowsPerPageChange}
          loadMore={() => {
            let currentRows = cloneDeep(rows)
            let newOffset = rows.length
            componentAPI.listUnitMeasurements(      
              deviceID,
              componentID,
              startDate,
              endDate,
              pageSize,
              newOffset,
              order,
              undefined,
              getContextKeys(),
              getContextTypes(),
              showErrors,
              showErrors,
              as)
              .then(resp => {
                  let newRows = UnitMeasurement.convertMeasurements(
                  resp.data.measurements.map(um => UnitMeasurement.any(um, user))
                )
                setRows(currentRows.concat(newRows))
              }).catch(err => {
                
              })
            
          }}
        />
      </Grid>
    );
  };

  return (
    <PageContainer>
      <Grid
        container
        direction="row"
        justifyContent="flex-start"
        alignContent="center"
        rowSpacing={1}
        className={classes.toolbar}>
        <Grid
          size={{ xs: 8, sm: 9 }}
          container
          justifyContent="flex-start"
          alignContent="center"
          className={classes.toolbarTitle}>
          <SmartBreadcrumb
            deviceName={device.name()}
            componentName={component.name()}
            groupName={group ? group.name() : undefined}
            loading={loadingInitial}
            reload={loadState}
          />
        </Grid>

        <Grid size={{ xs: 4, sm: 3 }} container justifyContent="flex-end" alignContent="center">
          <ComponentActions
            device={device}
            components={components}
            component={component}
            permissions={permissions}
            availablePositions={availablePositions}
            availableOffsets={availableOffsets}
            refreshCallback={loadState}
            initialStartDate={startDate}
            initialEndDate={endDate}
            deviceComponentPreferences={deviceComponentPrefs}
          />
        </Grid>
        <Grid size={{ xs: 12 }}>
          {status()}
        </Grid>
      </Grid>

      <Grid
        container
        direction="column"
        justifyContent="flex-start"
        alignItems="center"
        className={classes.contentContainer}>

        <Grid container size={{ xs: 12 }}>
          {
            <Grid size={{ xs: 12 }} className={classes.verticalSpacing}>
              <ComponentChart
                deviceKey={deviceID.toString()}
                componentKey={componentID}
                component={component}
                interactions={interactions}
                sampling={sampling}
                unitMeasurements={unitMeasurements}
                sampleLimit={sampleLimit}
                deviceComponentPreferences={deviceComponentPrefs}
                updateDate={(newStartDate, newEndDate, newLive) => {
                  setStartDate(newStartDate);
                  setEndDate(newEndDate);
                  live.current = !!newLive;
                }}
                allowLive={true}
                recentUnitMeasurement={
                  showErrors ? recentUnitMeasurement : recentGoodUnitMeasurement
                }
              />
            </Grid>
          }
        </Grid>
        <Grid container size={{ xs: 12 }}>
          {/* {newStructure ? unitMeasurementsTable() : measurementsTable()} */}
          {measurementsTable()}
        </Grid>
      </Grid>
      <InteractionSettings
        device={device}
        components={components.map((component: Component) => Component.clone(component))}
        initialComponent={component}
        initialInteraction={selectedInteraction}
        mode="update"
        isDialogOpen={isInteractionSettingsOpen}
        closeDialogCallback={() => selectInteraction(getDefaultInteraction())}
        refreshCallback={loadState}
        canEdit={permissions.includes(pond.Permission.PERMISSION_WRITE)}
      />
    </PageContainer>
  );
}
