import { assembleQueryParams, FetchError } from '../utils/FetchHelper';
import { getInstructors, getUsers } from './Users';
import { getSuppliers } from './Suppliers';
import { APIColumnDef } from '../utils/TableHelper';

// Define API endpoint for orders
export const ORDERS_API_URL = location.protocol + location.host + '/api/orders';

// Define TypeScript types for orders matching ShipME API schema
// NOTE: These cannot be used like classes, they are essentially just validators for JSON objects
export type Order = {
  // Uses POST body to determine required properties
  order_id?: number;
  order_timestamp?: string;
  instructor_id: number;
  supplier_id: number;
  is_fulfilled: boolean;
  total: number;
};
export type OrderPatch = {
  instructor_id?: number;
  is_fulfilled?: boolean;
  total?: number;
};
export type OrderTableData = {
  order_id?: number | string;
  order_timestamp?: string;
  instructor_id: number | string;
  supplier_id: number | string;
  is_fulfilled: boolean | string;
  total: number | string;
};

// Function transforms an array of Order objects and replaces members with new formatted strings for table display
// IN: orders : Order array to be transformed
// OUT: transformed OrderTableData array
export async function transformOrders(
  orders: Order[]
): Promise<OrderTableData[]> {
  // Get data for foreign key objects
  const users = await getUsers('instructor');
  const instructors = await getInstructors();
  const suppliers = await getSuppliers();

  orders.forEach((order: OrderTableData) => {
    order.order_timestamp = new Date(
      order.order_timestamp as string
    ).toLocaleString();
    order.is_fulfilled = order.is_fulfilled ? 'fulfilled' : 'unfulfilled';

    // Set the names of foreign key references in place of their IDs
    const instructor = instructors.find(
      (instructor) => instructor.instructor_id === order.instructor_id
    );
    const user = users.find((user) => user.user_id === instructor?.user_id);
    if (user) {
      order.instructor_id = user.first_name + ' ' + user.last_name;
    }
    const supplier = suppliers.find(
      (supplier) => supplier.supplier_id === order.supplier_id
    );
    if (supplier) {
      order.supplier_id = supplier.name;
    }
  });

  return orders;
}

// Define ColumnDef array for Order type
export const defaultOrderColumns: APIColumnDef<OrderTableData, OrderPatch>[] = [
  {
    accessorKey: 'order_id',
    header: 'ID',
    enableColumnFilter: false,
  },
  {
    accessorKey: 'order_timestamp',
    header: 'Timestamp',
    sortDescFirst: false,
    enableColumnFilter: false,
    meta: {
      transform: (value) => new Date(value).toLocaleString(),
    },
  },
  {
    accessorKey: 'instructor_id',
    header: 'Instructor',
    sortDescFirst: false,
    filterFn: 'equalsString',
  },
  {
    accessorKey: 'supplier_id',
    header: 'Supplier',
    sortDescFirst: false,
    filterFn: 'equalsString',
  },
  {
    accessorKey: 'is_fulfilled',
    header: 'Fulfillment Status',
    filterFn: 'equalsString',
    meta: {
      update: updateOrder,
      transform: (value: number) => (value ? 'fulfilled' : 'unfulfilled'),
      type: 'boolean',
      element: 'select',
      values: {
        false: 'unfulfilled',
        true: 'fulfilled',
      },
      allowedUserTypes: ['instructor'],
    },
  },
  {
    accessorKey: 'total',
    header: 'Total ($)',
    sortDescFirst: false,
    enableColumnFilter: false,
    meta: {
      update: updateOrder,
      type: 'number',
      element: 'input',
      allowedUserTypes: ['instructor'],
    },
  },
];

