import { AddMemberInfo } from "../Components/AddMember";
import { ProjectRoleChange } from "../Components/ChangeRole";
import { projectTimes } from "../Components/GetProjectTimes";
import { ProjectMember } from "../Components/GetUsersInProject";
import {
  UpdateWeeklyReport,
  NewWeeklyReport,
  NewUser,
  User,
  Project,
  NewProject,
  WeeklyReport,
  StrNameChange,
} from "../Types/goTypes";

/**
 * Response object returned by API methods.
 */
export interface APIResponse<T> {
  /** Indicates whether the API call was successful */
  success: boolean;
  /** Optional message providing additional information or error description */
  message?: string;
  /** Optional data returned by the API method */
  data?: T;
}

/**
 * Interface defining methods that an instance of the API must implement.
 */
interface API {
  /**
   * Register a new user
   * @param {NewUser} user The user object to be registered
   * @returns {Promise<APIResponse<User>>} A promise containing the API response with the user data.
   */
  registerUser(user: NewUser): Promise<APIResponse<User>>;

  /**
   * Removes a user.
   * @param {string} username The username of the user to be removed.
   * @param {string} token The authentication token.
   * @returns {Promise<APIResponse<User>>} A promise containing the API response with the removed user data.
   */
  removeUser(username: string, token: string): Promise<APIResponse<User>>;

  /**
   * Check if user is project manager.
   * @param {string} username The username of the user.
   * @param {string} projectName The name of the project.
   * @param {string} token The authentication token.
   * @returns {Promise<APIResponse<boolean>>} A promise containing the API response indicating if the user is a project manager.
   */
  checkIfProjectManager(
    projectName: string,
    token: string,
  ): Promise<APIResponse<boolean>>;

  /** Logs in a user with the provided credentials.
   * @param {NewUser} NewUser The user object containing username and password.
   * @returns {Promise<APIResponse<string>>} A promise resolving to an API response with a token.
   */
  login(NewUser: NewUser): Promise<APIResponse<string>>;

  /**
   *  Renew the token
   * @param {string} token The current authentication token.
   * @returns {Promise<APIResponse<string>>} A promise resolving to an API response with a renewed token.
   */
  renewToken(token: string): Promise<APIResponse<string>>;

  /** Promote user to admin */

  /** Creates a new project.
   * @param {NewProject} project The project object containing name and description.
   * @param {string} token The authentication token.
   * @returns {Promise<APIResponse<Project>>} A promise resolving to an API response with the created project.
   */
  createProject(project: NewProject, token: string): Promise<APIResponse<void>>;

  /** Submits a weekly report
   * @param {NewWeeklyReport} weeklyReport The weekly report object.
   * @param {string} token The authentication token.
   * @returns {Promise<APIResponse<NewWeeklyReport>>} A promise resolving to an API response with the submitted report.
   */
  submitWeeklyReport(
    weeklyReport: NewWeeklyReport,
    token: string,
  ): Promise<APIResponse<string>>;

  /**
   * Updates a weekly report.
   * @param {UpdateWeeklyReport} weeklyReport The updated weekly report object.
   * @param {string} token The authentication token.
   * @returns {Promise<APIResponse<string>>} A promise containing the API response with the updated report.
   */
  updateWeeklyReport(
    weeklyReport: UpdateWeeklyReport,
    token: string,
  ): Promise<APIResponse<string>>;

  /** Gets a weekly report for a specific user, project and week.
   * Keep in mind that the user within the token needs to be PM
   * of the project to get the report, unless the user is the target user.
   * @param {string} projectName The name of the project.
   * @param {string} week The week number.
   * @param {string} token The authentication token.
   * @param {string} targetUser The username of the target user. Defaults to token user.
   * @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report.
   */
  getWeeklyReport(
    projectName: string,
    week: string,
    token: string,
    targetUser?: string,
  ): Promise<APIResponse<WeeklyReport>>;

  /**
   * Returns all the weekly reports for a user in a particular project
   * The username is derived from the token
   * @param {string} projectName The name of the project
   * @param {string} token The token of the user
   * @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
   */
  getAllWeeklyReportsForUser(
    projectName: string,
    token: string,
    targetUser?: string,
  ): Promise<APIResponse<WeeklyReport[]>>;

  /** Gets all the projects of a user
   * @param {string} username - The authentication token.
   * @param {string} token - The authentication token.
   * @returns {Promise<APIResponse<Project[]>>} A promise containing the API response with the user's projects.
   */
  getUserProjects(
    username: string,
    token: string,
  ): Promise<APIResponse<Project[]>>;

  /** Gets a project by its id.
   * @param {number} id The id of the project to retrieve.
   * @returns {Promise<APIResponse<Project>>} A promise resolving to an API response containing the project data.
   */
  getProject(id: number): Promise<APIResponse<Project>>;

