import { assembleQueryParams, FetchError } from '../utils/FetchHelper';
import { APIColumnDef } from '../utils/TableHelper';

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

// Define TypeScript types for suppliers matching ShipME API schema
// NOTE: These cannot be used like classes, they are essentially just validators for JSON objects
export type Supplier = {
  // Uses POST body to determine required properties
  supplier_id?: number;
  url: string;
  name: string;
};
export type SupplierPatch = {
  url?: string;
  name?: string;
};
export type SupplierTableData = {
  supplier_id?: number | string;
  url: string;
  name: string;
};

// Define ColumnDef array for Supplier type
export const defaultOrderColumns: APIColumnDef<
  SupplierTableData,
  SupplierPatch
>[] = [
  {
    accessorKey: 'supplier_id',
    header: 'ID',
    enableColumnFilter: false,
  },
  {
    accessorKey: 'url',
    header: 'URL',
    enableSorting: false,
    enableColumnFilter: false,
    meta: {
      update: updateSupplier,
      element: 'input',
      allowedUserTypes: ['instructor', 'ta'],
    },
  },
  {
    accessorKey: 'name',
    header: 'Name',
    sortDescFirst: false,
    enableColumnFilter: false,
    meta: {
      update: updateSupplier,
      element: 'input',
      allowedUserTypes: ['instructor', 'ta'],
    },
  },
];

// Function makes GET request to ShipME API /api/suppliers endpoint and returns an array of Suppliers
// IN: url : optional url to filter suppliers by (would return single supplier)
// OUT: array of retrieved Suppliers
// THROWS: FetchError
export async function getSuppliers(url?: string): Promise<Supplier[]> {
  const _url = assembleQueryParams(SUPPLIERS_API_URL, {
    url: url,
  });

  // Attempt GET /api/suppliers
  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 suppliers',
        response
      );
    }

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

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

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

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

// Function makes GET request to ShipME API /api/suppliers/{supplier_id} endpoint and returns a single Supplier
// IN: supplier_id : the supplier_id of the supplier to retrieve
// OUT: retrieved Supplier
// THROWS: FetchError
export async function getSupplier(supplier_id: number): Promise<Supplier> {
  const _url = new URL(SUPPLIERS_API_URL + '/' + supplier_id);

  // Attempt GET /api/suppliers/{supplier_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 supplier',
        response
      );
    }

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

// Function makes PATCH request to ShipME API /api/suppliers/{supplier_id} endpoint and returns a single Supplier
// IN: supplier_id : the supplier_id of the supplier to update
//     patchObject : SupplierPatch object with optional supplier data to be submitted
// OUT: response Supplier object from API
// THROWS: FetchError
export async function updateSupplier(
  supplier_id: number,
  patchObject: SupplierPatch
): Promise<Supplier> {
  const _url = new URL(SUPPLIERS_API_URL + '/' + supplier_id);

  // Attempt PATCH /api/suppliers/{supplier_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 supplier', response);
    }

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

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

  // Attempt DELETE /api/suppliers/{supplier_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 supplier', response);
    }

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