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

// Define API endpoints for users
export const USERS_API_URL = location.protocol + location.host + '/api/users';
export const INSTRUCTORS_API_URL =
  location.protocol + location.host + '/api/instructors';
export const STUDENTS_API_URL =
  location.protocol + location.host + '/api/students';
export const TAS_API_URL = location.protocol + location.host + '/api/tas';

// Define TypeScript types for users matching ShipME API schema
// NOTE: These cannot be used like classes, they are essentially just validators for JSON objects
export type User = {
  // Uses POST body to determine required properties
  user_id?: number;
  first_name: string;
  last_name: string;
  email_address: string;
  type: string;
  instructor_id?: number; // Optional, used so that instructor_id can be retrieved in useAuth
  ta_id?: number; // Optional, used to that ta_id can be retrieved in useAuth
  student_id?: number; // Optional, used so that student_id can be retrieved in useAuth
  team_id?: number; // Optional POST param for type === 'student'
  section_id?: number; // Optional POST param for type === 'student'
  receive_emails?: string; // Optional, defaults to true
  is_admin?: string;
};
export type UserPatch = {
  first_name?: string;
  last_name?: string;
  email_address?: string;
  receive_emails?: string;
};
export type UserTableData = {
  user_id?: number | string;
  first_name: string;
  last_name: string;
  email_address: string;
  type: string;
  team_id?: number | string;
  section_id?: number | string;
};
export type Instructor = {
  instructor_id: number;
  user_id: number;
  is_admin: string;
};
export type InstructorPatch = {
  is_admin?: string;
};
export type TA = {
  ta_id: number;
  user_id: number;
};
export type Student = {
  student_id: number;
  user_id: number;
  team_id: number;
  section_id: number;
};
export type StudentPatch = {
  team_id?: number;
  section_id?: number;
};

// Define ColumnDef array for User type
export const defaultUserColumns: APIColumnDef<UserTableData, UserPatch>[] = [
  {
    accessorKey: 'user_id',
    header: 'ID',
    enableColumnFilter: false,
  },
  {
    accessorKey: 'first_name',
    header: 'First Name',
    sortDescFirst: false,
    enableColumnFilter: false,
    meta: {
      update: updateUser,
      element: 'input',
      allowedUserTypes: ['instructor', 'ta'],
    },
  },
  {
    accessorKey: 'last_name',
    header: 'Last Name',
    sortDescFirst: false,
    enableColumnFilter: false,
    meta: {
      update: updateUser,
      element: 'input',
      allowedUserTypes: ['instructor', 'ta'],
    },
  },
  {
    accessorKey: 'email_address',
    header: 'Email Address',
    sortDescFirst: false,
    enableColumnFilter: false,
    meta: {
      update: updateUser,
      element: 'input',
      allowedUserTypes: ['instructor', 'ta'],
    },
  },
  {
    accessorKey: 'type',
    header: 'Type',
    sortDescFirst: false,
    filterFn: 'equalsString',
  },
  {
    accessorKey: 'receive_emails',
    header: 'Notifications Enabled',
    filterFn: 'equalsString',
    meta: {
      transform: (value) => value.toString(),
    },
  },
];