  /** Gets a projects reported time
   * @param {string} projectName The name of the project.
   * @param {string} token The usertoken.
   * @returns {Promise<APIResponse<Times>>} A promise resolving to an API response containing the project times.
   */
  getProjectTimes(
    projectName: string,
    token: string,
  ): Promise<APIResponse<projectTimes>>;

  /** Gets a list of all users.
   * @param {string} token The authentication token of the requesting user.
   * @returns {Promise<APIResponse<string[]>>} A promise resolving to an API response containing the list of users.
   */
  getAllUsers(token: string): Promise<APIResponse<string[]>>;
  /** Gets all users in a project from name*/
  getAllUsersProject(
    projectName: string,
    token: string,
  ): Promise<APIResponse<ProjectMember[]>>;

  /** Gets all unsigned reports in a project.
   * @param {string} projectName The name of the project.
   * @param {string} token The authentication token.
   * @returns {Promise<APIResponse<WeeklyReport[]>>} A promise resolving to an API response containing the list of unsigned reports.
   */
  getUnsignedReportsInProject(
    projectName: string,
    token: string,
  ): Promise<APIResponse<WeeklyReport[]>>;

  /**
   * Changes the username of a user in the database.
   * @param {StrNameChange} data The object containing the previous and new username.
   * @param {string} token The authentication token.
   * @returns {Promise<APIResponse<void>>} A promise resolving to an API response.
   */
  changeUserName(
    data: StrNameChange,
    token: string,
  ): Promise<APIResponse<void>>;
  /**
   * Changes the role of a user in the database.
   * @param {RoleChange} roleInfo The object containing the previous and new username.
   * @param {string} token The authentication token.
   * @returns {Promise<APIResponse<void>>} A promise resolving to an API response.
   */
  changeUserRole(
    roleInfo: ProjectRoleChange,
    token: string,
  ): Promise<APIResponse<void>>;

  addUserToProject(
    addMemberInfo: AddMemberInfo,
    token: string,
  ): Promise<APIResponse<void>>;

  removeProject(
    projectName: string,
    token: string,
  ): Promise<APIResponse<string>>;

  /**
   * Signs a report. Keep in mind that the user which the token belongs to must be
   * the project manager of the project the report belongs to.
   *
   * @param {number} reportId The id of the report to sign
   * @param {string} token The authentication token
   */
  signReport(reportId: number, token: string): Promise<APIResponse<string>>;

  /**
   * Promotes a user to project manager within a project.
   *
   * @param {string} userName The username of the user to promote
   * @param {string} projectName The name of the project to promote the user in
   * @returns {Promise<APIResponse<string>} A promise resolving to an API response.
   */
  promoteToPm(
    userName: string,
    projectName: string,
    token: string,
  ): Promise<APIResponse<string>>;
}

