import React, { useCallback, useEffect, useMemo, useState } from "react";

import FilterListIcon from "@mui/icons-material/FilterList";
import { TableBody, TableHead, TableRow } from "@mui/material";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
import CardActions from "@mui/material/CardActions";
import IconButton from "@mui/material/IconButton";
import Stack from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";

import FilterChip from "../../../components/FilterChip";
import Table from "../../../components/Table";
import { TableCell } from "../../../components/Table/TableCell";
import TableHeading from "../../../components/Table/TableHeading";
import TableCardHeader from "../../../components/TableCardHeader";
import { useApi } from "../../../contexts/ApiContext";
import { useDialog } from "../../../contexts/DialogContext";
import { useI18n } from "../../../contexts/I18nContext";
import { useUser } from "../../../contexts/UserContext";
import { hasPermission } from "../../../util/has_permission";
import {
  ArticlePriceListSerializer,
  ArticlePriceSerializer,
  ArticlePriceUpdateSerializer,
} from "../types/contrib";

import { ArticlePriceRow } from "./ArticlePriceRow";

export interface ArticlePricingCardProps {
  code: string;
  prices: ArticlePriceSerializer[];
  setPrices: (prices: ArticlePriceSerializer[]) => void;
}

export const ArticlePricingCard: React.FC<ArticlePricingCardProps> = ({
  code,
  prices,
  setPrices,
}) => {
  const theme = useTheme();
  const openDialog = useDialog();
  const api = useApi();
  const { t } = useI18n();
  const { user } = useUser();

  const [priceLists, setPriceLists] = useState<ArticlePriceListSerializer[]>([]);

  const [isEditing, setIsEditing] = useState(false);
  const [isDisabled, setIsDisabled] = useState(false);

  const [showActive, setShowActive] = useState(true);
  const [showInactive, setShowInactive] = useState(false);

  const [changedPrices, setChangedPrices] = useState<Record<number, string>>({});
  const [deletedPrices, setDeletedPrices] = useState<number[]>([]);
  const [createdPrices, setCreatedPrices] = useState<ArticlePriceUpdateSerializer[]>([]);

  const hasChanges = useMemo(
    () =>
      Object.entries(changedPrices).length > 0 ||
      deletedPrices.length > 0 ||
      createdPrices.length > 0,
    [changedPrices, deletedPrices, createdPrices],
  );

  const shownPrices = useMemo(() => {
    if (!isEditing) {
      return prices.filter(
        (price) =>
          (showActive && price.price_list.is_active) ||
          (showInactive && !price.price_list.is_active),
      );
    }

    return prices
      .map(({ amount, ...price }) => {
        if (deletedPrices.includes(price.price_list.id)) {
          return null;
        }

        return {
          ...price,
          amount: changedPrices[price.price_list.id] ?? amount,
        };
      })
      .filter((price): price is ArticlePriceSerializer => price !== null)
      .filter(
        (price) =>
          (showActive && price.price_list.is_active) ||
          (showInactive && !price.price_list.is_active),
      );
  }, [prices, isEditing, showActive, showInactive, changedPrices, deletedPrices]);

  const unusedPriceLists = useMemo(() => {
    const usedPriceLists = [
      ...shownPrices.map((price) => price.price_list.id),
      ...createdPrices.map((price) => price.price_list_id),
    ];
    return [
      ...priceLists.filter((pl) => !usedPriceLists.includes(pl.id)),
      // Include deleted price lists in the list of unused price lists.
      // These are not acquired from the OPTIONS call but are still guaranteed
      // to be valid.
      ...prices
        .map((p) => p.price_list)
        .filter((pl) => deletedPrices.includes(pl.id) && !usedPriceLists.includes(pl.id)),
    ];
  }, [priceLists, shownPrices, createdPrices, deletedPrices]);

  const clear = useCallback(() => {
    setChangedPrices({});
    setDeletedPrices([]);
    setCreatedPrices([]);
    setIsEditing(false);
  }, []);

  const save = useCallback(async () => {
    const action = api.operations["pricing.contrib:pricing-update"];
    if (!action) {
      throw new Error('Invalid action "pricing.contrib:pricing-update".');
    }

    setIsDisabled(true);
    const response = await action.call({
      params: { code },
      body: {
        updates: [
          ...Object.entries(changedPrices).map(([price_list, amount]) => ({
            price_list_id: parseInt(price_list),
            amount,
          })),
          ...createdPrices,
        ],
        deleted: deletedPrices.filter(
          (pl) => !createdPrices.map((p) => p.price_list_id).includes(pl),
        ),
      },
    });
    setIsDisabled(false);

    if (response.ok) {
      const updatedPriceLists = await response.json();
      setPrices(updatedPriceLists);
      clear();
    } else {
      console.error("[ARTICLE_PRICING_CARD]", response);
    }
  }, [api, prices, changedPrices, deletedPrices, createdPrices]);

  useEffect(() => {
    if (isEditing) {
      api.operations["pricing.contrib:pricing-options"]
        .call({ params: { code } })
        .then(async (response) => {
          if (response.ok) {
            setPriceLists(await response.json());
          }
        });
    }
  }, [isEditing]);

  return (
    <Card
      sx={{
        boxShadow: 0,
        borderRadius: 3,
        borderWidth: "1px",
        borderStyle: "solid",
        borderColor: theme.palette.divider,
        backgroundColor: theme.palette.background.paper,
        overflow: "visible",
        width: "100%",
      }}
    >
      <TableCardHeader
        isDisabled={isDisabled}
        isEditable={hasPermission(user, "pricing.change_price")}
        title="Prices"
        toggled={isEditing}
        onChange={async (isEditing) => {
          if (hasChanges) {
            if (
              await openDialog(
                t("Unsaved changes"),
                t("You have unsaved changes. Are you sure you want to discard your changes?"),
                {
                  ok: t("Discard changes"),
                  cancel: t("Cancel"),
                },
              )
            ) {
              clear();
            }
          } else {
            setIsEditing(isEditing);
          }
        }}
      />

      <Table count={shownPrices.length}>
        <TableHead>
          <TableRow>
            <TableCell colSpan={5} sx={{ px: 2, py: 1 }}>
              <Stack direction="row" spacing={1}>
                <IconButton size="small">
                  <FilterListIcon />
                </IconButton>
                <FilterChip
                  checked={showActive}
                  color="primary"
                  label={t("Active")}
                  onChange={setShowActive}
                />
                <FilterChip
                  checked={showInactive}
                  color="primary"
                  label={t("Inactive")}
                  onChange={setShowInactive}
                />
              </Stack>
            </TableCell>
          </TableRow>

          <TableRow>
            <TableHeading>{t("Site")}</TableHeading>
            <TableHeading>{t("Price List")}</TableHeading>
            <TableHeading align="right">{t("Amount")}</TableHeading>
            <TableHeading align="right">{t("Active")}</TableHeading>
            <TableHeading />
          </TableRow>
        </TableHead>

        <TableBody
          sx={{ ".MuiTableRow-root:last-child > .MuiTableCell-root": { borderBottom: "none" } }}
        >
          {shownPrices.length === 0 && (
            <TableRow>
              <TableCell align="center" colSpan={5} sx={{ color: "GrayText" }}>
                {showActive && showInactive
                  ? t("No prices")
                  : t("Some prices are hidden by filters")}
              </TableCell>
            </TableRow>
          )}

          {shownPrices.map((price, i) => (
            <ArticlePriceRow
              key={i}
              isDisabled={isDisabled}
              isEditing={isEditing}
              price={price}
              onChangeAmount={(amount) => {
                setChangedPrices((prev) => ({ ...prev, [price.price_list.id]: amount }));
              }}
              onDelete={() => {
                setDeletedPrices((prev) => [...prev, price.price_list.id]);
              }}
            />
          ))}

          {isEditing && (
            <>
              {createdPrices.map((price, i) => {
                const allPriceLists = [...priceLists, ...prices.map((p) => p.price_list)];
                const priceList = allPriceLists.find((pl) => pl.id === price.price_list_id)!;

                return (
                  <ArticlePriceRow
                    key={i}
                    isDisabled={isDisabled}
                    isEditing={true}
                    price={{ price_list: priceList, amount: price.amount }}
                    priceLists={[priceList, ...unusedPriceLists]}
                    onChangeAmount={(amount) => {
                      setCreatedPrices((prev) =>
                        prev.map((p, j) => (i === j ? { ...p, amount } : p)),
                      );
                    }}
                    onChangePriceList={(price_list) => {
                      setCreatedPrices((prev) =>
                        prev.map((p, j) => (i === j ? { ...p, price_list_id: price_list } : p)),
                      );
                    }}
                    onDelete={() => {
                      setCreatedPrices((prev) => prev.filter((_, j) => j !== i));
                    }}
                  />
                );
              })}
            </>
          )}
        </TableBody>
      </Table>

      {isEditing && (
        <Stack direction="row" justifyContent="space-between">
          <Box p={[1, 2, 3, 2]}>
            <Button
              disabled={isDisabled || unusedPriceLists.length === 0}
              size="medium"
              variant="outlined"
              onClick={() => {
                setCreatedPrices((prev) => [
                  ...prev,
                  { price_list_id: unusedPriceLists[0].id, amount: "" },
                ]);
              }}
            >
              {t("Add price")}
            </Button>
          </Box>

          <CardActions
            sx={{
              p: [1, 2, 3, 2],
              display: "flex",
              justifyContent: "flex-end",
            }}
          >
            <Button disabled={isDisabled} size="medium" variant="outlined" onClick={clear}>
              {t("Cancel")}
            </Button>
            <Button disabled={isDisabled} size="medium" variant="contained" onClick={save}>
              {t("Save")}
            </Button>
          </CardActions>
        </Stack>
      )}
    </Card>
  );
};
