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

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 Stack from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";

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 allPriceLists = useMemo(
    () => [...priceLists, ...prices.map((p) => p.price_list)],
    [priceLists, prices],
  );

  const sortedRows = useMemo(() => {
    const existingRows = shownPrices.map((price) => ({ kind: "existing" as const, price }));
    const createdRows = !isEditing
      ? []
      : createdPrices
          .map((price, i) => {
            const priceList = allPriceLists.find((pl) => pl.id === price.price_list_id);
            if (!priceList) {
              return null;
            }
            return {
              kind: "created" as const,
              price: { price_list: priceList, amount: price.amount },
              index: i,
              priceLists: [priceList, ...unusedPriceLists],
            };
          })
          .filter(
            (
              row,
            ): row is {
              kind: "created";
              price: ArticlePriceSerializer;
              index: number;
              priceLists: ArticlePriceListSerializer[];
            } => row !== null,
          );

    return [...existingRows, ...createdRows].sort((a, b) => {
      const plA = a.price.price_list;
      const plB = b.price.price_list;
      return (
        plA.channel.localeCompare(plB.channel) ||
        plA.site_code.localeCompare(plB.site_code) ||
        plA.name.localeCompare(plB.name)
      );
    });
  }, [shownPrices, isEditing, createdPrices, allPriceLists, unusedPriceLists]);

  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>
          {/*
           * TODO: Redesign and show filters when needed
           * <TableRow>
           *   <TableCell colSpan={6} 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 sx={{ width: "30%" }}>{t("Price List")}</TableHeading>
            <TableHeading sx={{ width: "15%" }}>{t("Channel")}</TableHeading>
            <TableHeading sx={{ width: "15%" }}>{t("Site")}</TableHeading>
            <TableHeading align="right" sx={{ width: "20%" }}>
              {t("Amount")}
            </TableHeading>
            <TableHeading align="right" sx={{ width: "10%" }}>
              {t("Active")}
            </TableHeading>
            <TableHeading sx={{ width: "10%" }} />
          </TableRow>
        </TableHead>

        <TableBody
          sx={{ ".MuiTableRow-root:last-child > .MuiTableCell-root": { borderBottom: "none" } }}
        >
          {shownPrices.length === 0 && !(isEditing && createdPrices.length > 0) && (
            <TableRow>
              <TableCell align="center" colSpan={6} sx={{ color: "GrayText" }}>
                {t("No prices")}
              </TableCell>
            </TableRow>
          )}

          {sortedRows.map((row, i) => {
            if (row.kind === "existing") {
              return (
                <ArticlePriceRow
                  key={`existing-${row.price.price_list.id}-${i}`}
                  isDisabled={isDisabled}
                  isEditing={isEditing}
                  price={row.price}
                  onChangeAmount={(amount) => {
                    setChangedPrices((prev) => ({
                      ...prev,
                      [row.price.price_list.id]: amount,
                    }));
                  }}
                  onDelete={() => {
                    setDeletedPrices((prev) => [...prev, row.price.price_list.id]);
                  }}
                />
              );
            }

            return (
              <ArticlePriceRow
                key={`created-${row.price.price_list.id}-${row.index}`}
                isDisabled={isDisabled}
                isEditing={true}
                price={row.price}
                priceLists={row.priceLists}
                onChangeAmount={(amount) => {
                  setCreatedPrices((prev) =>
                    prev.map((p, j) => (row.index === j ? { ...p, amount } : p)),
                  );
                }}
                onChangePriceList={(price_list) => {
                  setCreatedPrices((prev) =>
                    prev.map((p, j) => (row.index === j ? { ...p, price_list_id: price_list } : p)),
                  );
                }}
                onDelete={() => {
                  setCreatedPrices((prev) => prev.filter((_, j) => j !== row.index));
                }}
              />
            );
          })}
        </TableBody>
      </Table>

      {isEditing && (
        <Stack
          direction="row"
          justifyContent="space-between"
          sx={{ borderTop: `1px solid ${theme.palette.divider}` }}
        >
          <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>
  );
};
