import {
  useState,
  useEffect,
  Fragment,
  useMemo,
  ChangeEvent,
  useRef,
} from 'react';
import { cloneDeep } from 'lodash';
import { Form, InputGroup, Button, Modal, Table } from 'react-bootstrap';
import ContentCard from '../components/shared/ContentCard';
import { DataTable } from '../components/shared/DataTable';
import {
  Order,
  defaultOrderColumns,
  getOrders,
  createOrder,
  transformOrders,
  OrderTableData,
  updateOrder,
  deleteOrder,
} from '../api/Orders';
import {
  Request,
  RequestTableData,
  defaultRequestColumns,
  getRequests,
  transformRequests,
  updateRequest,
} from '../api/Requests';
import { getSuppliers, Supplier } from '../api/Suppliers';
import { useAuth } from '../auth/AuthProvider';
import { useToast } from '../components/Toast/ToastContext';

export const Orders = () => {
  const { user } = useAuth();
  const { addToast } = useToast();

  // Used to trigger full update of page data
  const [updateFlag, setUpdateFlag] = useState(false);

  // State variables for each type of API data required by this page
  const [suppliers, setSuppliers] = useState<Supplier[]>([]);
  const [baseRequests, setBaseRequests] = useState<Request[]>([]);
  const [transformedBaseRequests, setTransformedBaseRequests] = useState<
    RequestTableData[]
  >([]);
  const [incomingRequests, setIncomingRequests] = useState<Request[]>([]);
  const [orders, setOrders] = useState<Order[]>([]);
  const [transformedOrders, setTransformedOrders] = useState<OrderTableData[]>(
    []
  );
  useEffect(() => {
    const retrieveBaseData = async () => {
      // Retrieve all suppliers
      const suppliers = await getSuppliers();
      setSuppliers(suppliers);

      // Retrieve all requests
      const baseRequests = await getRequests();
      setBaseRequests(baseRequests);
      // Transform requests
      const transformedRequests = await transformRequests(
        baseRequests.map((request) => cloneDeep(request))
      );
      setTransformedBaseRequests(transformedRequests);

      // Retrieve incoming requests
      // Request is "incoming" if it has no associated order_id and has a status of "requested" or "approved"
      const incomingRequests = await getRequests();
      setIncomingRequests(
        incomingRequests.filter(
          (request) =>
            request.order_id === null &&
            ['requested', 'approved', 'reordered'].includes(request.status)
        )
      );

      // Retrieve all orders
      const orders = await getOrders();
      // Mark orders as fulfilled if all requests are delivered
      orders.forEach(async (order) => {
        if (!order.is_fulfilled) {
          const associatedRequests = baseRequests.filter(
            (request) => request.order_id === order.order_id
          );

          if (
            associatedRequests.length &&
            associatedRequests.every(
              (request) => request.status === 'delivered'
            )
          ) {
            order.is_fulfilled = true;
            await updateOrder(order.order_id as number, {
              is_fulfilled: true,
            });
          }
        }
      });
      setOrders(orders);
      // Transform orders
      const transformedOrders = await transformOrders(
        orders.map((order) => cloneDeep(order))
      );
      setTransformedOrders(transformedOrders);
    };

    retrieveBaseData();
    setUpdateFlag(false);
  }, [updateFlag]);

  // Keep track of selected supplier and update on change in Form.Select element
  const [selectedSupplier, setSelectedSupplier] = useState<number>();
  const updateSelectedSupplier = (event: ChangeEvent<HTMLSelectElement>) => {
    setSelectedSupplier(Number(event.target.value));
  };
  // Filter the suppliers actually referenced by the incoming requests
  const uniqueSuppliers = useMemo(() => {
    let list: Supplier[] = [];
    incomingRequests.forEach((request) => {
      list.push(
        suppliers.find(
          (supplier) => supplier.supplier_id === request.supplier_id
        ) as Supplier
      );
    });

    let unique = Array.from(
      new Map(list.map((supplier) => [supplier.supplier_id, supplier]))
    ).map((item) => item[1]);

    return unique;
  }, [suppliers, incomingRequests]);
  // Set an initial value for selectedSupplier
  useEffect(() => {
    if (
      uniqueSuppliers.length &&
      (!selectedSupplier ||
        !uniqueSuppliers
          .map((supplier) => supplier.supplier_id)
          .includes(selectedSupplier))
    ) {
      setSelectedSupplier(uniqueSuppliers[0].supplier_id);
    }
  }, [uniqueSuppliers]);

  const [transformedIncomingRequests, setTransformedIncomingRequests] =
    useState<RequestTableData[]>([]);
  // Update transformedIncomingRequests when selectedSupplier changes
  useEffect(() => {
    const retrieveRequests = async () => {
      if (selectedSupplier) {
        // Filter requests where supplier_id matches Form.Select value
        const filteredRequests = incomingRequests
          .filter((request) => request.supplier_id === selectedSupplier)
          .map((request) => cloneDeep(request));

        const transformed = await transformRequests(filteredRequests);
        setTransformedIncomingRequests(transformed);
      }
    };

    retrieveRequests();
  }, [incomingRequests, selectedSupplier]);

  // Updates all requests matching selected supplier as "approved", except those marked as "reordered"
  const approveAllRequests = async () => {
    const filteredRequests = incomingRequests.filter(
      (request) => request.supplier_id === selectedSupplier
    );

    try {
      // Mark requests as "approved" where appropriate
      for (let i = 0; i < filteredRequests.length; i++) {
        const currentRequest = filteredRequests[i];
        if (currentRequest.status !== 'reordered') {
          await updateRequest(currentRequest.request_id as number, {
            status: 'approved',
          });
        }
      }

      addToast('Incoming requests marked as approved.', 'success');

      setUpdateFlag(true);
    } catch {
      addToast('Failed to update incoming requests.', 'danger');
    }
  };

  // When incoming requests are modified, check their status and find all where status is "approved"
  const approvedRequests = useMemo(() => {
    let approved: Request[] = [];
    transformedIncomingRequests.forEach((request) => {
      if (['approved', 'reordered'].includes(request.status)) {
        approved.push(
          baseRequests.find(
            (baseRequest) => baseRequest.request_id === request.request_id
          ) as Request
        );
      }
    });

    return approved;
  }, [transformedIncomingRequests]);

  // State variables and functions for "Create New Order" modal
  const [createOrderModalShown, setCreateOrderModalShown] = useState(false);
  const showCreateOrderModal = () => setCreateOrderModalShown(true);
  const hideCreateOrderModal = () => setCreateOrderModalShown(false);
  // "Finalize Order" modal
  const [finalizeOrderModalShown, setFinalizeOrderModalShown] = useState(false);
  const showFinalizeOrderModal = () => setFinalizeOrderModalShown(true);
  const hideFinalizeOrderModal = () => setFinalizeOrderModalShown(false);
  const finalizeOrderForm = useRef<HTMLFormElement | null>(null);
  const [finalizeOrderFormValidated, setFinalizeOrderFormValidated] =
    useState(false);
  const finalizedOrderTotal = useRef<HTMLInputElement | null>(null);
  // Function advances to step 2 of the order creation modal flow
  const advanceOrderCreationModals = () => {
    hideCreateOrderModal();
    showFinalizeOrderModal();
  };
  // Function finalizes a new order
  const finalizeOrder = async () => {
    // Validate form
    if (
      finalizeOrderForm.current &&
      !finalizeOrderForm.current.checkValidity()
    ) {
      setFinalizeOrderFormValidated(true);
      return;
    }

    try {
      // Assemble order properties and create order
      const newOrder: Order = {
        instructor_id: user?.instructor_id as number,
        supplier_id: selectedSupplier as number,
        is_fulfilled: false,
        total: Number(finalizedOrderTotal.current?.value as string),
      };
      const order = await createOrder(newOrder);

      // Mark each associated request as 'ordered'
      approvedRequests.forEach(async (request) => {
        await updateRequest(request.request_id as number, {
          order_id: order.order_id,
          status: 'ordered',
        });
      });

      addToast('Order has been created.', 'success');

      setUpdateFlag(true);

      hideFinalizeOrderModal();
    } catch {
      addToast('Failed to create new order.', 'danger');
    }
  };

  // State variables and functions for "Cancel Order" modal
  const [cancelOrderModalShown, setCancelOrderModalShown] = useState(false);
  const showCancelOrderModal = (order_id: number) => {
    orderIdToCancel.current = order_id;

    setCancelOrderModalShown(true);
  };
  const hideCancelOrderModal = () => setCancelOrderModalShown(false);
  const orderIdToCancel = useRef<number>();

  // Updates all associated requests prior to order deletion
  const unassociateRequests = async () => {
    const associatedRequests = baseRequests.filter(
      (request) => request.order_id === orderIdToCancel.current
    );

    for (let i = 0; i < associatedRequests.length; i++) {
      await updateRequest(associatedRequests[i].request_id as number, {
        order_id: 'unset',
        status: 'cancelled',
      });
    }
  };
  // Allows canceling an order
  const cancelOrder = async () => {
    try {
      await unassociateRequests();
      await deleteOrder(orderIdToCancel.current as number);

      addToast('Order has been cancelled.', 'success');

      setUpdateFlag(true);

      hideCancelOrderModal();
    } catch {
      addToast('Failed to cancel order.', 'danger');
    }
  };

  return (
    <Fragment>
      <Modal show={createOrderModalShown} onHide={hideCreateOrderModal}>
        <Modal.Header closeButton>
          <Modal.Title>Create New Order</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <div className='mb-3'>
            Please carefully review the approved requests for the selected
            supplier (
            <b>
              {
                suppliers.find(
                  (supplier) => supplier.supplier_id === selectedSupplier
                )?.name
              }
            </b>
            ). If you need to change a request before creating an order, click
            "Cancel" and edit as necessary.
          </div>
          <Table responsive bordered size='sm'>
            <thead className='text-end'>
              <tr>
                <th style={{ whiteSpace: 'nowrap' }}>Request ID</th>
                <th style={{ whiteSpace: 'nowrap' }}>Team</th>
                <th style={{ whiteSpace: 'nowrap' }}># Items</th>
                <th style={{ whiteSpace: 'nowrap' }}>Subtotal ($)</th>
              </tr>
            </thead>
            <tbody className='text-end table-group-divider'>
              {approvedRequests.map((request) => (
                <tr>
                  <td>{request.request_id}</td>
                  <td>
                    {
                      transformedBaseRequests.find(
                        (req) => req.request_id === request.request_id
                      )?.team_id
                    }
                  </td>
                  <td>{request.quantity}</td>
                  <td>{request.subtotal.toFixed(2)}</td>
                </tr>
              ))}
            </tbody>
            <tfoot className='text-end table-group-divider'>
              <tr>
                <td colSpan={3}>
                  <b>Approx. Order Total ($)</b>
                </td>
                <td>
                  {approvedRequests
                    .reduce((sum, { subtotal }) => sum + subtotal, 0)
                    .toFixed(2)}
                </td>
              </tr>
            </tfoot>
          </Table>
          <div className='mb-2'>
            When you have confirmed the request information, click "Create
            Order". A new dialog will appear that displays the link to each
            part.
          </div>
        </Modal.Body>
        <Modal.Footer>
          <Button variant='secondary' onClick={hideCreateOrderModal}>
            Cancel
          </Button>
          <Button variant='primary' onClick={advanceOrderCreationModals}>
            Create Order
          </Button>
        </Modal.Footer>
      </Modal>

      <Modal
        show={finalizeOrderModalShown}
        onHide={hideFinalizeOrderModal}
        backdrop='static'
      >
        <Form
          ref={finalizeOrderForm}
          onSubmit={(event) => {
            event.preventDefault();
            finalizeOrder();
          }}
          noValidate
          validated={finalizeOrderFormValidated}
        >
          <Modal.Header closeButton>
            <Modal.Title>Finalize Order</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <div className='mb-3'>
              Click each of the part links below to open them in a new tab. Add
              them to your{' '}
              <b>
                {
                  suppliers.find(
                    (supplier) => supplier.supplier_id === selectedSupplier
                  )?.name
                }
              </b>{' '}
              cart (making sure to select the correct quantity), place the
              order, then return to this page to finalize the order.
            </div>
            <Table responsive bordered size='sm' className='mb-2'>
              <thead>
                <tr>
                  <th style={{ whiteSpace: 'nowrap' }}>#</th>
                  <th style={{ whiteSpace: 'nowrap' }}>Part</th>
                  <th style={{ whiteSpace: 'nowrap' }}>Quantity</th>
                </tr>
              </thead>
              <tbody className='table-group-divider'>
                {approvedRequests.map((request, index) => {
                  const part = (
                    transformedIncomingRequests.find(
                      (req) => req.request_id === request.request_id
                    )?.part_id as string
                  ).split('\u001E');

                  const description = part[0];
                  const url = part[1];

                  return (
                    <tr>
                      <td>{index + 1}</td>
                      <td>
                        <a href={url} target='_blank' rel='noopener noreferrer'>
                          {description}
                        </a>
                      </td>
                      <td>{request.quantity}</td>
                    </tr>
                  );
                })}
              </tbody>
            </Table>
            <hr />
            <div className='mb-2'>
              If the final order total is different than the expected total,
              edit the total below:
            </div>
            <InputGroup className='mb-2' hasValidation>
              <InputGroup.Text>$</InputGroup.Text>
              <Form.Control
                ref={finalizedOrderTotal}
                required
                type='number'
                step='0.01'
                min='0.01'
                placeholder={approvedRequests
                  .reduce((sum, { subtotal }) => sum + subtotal, 0)
                  .toFixed(2)
                  .toString()}
                defaultValue={approvedRequests
                  .reduce((sum, { subtotal }) => sum + subtotal, 0)
                  .toFixed(2)
                  .toString()}
              />
              <Form.Control.Feedback type='invalid'>
                Please enter a valid dollar amount.
              </Form.Control.Feedback>
            </InputGroup>
          </Modal.Body>
          <Modal.Footer>
            <Button variant='secondary' onClick={hideFinalizeOrderModal}>
              Cancel
            </Button>
            <Button type='submit' variant='primary'>
              Finalize Order
            </Button>
          </Modal.Footer>
        </Form>
      </Modal>

      <Modal show={cancelOrderModalShown} onHide={hideCancelOrderModal}>
        <Modal.Header closeButton>
          <Modal.Title>Cancel Order</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <div className='mb-3'>
            Are you sure you want to cancel order #{orderIdToCancel.current}?
            All requests associated with the order will be marked as "cancelled"
            and the order record will be deleted.
          </div>
          <div>
            <span className='text-danger'>
              <b>Warning:</b> this action is irreversible.
            </span>{' '}
            Once the order has been cancelled, you can find the previously
            associated requests and mark them as "reordered" to create a new
            order with them.
          </div>
        </Modal.Body>
        <Modal.Footer>
          <Button variant='secondary' onClick={hideCancelOrderModal}>
            Cancel
          </Button>
          <Button variant='primary' onClick={cancelOrder}>
            Cancel Order
          </Button>
        </Modal.Footer>
      </Modal>

      <ContentCard
        title='Incoming Requests'
        description='Select a supplier to view incoming requests, and update their
            statuses using the "Edit" buttons shown in each row. When you have
            approved at least one request for the selected supplier, click "Create New Order" to start a
            new order.'
      >
        <div className='d-flex flex-column flex-sm-row align-items-sm-center gap-2 gap-sm-3'>
          <div className='flex-grow-1'>
            <InputGroup>
              <InputGroup.Text>Selected supplier</InputGroup.Text>
              <Form.Select
                defaultValue={0}
                value={selectedSupplier}
                onChange={updateSelectedSupplier}
                disabled={uniqueSuppliers.length ? false : true}
              >
                {uniqueSuppliers.length ? (
                  <option value={0} disabled>
                    Select a supplier...
                  </option>
                ) : (
                  <option value={0} disabled>
                    No suppliers with incoming requests
                  </option>
                )}
                {uniqueSuppliers.map((supplier) => {
                  return (
                    <option
                      key={supplier.supplier_id}
                      value={supplier.supplier_id}
                    >
                      {supplier.name}
                    </option>
                  );
                })}
              </Form.Select>
            </InputGroup>
          </div>
          {user?.type === 'instructor' && (
            <Fragment>
              <div className='text-md-end'>
                <Button
                  variant='success'
                  className='w-100 w-md-auto'
                  disabled={
                    transformedIncomingRequests.filter((request) =>
                      ['approved', 'reordered'].includes(request.status)
                    ).length === transformedIncomingRequests.length
                  }
                  onClick={approveAllRequests}
                >
                  Approve All Requests
                </Button>
              </div>
              <div className='text-md-end'>
                <Button
                  variant='primary'
                  className='w-100 w-md-auto'
                  disabled={!approvedRequests.length}
                  onClick={showCreateOrderModal}
                >
                  Create New Order
                </Button>
              </div>
            </Fragment>
          )}
        </div>
        <hr />
        <DataTable
          data={transformedIncomingRequests}
          setData={
            user?.type === 'instructor'
              ? setTransformedIncomingRequests
              : undefined
          }
          columns={defaultRequestColumns.filter(
            // Filter out the supplier_id and order_id columns, since the Form.Select above handles supplier filtering
            (columnDef) =>
              // @ts-ignore
              !['supplier_id', 'order_id'].includes(columnDef.accessorKey)
          )}
        />
      </ContentCard>

      <ContentCard
        title='Orders'
        description={
          "View all orders placed to date, and expand an order's row to display the requests associated with that order or cancel the order entirely."
        }
      >
        <DataTable
          data={transformedOrders}
          setData={setTransformedOrders}
          columns={defaultOrderColumns}
          padding
          expandable
          collapseData={transformedBaseRequests}
          collapseDataKey='order_id'
          children={(requests) => (
            <Fragment>
              {requests && requests.length && (
                <Fragment>
                  {!(
                    transformedOrders.find(
                      (order) => order.order_id === requests[0].order_id
                    )?.is_fulfilled === 'fulfilled'
                  ) &&
                    user?.type !== 'ta' && (
                      <Fragment>
                        <Button
                          variant='outline-danger'
                          className='w-100'
                          onClick={() => {
                            showCancelOrderModal(
                              requests[0].order_id as number
                            );
                          }}
                        >
                          Cancel Order
                        </Button>
                        <hr />
                      </Fragment>
                    )}
                  <DataTable
                    data={requests as Request[]}
                    setData={setTransformedBaseRequests}
                    columns={defaultRequestColumns.filter(
                      // Filter out the supplier_id and order_id columns, since the Order is the parent object
                      (columnDef) =>
                        !['supplier_id', 'order_id'].includes(
                          // @ts-ignore
                          columnDef.accessorKey
                        )
                    )}
                  />
                </Fragment>
              )}
            </Fragment>
          )}
        />
      </ContentCard>
    </Fragment>
  );
};
