import { useEffect, useRef, useState } from 'react';

import CheckIcon from '@mui/icons-material/Check';
import { Box, Button, Divider } from '@mui/material';

import { buildGroupFilter, convertFilterObjectToFilters, entriesFilterFromGroup, InventoryFilter } from '../../common/InventoryFilter';
import type { InventoryFilterState } from '../../common/InventoryFilter';
import { selectInventoryLoaded, selectInventoryMapping } from '../../../store/inventorySlice/selectors';
import { DEFAULT_COLUMNS } from '../constants';
import { useAppDispatch, useAppSelector } from '../store';
import {
  cancelFilterChanges,
  clearAllFilters,
  commitFilterState,
  initFilterState,
  setFilterState,
  setResultPage,
  setResultRowsPerPage,
  setResultSort
} from '../store/groupDetailSlice';
import { fetchStats, runInventoryQuery, saveGroupFilter, type InventoryQueryArgs } from '../store/groupDetailSlice/thunks';
import { selectIsFilterDirty } from '../store/groupDetailSlice/selectors';
import type { GroupType, SortState } from '../types';
import { GroupStatistics } from './GroupStatistics';
import { HostsResultTable } from './HostsResultTable';

interface GroupOverviewProps {
  groupId: string;
  type: GroupType;
}

// Build inventory query args from the filter + table state. The backend reads a
// leading '-' as ascending, so prefix it when NOT descending.
const buildQueryArgs = (filterState: InventoryFilterState, sort: SortState, page: number, rowsPerPage: number): InventoryQueryArgs => {
  const { filter, hostFilter, hostContextInclude } = buildGroupFilter(filterState);
  const sortParam = sort.column ? (sort.descending ? sort.column : `-${sort.column}`) : '';
  return { filter, hostFilter, hostContextInclude, sort: sortParam, limit: rowsPerPage, start: (page - 1) * rowsPerPage };
};

export const GroupOverview = ({ groupId, type }: GroupOverviewProps) => {
  const dispatch = useAppDispatch();
  const group = useAppSelector(state => state.groupDetail.group);
  const filterState = useAppSelector(state => state.groupDetail.filterState);
  const result = useAppSelector(state => state.groupDetail.result);
  const stats = useAppSelector(state => state.groupDetail.stats);
  const { resultSort, resultPage, resultRowsPerPage, loadingResult } = useAppSelector(state => state.groupDetail);
  const isDirty = useAppSelector(selectIsFilterDirty);
  const mapping = useAppSelector(selectInventoryMapping);
  const inventoryLoaded = useAppSelector(selectInventoryLoaded);

  const initialized = useRef(false);
  const [filterReady, setFilterReady] = useState(false);

  // Once the group and inventory attributes are both loaded, rebuild the
  // editable filter from the saved group and load stats.
  useEffect(() => {
    if (!group || !inventoryLoaded || initialized.current) return;
    initialized.current = true;
    dispatch(
      initFilterState({
        filters: convertFilterObjectToFilters(group.filter, mapping),
        entriesFilter: entriesFilterFromGroup(group.filter)
      })
    );
    dispatch(fetchStats(group.filter));
    setFilterReady(true);
  }, [group, inventoryLoaded, mapping, dispatch]);

  // Own the hosts query. Runs once the filter is committed to the store, then
  // again on sort/page/size change; keyed off those, not live rule edits, so
  // the host list reflects the last saved/loaded filter.
  useEffect(() => {
    if (!filterReady) return;
    dispatch(runInventoryQuery(buildQueryArgs(filterState, resultSort, resultPage, resultRowsPerPage)));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [filterReady, resultSort, resultPage, resultRowsPerPage]);

  const handleSave = () => {
    const groupFilter = buildGroupFilter(filterState);
    dispatch(saveGroupFilter({ id: groupId, type, groupFilter }))
      .unwrap()
      .then(() => {
        dispatch(commitFilterState());
        // Back to the first page; when already there the page effect won't
        // re-run, so query explicitly.
        if (resultPage === 1) {
          dispatch(runInventoryQuery(buildQueryArgs(filterState, resultSort, 1, resultRowsPerPage)));
        } else {
          dispatch(setResultPage(1));
        }
        dispatch(fetchStats(groupFilter));
      })
      .catch(() => undefined);
  };

  const openInventoryReport = () => {
    const filters = JSON.stringify(buildGroupFilter(filterState));
    // encodeURIComponent per value: the JSON (and CFEngine class expressions in
    // it) contain & = + which encodeURI leaves intact, corrupting the query.
    window.location.href = `/reports/inventory?columns=${DEFAULT_COLUMNS.join(',')}&group_filters=${encodeURIComponent(filters)}`;
  };

  return (
    <Box>
      <Box sx={{ mt: 3, mb: 2 }}>
        <InventoryFilter
          value={filterState}
          onChange={next => dispatch(setFilterState(next))}
          mapping={mapping}
          includeClasses
          toolbarActions={
            <Button color="inherit" variant="text" sx={{ color: 'text.primary', fontWeight: 500 }} onClick={() => dispatch(clearAllFilters())}>
              Reset
            </Button>
          }
        />

        {isDirty && (
          <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 2 }}>
            <Button color="inherit" variant="outlined" onClick={() => dispatch(cancelFilterChanges())}>
              Cancel
            </Button>
            <Button color="primary" variant="contained" startIcon={<CheckIcon />} onClick={handleSave}>
              Save changes
            </Button>
          </Box>
        )}
      </Box>

      <GroupStatistics rowCount={result.rowCount} stats={stats} />
      <Divider sx={{ my: 2 }} />
      <HostsResultTable
        result={result}
        sort={resultSort}
        loading={loadingResult}
        page={resultPage}
        rowsPerPage={resultRowsPerPage}
        onSort={column => dispatch(setResultSort({ column, descending: resultSort.column === column ? !resultSort.descending : false }))}
        onChangePage={page => dispatch(setResultPage(page))}
        onChangeRowsPerPage={value => dispatch(setResultRowsPerPage(value))}
        onOpenReport={openInventoryReport}
      />
    </Box>
  );
};
