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

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

// Define TypeScript types for parts matching ShipME API schema
// NOTE: These cannot be used like classes, they are essentially just validators for JSON objects
export type Part = {
  // Uses POST body to determine required properties
  part_id?: number;
  url: string;
  part_num?: string;
  supplier_id: number;
  description: string;
  price: number;
};
export type PartPatch = {
  url?: string;
  part_num?: string;
  description?: string;
  price?: number;
};
export type PartTableData = {
  part_id?: number | string;
  url: string;
  part_num?: string;
  supplier_id: number | string;
  description: string;
  price: number | string;
};

// Function transforms an array of Part objects and replaces members with new formatted strings for table display
// IN: parts : Part array to be transformed
// OUT: transformed PartTableData array
export async function transformParts(parts: Part[]): Promise<PartTableData[]> {
  // Get data for foreign key objects
  const suppliers = await getSuppliers();

  parts.forEach((part: PartTableData) => {
    // Set the names of foreign key references in place of their IDs
    const supplier = suppliers.find(
      (supplier) => supplier.supplier_id === part.supplier_id
    );
    if (supplier) {
      part.supplier_id = supplier.name;
    }
  });

  return parts;
}

// Define ColumnDef array for Part type
export const defaultPartColumns: APIColumnDef<PartTableData, PartPatch>[] = [
  {
    accessorKey: 'part_id',
    header: 'ID',
    enableColumnFilter: false,
  },
  {
    accessorKey: 'url',
    header: 'URL',
    enableSorting: false,
    enableColumnFilter: false,
    meta: {
      update: updatePart,
      element: 'input',
      allowedUserTypes: ['instructor', 'ta'],
    },
  },
  {
    accessorKey: 'supplier_id',
    header: 'Supplier',
    sortDescFirst: false,
    filterFn: 'equalsString',
  },
  {
    accessorKey: 'description',
    header: 'Description',
    enableSorting: false,
    enableColumnFilter: false,
    meta: {
      update: updatePart,
      element: 'input',
      allowedUserTypes: ['instructor', 'ta'],
    },
  },
  {
    accessorKey: 'price',
    header: 'Price ($)',
    sortDescFirst: false,
    enableColumnFilter: false,
    meta: {
      update: updatePart,
      type: 'number',
      element: 'input',
      allowedUserTypes: ['instructor', 'ta'],
    },
  },
];

// Function makes GET request to ShipME API /api/parts endpoint and returns an array of Parts
// To use only one filter, pass undefined (the word, not a string) for the ommitted parameter
// IN: supplier_id : optional supplier_id to filter parts by
//     url : optional url to filter parts by (would return single part)
// OUT: array of retrieved Parts
// THROWS: FetchError
export async function getParts(
  supplier_id?: number,
  url?: string
): Promise<Part[]> {
  const _url = assembleQueryParams(PARTS_API_URL, {
    supplier_id: supplier_id?.toString(),
    url: url,
  });

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

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

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

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

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

// Function makes GET request to ShipME API /api/parts/{part_id} endpoint and returns a single Part
// IN: part_id : the part_id of the part to retrieve
// OUT: retrieved Part
// THROWS: FetchError
export async function getPart(part_id: number): Promise<Part> {
  const _url = new URL(PARTS_API_URL + '/' + part_id);

  // Attempt GET /api/parts/{part_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 part', response);
    }

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

// Function makes PATCH request to ShipME API /api/parts/{part_id} endpoint and returns a single Part
// IN: part_id : the part_id of the part to update
//     patchObject : PartPatch object with optional part data to be submitted
// OUT: response Part object from API
// THROWS: FetchError
export async function updatePart(
  part_id: number,
  patchObject: PartPatch
): Promise<Part> {
  const _url = new URL(PARTS_API_URL + '/' + part_id);

  // Attempt PATCH /api/parts/{part_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 part', response);
    }

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

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

  // Attempt DELETE /api/parts/{part_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 part', response);
    }

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