// Function makes GET request to ShipME API /api/users endpoint and returns an array of Users
// To use only one filter, pass undefined (the word, not a string) for the ommitted parameter
// IN: type : optional type to filter users by
//     team_id : optional team_id to filter users by (where type is 'student')
//     section_id : optional section_id to filter users by (where type is 'student')
// OUT: array of retrieved Users
// THROWS: FetchError
export async function getUsers(
  type?: string,
  team_id?: number,
  section_id?: number
): Promise<User[]> {
  const _url = assembleQueryParams(USERS_API_URL, {
    type: type,
    team_id: team_id?.toString(),
    section_id: section_id?.toString(),
  });

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

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

// Function makes GET request to ShipME API /api/instructors endpoint and returns an array of Instructors
// OUT: array of retrieved Instructors
// THROWS: FetchError
export async function getInstructors(): Promise<Instructor[]> {
  const _url = new URL(INSTRUCTORS_API_URL);

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

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

// Function makes GET request to ShipME API /api/tas endpoint and returns an array of TAs
// OUT: array of retrieved TAs
// THROWS: FetchError
export async function getTAs(): Promise<TA[]> {
  const _url = new URL(TAS_API_URL);

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

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

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

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

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

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

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

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

// Function makes GET request to ShipME API /api/users/{user_id} endpoint and returns a single User
// IN: user_id : the user_id of the user to retrieve
// OUT: retrieved User
// THROWS: FetchError
export async function getUser(user_id: number): Promise<User> {
  const _url = new URL(USERS_API_URL + '/' + user_id);

  // Attempt GET /api/users/{user_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 user', response);
    }

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

// Function makes GET request to ShipME API /api/instructors/{instructor_id} endpoint and returns a single User
// IN: instructor_id : the instructor_id of the user to retrieve
// OUT: retrieved User
// THROWS: FetchError
export async function getInstructor(instructor_id: number): Promise<User> {
  const _url = new URL(INSTRUCTORS_API_URL + '/' + instructor_id);

  // Attempt GET /api/instructors/{instructor_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 user', response);
    }

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

// Function makes GET request to ShipME API /api/students/{student_id} endpoint and returns a single User
// IN: student_id : the student_id of the user to retrieve
// OUT: retrieved User
// THROWS: FetchError
export async function getStudent(student_id: number): Promise<User> {
  const _url = new URL(STUDENTS_API_URL + '/' + student_id);

  // Attempt GET /api/students/{student_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 user', response);
    }

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

// Function makes GET request to ShipME API /api/tas/{ta_id} endpoint and returns a single User
// IN: ta_id : the ta_id of the user to retrieve
// OUT: retrieved User
// THROWS: FetchError
export async function getTA(ta_id: number): Promise<User> {
  const _url = new URL(TAS_API_URL + '/' + ta_id);

  // Attempt GET /api/tas/{ta_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 user', response);
    }

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

// Function makes PATCH request to ShipME API /api/users/{user_id} endpoint and returns a single User
// IN: user_id : the user_id of the user to update
//     patchObject : UserPatch object with optional user data to be submitted
// OUT: response User object from API
// THROWS: FetchError
export async function updateUser(
  user_id: number,
  patchObject: UserPatch
): Promise<User> {
  const _url = new URL(USERS_API_URL + '/' + user_id);

  // Attempt PATCH /api/users/{user_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 user', response);
    }

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

// Function makes PATCH request to ShipME API /api/students/{student_id} endpoint and returns a single Student
// IN: student_id : the student_id of the student to update
//     patchObject : StudentPatch object with optional student data to be submitted
// OUT: response Student object from API
// THROWS: FetchError
export async function updateStudent(
  student_id: number,
  patchObject: StudentPatch
): Promise<Student> {
  const _url = new URL(STUDENTS_API_URL + '/' + student_id);

  // Attempt PATCH /api/students/{student_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 student', response);
    }

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

// Function makes PATCH request to ShipME API /api/instructor/{instructor_id} endpoint and returns a single instructor
// IN: instructor_id : the instructor_id of the instructor to update
//     patchObject : InstructorPatch object with optional instructor data to be submitted
// OUT: response instructor object from API
// THROWS: FetchError
export async function updateInstructor(
  instructor_id: number,
  patchObject: InstructorPatch
): Promise<Instructor> {
  const _url = new URL(INSTRUCTORS_API_URL + '/' + instructor_id);

  // Attempt PATCH /api/instructors/{instructor_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 instructor',
        response
      );
    }

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

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

  // Attempt DELETE /api/users/{user_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 user', response);
    }

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

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

  // Attempt DELETE /api/instructors/{instructor_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 instructor',
        response
      );
    }

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

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

  // Attempt DELETE /api/students/{student_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 student', response);
    }

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

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

  // Attempt DELETE /api/tas/{ta_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 TA', response);
    }

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