/** An instance of the API */
export const api: API = {
  async registerUser(user: NewUser): Promise<APIResponse<User>> {
    try {
      const response = await fetch("/api/register", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(user),
      });

      if (!response.ok) {
        return {
          success: false,
          message: "Failed to register user: " + response.status,
        };
      } else {
        // const data = (await response.json()) as User; // The API does not currently return the user
        return { success: true };
      }
    } catch (e) {
      return {
        success: false,
        message: "Unknown error while registering user",
      };
    }
  },

  async removeUser(
    username: string,
    token: string,
  ): Promise<APIResponse<User>> {
    try {
      const response = await fetch(`/api/userdelete/${username}`, {
        method: "DELETE",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
        body: JSON.stringify(username),
      });
      if (!response.ok) {
        return { success: false, message: "Could not remove user" };
      } else {
        return { success: true };
      }
    } catch (e) {
      return { success: false, message: "Failed to remove user" };
    }
  },

  async checkIfProjectManager(
    projectName: string,
    token: string,
  ): Promise<APIResponse<boolean>> {
    try {
      const response = await fetch(
        `/api/checkIfProjectManager/${projectName}`,
        {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            Authorization: "Bearer " + token,
          },
        },
      );

      if (!response.ok) {
        return {
          success: false,
          message: "Failed to check if project manager",
        };
      } else {
        const data = (await response.json()) as boolean;
        return { success: true, data };
      }
    } catch (e) {
      return { success: false, message: "Failed to check if project manager" };
    }
  },

  async createProject(
    project: NewProject,
    token: string,
  ): Promise<APIResponse<void>> {
    try {
      const response = await fetch("/api/project", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
        body: JSON.stringify(project),
      });

      if (!response.ok) {
        return { success: false, message: "Failed to create project" };
      } else {
        return { success: true };
      }
    } catch (e) {
      return { success: false, message: "Failed to create project!" };
    }
  },

  async addUserToProject(
    addMemberInfo: AddMemberInfo,
    token: string,
  ): Promise<APIResponse<void>> {
    try {
      const response = await fetch(
        `/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`,
        {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
            Authorization: "Bearer " + token,
          },
        },
      );

      if (!response.ok) {
        return { success: false, message: "Failed to add member" };
      } else {
        return { success: true, message: "Added member" };
      }
    } catch (e) {
      return { success: false, message: "Failed to add member" };
    }
  },

  async renewToken(token: string): Promise<APIResponse<string>> {
    try {
      const response = await fetch("/api/loginrenew", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
      });

      if (!response.ok) {
        return { success: false, message: "Failed to renew token" };
      } else {
        const data = (await response.json()) as string;
        return { success: true, data };
      }
    } catch (e) {
      return { success: false, message: "Failed to renew token" };
    }
  },

  async changeUserRole(
    roleInfo: ProjectRoleChange,
    token: string,
  ): Promise<APIResponse<void>> {
    try {
      const response = await fetch("/api/ProjectRoleChange", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
        body: JSON.stringify(roleInfo),
      });

      if (!response.ok) {
        if (response.status === 403) {
          return { success: false, message: "Cannot change your own role" };
        }
        return { success: false, message: "Could not change role" };
      } else {
        return { success: true };
      }
    } catch (e) {
      return { success: false, message: "Could not change role" };
    }
  },

  async getUserProjects(
    username: string,
    token: string,
  ): Promise<APIResponse<Project[]>> {
    try {
      const response = await fetch(`/api/getUserProjects/${username}`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
      });

      if (!response.ok) {
        return Promise.resolve({
          success: false,
          message: "Failed to get user projects",
        });
      } else {
        const data = (await response.json()) as Project[];
        return Promise.resolve({ success: true, data });
      }
    } catch (e) {
      return Promise.resolve({
        success: false,
        message: "API fucked",
      });
    }
  },

  async getProjectTimes(
    projectName: string,
    token: string,
  ): Promise<APIResponse<projectTimes>> {
    try {
      const response = await fetch(`/api/getProjectTimes/${projectName}`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
      });

      if (!response.ok) {
        return Promise.resolve({
          success: false,
          message:
            "Fetch error: " + response.status + ", failed to get project times",
        });
      } else {
        const data = (await response.json()) as projectTimes;
        return Promise.resolve({ success: true, data });
      }
    } catch (e) {
      return Promise.resolve({
        success: false,
        message: "API error! Could not get times.",
      });
    }
  },

  async submitWeeklyReport(
    weeklyReport: NewWeeklyReport,
    token: string,
  ): Promise<APIResponse<string>> {
    try {
      const response = await fetch("/api/submitWeeklyReport", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
        body: JSON.stringify(weeklyReport),
      });

      if (!response.ok) {
        return {
          success: false,
          message: "Failed to submit weekly report",
        };
      }

      const data = await response.text();
      return { success: true, message: data };
    } catch (e) {
      return {
        success: false,
        message: "Failed to submit weekly report",
      };
    }
  },

  async updateWeeklyReport(
    weeklyReport: UpdateWeeklyReport,
    token: string,
  ): Promise<APIResponse<string>> {
    try {
      const response = await fetch("/api/updateWeeklyReport", {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
        body: JSON.stringify(weeklyReport),
      });

      if (!response.ok) {
        return {
          success: false,
          message: "Failed to update weekly report",
        };
      }

      const data = await response.text();
      return { success: true, message: data };
    } catch (e) {
      return {
        success: false,
        message: "Failed to update weekly report",
      };
    }
  },

  async getWeeklyReport(
    projectName: string,
    week: string,
    token: string,
    targetUser?: string,
  ): Promise<APIResponse<WeeklyReport>> {
    try {
      const response = await fetch(
        `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser}`,
        {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            Authorization: "Bearer " + token,
          },
        },
      );

      if (!response.ok) {
        return { success: false, message: "Failed to get weekly report" };
      } else {
        const data = (await response.json()) as WeeklyReport;
        return { success: true, data };
      }
    } catch (e) {
      return { success: false, message: "Failed to get weekly report" };
    }
  },

  async getAllWeeklyReportsForUser(
    projectName: string,
    token: string,
    targetUser?: string,
  ): Promise<APIResponse<WeeklyReport[]>> {
    try {
      const response = await fetch(
        `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser}`,
        {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            Authorization: "Bearer " + token,
          },
        },
      );

      if (!response.ok) {
        return {
          success: false,
          message:
            "Failed to get weekly reports for project: Response code " +
            response.status,
        };
      } else {
        const data = (await response.json()) as WeeklyReport[];
        return { success: true, data };
      }
    } catch (e) {
      return {
        success: false,
        message: "Failed to get weekly reports for project, unknown error",
      };
    }
  },

  async login(NewUser: NewUser): Promise<APIResponse<string>> {
    try {
      const response = await fetch("/api/login", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(NewUser),
      });

      if (!response.ok) {
        return { success: false, message: "Failed to login" };
      } else {
        const data = (await response.json()) as { token: string }; // Update the type of 'data'
        return { success: true, data: data.token };
      }
    } catch (e) {
      return Promise.resolve({ success: false, message: "Failed to login" });
    }
  },

  async getProject(id: number): Promise<APIResponse<Project>> {
    try {
      const response = await fetch(`/api/project/${id}`, {
        method: "GET",
      });

      if (!response.ok) {
        return {
          success: false,
          message: "Failed to get project: Response code " + response.status,
        };
      } else {
        const data = (await response.json()) as Project;
        return { success: true, data };
      }
      // The code below is garbage but satisfies the linter
      // This needs fixing, do not copy this pattern
    } catch (e: unknown) {
      return {
        success: false,
        message: "Failed to get project: " + (e as Error).toString(),
      };
    }
  },

  async getAllUsers(token: string): Promise<APIResponse<string[]>> {
    try {
      const response = await fetch("/api/users/all", {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
      });

      if (!response.ok) {
        return Promise.resolve({
          success: false,
          message: "Failed to get users",
        });
      } else {
        const data = (await response.json()) as string[];
        return Promise.resolve({ success: true, data });
      }
    } catch (e) {
      return Promise.resolve({
        success: false,
        message: "API is not ok",
      });
    }
  },
  //Gets all users in a project
  async getAllUsersProject(
    projectName: string,
    token: string,
  ): Promise<APIResponse<ProjectMember[]>> {
    try {
      const response = await fetch(`/api/getUsersProject/${projectName}`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
      });

      if (!response.ok) {
        return Promise.resolve({
          success: false,
          message: "Failed to get users",
        });
      } else {
        const data = (await response.json()) as ProjectMember[];
        return Promise.resolve({ success: true, data });
      }
    } catch (e) {
      return Promise.resolve({
        success: false,
        message: "API is not ok",
      });
    }
  },

  async getUnsignedReportsInProject(
    projectName: string,
    token: string,
  ): Promise<APIResponse<WeeklyReport[]>> {
    try {
      const response = await fetch(`/api/getUnsignedReports/${projectName}`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
      });

      if (!response.ok) {
        return {
          success: false,
          message:
            "Failed to get unsigned reports for project: Response code " +
            response.status,
        };
      } else {
        const data = (await response.json()) as WeeklyReport[];
        return { success: true, data };
      }
    } catch (e) {
      return {
        success: false,
        message: "Failed to get unsigned reports for project, unknown error",
      };
    }
  },

  async changeUserName(
    data: StrNameChange,
    token: string,
  ): Promise<APIResponse<void>> {
    try {
      const response = await fetch("/api/changeUserName", {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        return { success: false, message: "Failed to change username" };
      } else {
        return { success: true };
      }
    } catch (e) {
      return { success: false, message: "Failed to change username" };
    }
  },

  async removeProject(
    projectName: string,
    token: string,
  ): Promise<APIResponse<string>> {
    try {
      const response = await fetch(`/api/removeProject/${projectName}`, {
        method: "DELETE",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
      });

      if (!response.ok) {
        return Promise.resolve({
          success: false,
          message: "Failed to remove project",
        });
      } else {
        const data = await response.text();
        return Promise.resolve({ success: true, message: data });
      }
    } catch (e) {
      return Promise.resolve({
        success: false,
        message: "Failed to remove project",
      });
    }
  },

  async signReport(
    reportId: number,
    token: string,
  ): Promise<APIResponse<string>> {
    try {
      const response = await fetch(`/api/signReport/${reportId}`, {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + token,
        },
      });

      if (!response.ok) {
        return { success: false, message: "Failed to sign report" };
      } else {
        return { success: true, message: "Report signed" };
      }
    } catch (e) {
      return { success: false, message: "Failed to sign report" };
    }
  },

  async promoteToPm(
    userName: string,
    projectName: string,
    token: string,
  ): Promise<APIResponse<string>> {
    try {
      const response = await fetch(
        `/api/promoteToPm/${projectName}?userName=${userName}`,
        {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
            Authorization: "Bearer " + token,
          },
        },
      );
      if (!response.ok) {
        return {
          success: false,
          message: "Failed to promote user to project manager",
        };
      }
    } catch (e) {
      return {
        success: false,
        message: "Failed to promote user to project manager",
      };
    }
    return { success: true, message: "User promoted to project manager" };
  },
};