// Function makes GET request to ShipME API /api/orders endpoint and returns an array of Orders
// To use only one filter, pass undefined (the word, not a string) for the ommitted parameter
// IN: instructor_id : optional instructor_id to filter orders by
//     supplier_id : optional supplier_id to filter orders by
// OUT: array of retrieved Orders
// THROWS: FetchError
export async function getOrders(
  instructor_id?: number,
  supplier_id?: number
): Promise<Order[]> {
  const _url = assembleQueryParams(ORDERS_API_URL, {
    instructor_id: instructor_id?.toString(),
    supplier_id: supplier_id?.toString(),
  });

  // Attempt GET /api/orders
  try {
    const response = await fetch(_url.toString());
    // Catch response codes other than 200-class
    if (!response.ok) {
      const errorText = await response.text();
      throw new FetchError(errorText || 'Failed to retrieve orders', response);
    }

    // Return orders as array of type Order
    const orders: Order[] = await response.json();
    return orders;
  } catch (error) {
    throw new FetchError((error as Error).message);
  }
}

// Function makes POST request to ShipME API /api/orders endpoint and returns a single Order
// IN: order : Order object with order data to be submitted
// OUT: response Order object from API
// THROWS: FetchError
export async function createOrder(order: Order): Promise<Order> {
  const _url = new URL(ORDERS_API_URL);

  // Attempt POST /api/orders
  try {
    const response = await fetch(_url.toString(), {
      headers: {
        'Content-Type': 'application/json',
      },
      method: 'POST',
      body: JSON.stringify(order),
    });
    // Catch response codes other than 200-class
    if (!response.ok) {
      const errorText = await response.text();
      throw new FetchError(errorText || 'Failed to create order', response);
    }

    // Return order as type Order
    order = await response.json();
    return order;
  } catch (error) {
    throw new FetchError((error as Error).message);
  }
}

// Function makes GET request to ShipME API /api/orders/{order_id} endpoint and returns a single Order
// IN: order_id : the order_id of the order to retrieve
// OUT: retrieved Order
// THROWS: FetchError
export async function getOrder(order_id: number): Promise<Order> {
  const _url = new URL(ORDERS_API_URL + '/' + order_id);

  // Attempt GET /api/orders/{order_id}
  try {
    const response = await fetch(_url.toString());
    // Catch response codes other than 200-class
    if (!response.ok) {
      const errorText = await response.text();
      throw new FetchError(errorText || 'Failed to retrieve order', response);
    }

    // Return order as type Order
    const order: Order = await response.json();
    return order;
  } catch (error) {
    throw new FetchError((error as Error).message);
  }
}

// Function makes PATCH request to ShipME API /api/orders/{order_id} endpoint and returns a single Order
// IN: order_id : the order_id of the order to update
//     patchObject : OrderPatch object with optional order data to be submitted
// OUT: response Order object from API
// THROWS: FetchError
export async function updateOrder(
  order_id: number,
  patchObject: OrderPatch
): Promise<Order> {
  const _url = new URL(ORDERS_API_URL + '/' + order_id);

  // Attempt PATCH /api/orders/{order_id}
  try {
    const response = await fetch(_url.toString(), {
      headers: {
        'Content-Type': 'application/json',
      },
      method: 'PATCH',
      body: JSON.stringify(patchObject),
    });
    // Catch response codes other than 200-class
    if (!response.ok) {
      const errorText = await response.text();
      throw new FetchError(errorText || 'Failed to update order', response);
    }

    // Return order as type Order
    const order: Order = await response.json();
    return order;
  } catch (error) {
    throw new FetchError((error as Error).message);
  }
}

// Function makes DELETE request to ShipME API /api/orders/{order_id} endpoint and returns a Response
// IN: order_id : the order_id of the order to delete
// OUT: Fetch Response object
// THROWS: FetchError
export async function deleteOrder(order_id: number): Promise<Response> {
  const _url = new URL(ORDERS_API_URL + '/' + order_id);

  // Attempt DELETE /api/orders/{order_id}
  try {
    const response = await fetch(_url.toString(), {
      method: 'DELETE',
    });
    // Catch response codes other than 200-class
    if (!response.ok) {
      const errorText = await response.text();
      throw new FetchError(errorText || 'Failed to delete order', response);
    }

    return response;
  } catch (error) {
    throw new FetchError((error as Error).message);
  }
}
