import { URL_CONSTANTS } from "@/constants/URLS";
import type {
  AuthUser,
  ChangePasswordPayload,
  CheckAvailabilityPayload,
  CheckAvailabilityResponse,
  ForgotPasswordRequestPayload,
  ForgotPasswordVerifyPayload,
  GenerateVerificationCodePayload,
  LoginPayload,
  LoginResponse,
  OtpPayload,
  OtpResponse,
  ResetLinkAccount,
  ResetTicket,
  ResolveResetLinkPayload,
  SendContactOtpPayload,
  SendContactOtpResponse,
  SetPasswordPayload,
  SignupPayload,
  SignupResponse,
  UpdateAccountNamePayload,
  UpdateContactPayload,
  UpdateContactResponse,
} from "@/types/auth";
import { client } from "@/utils/api";
import { unwrap } from "@/utils/endpoint";
import { ApiResponse } from "@edr/types";

export const authService = {
  login: async (body: LoginPayload) => {
    const res = await client.post<LoginResponse>(
      URL_CONSTANTS.AUTH.LOGIN,
      body,
    );
    return res.data;
  },

  createUser: async (body: SignupPayload) => {
    const res = await client.post<SignupResponse & ApiResponse<void>>(
      URL_CONSTANTS.USERS.SIGN_UP,
      body,
    );
    return res.data;
  },

  getMyInfo: async () => {
    const res = await client.get<AuthUser>(URL_CONSTANTS.USERS.ME);
    return res.data;
  },

  generateVerificationCode: async (body: GenerateVerificationCodePayload) => {
    const res = await client.patch<ApiResponse<string>>(
      URL_CONSTANTS.USERS.GENERATE_VERIFICATION_CODE,
      body,
    );
    return res.data.data;
  },

  setPassword: async (body: SetPasswordPayload) => {
    const res = await client.patch<ApiResponse<void>>(
      URL_CONSTANTS.USERS.SET_PASSWORD,
      body,
    );
    return res.data.data;
  },

  // The three calls below drive the unauthenticated forgot-password flow.
  // Responses under /api/auth are *flattened* by the API's response
  // interceptor ({ success, ...payload }), so there is no `.data.data` here.

  requestPasswordReset: async (body: ForgotPasswordRequestPayload) => {
    await client.post(URL_CONSTANTS.AUTH.FORGOT_PASSWORD_REQUEST, body);
  },

  verifyPasswordResetOtp: async (body: ForgotPasswordVerifyPayload) => {
    const res = await client.post<ResetTicket>(
      URL_CONSTANTS.AUTH.FORGOT_PASSWORD_VERIFY,
      body,
    );
    return { userId: res.data.userId, verificationCode: res.data.verificationCode };
  },

  /**
   * Validate a staff-issued reset link before showing the password form, and
   * pick up the ticket it carries. Rejected links (expired, already spent) fail
   * here rather than after the customer has typed a new password.
   */
  resolveResetLink: async (body: ResolveResetLinkPayload) => {
    const res = await client.post<ResetLinkAccount>(
      URL_CONSTANTS.AUTH.FORGOT_PASSWORD_RESOLVE_LINK,
      body,
    );
    return res.data;
  },

  /**
   * Spend the reset ticket. Distinct from `setPassword` above, which the
   * authenticated post-signup flow drives through `useAuth` — this one carries
   * its own userId/verificationCode and never touches the session.
   */
  resetPassword: async (body: SetPasswordPayload) => {
    await client.patch(URL_CONSTANTS.USERS.SET_PASSWORD, body);
  },

  checkAvailability: async (params: CheckAvailabilityPayload) => {
    const res = await client.get<CheckAvailabilityResponse>(
      URL_CONSTANTS.USERS.CHECK_AVAILABILITY,
      { params },
    );
    return res.data;
  },

  sendOTP: async (body: OtpPayload) => {
    const res = await client.post<ApiResponse<OtpResponse>>(
      URL_CONSTANTS.OTP.SEND,
      body,
    );
    return res.data.data;
  },

  verifyOTP: async (body: OtpPayload) => {
    const res = await client.post<ApiResponse<OtpResponse>>(
      URL_CONSTANTS.OTP.VERIFY,
      body,
    );
    return res.data.data;
  },

  /**
   * Change the signed-in user's password via IAM's own route: it verifies the
   * old password with argon and owns the credential write (deactivating the
   * previous one), so this app never touches password material.
   */
  changePassword: async (body: ChangePasswordPayload) => {
    await client.patch(URL_CONSTANTS.AUTH.CHANGE_PASSWORD, body);
  },

  // The three calls below manage the signed-in user's own account record
  // (`/api/me`), which is what OTPs and SMS notifications are delivered to.
  // Changing phone/email is OTP-gated server-side: the code goes to the NEW
  // value, and the write only lands once it is verified.

  sendContactOtp: async (body: SendContactOtpPayload) => {
    const res = await client.post<ApiResponse<SendContactOtpResponse>>(
      URL_CONSTANTS.ACCOUNT.CONTACT_OTP,
      body,
    );
    return unwrap(res.data);
  },

  updateContact: async (body: UpdateContactPayload) => {
    const res = await client.patch<ApiResponse<UpdateContactResponse>>(
      URL_CONSTANTS.ACCOUNT.CONTACT,
      body,
    );
    return unwrap(res.data);
  },

  updateAccountName: async (body: UpdateAccountNamePayload) => {
    const res = await client.patch<ApiResponse<{ success: true }>>(
      URL_CONSTANTS.ACCOUNT.NAME,
      body,
    );
    return unwrap(res.data);
  },

  refreshToken: async () => {
    const refreshTokenCookie = document.cookie
      .split("; ")
      .find((row) => row.startsWith("refresh-token="))
      ?.split("=")[1];
    const res = await client.post<ApiResponse<LoginResponse>>(
      URL_CONSTANTS.AUTH.REFRESH_TOKEN,
      { refreshToken: refreshTokenCookie },
    );
    return res.data.data;
  },

  logout: async () => {
    const res = await client.patch<ApiResponse<void>>(
      URL_CONSTANTS.AUTH.LOGOUT,
    );
    return res.data.data;
  },
};
