const BASE = '/api/experience';

async function request(method: string, path: string, body?: unknown): Promise<Response> {
  const res = await fetch(`${BASE}${path}`, {
    method,
    headers: body ? { 'Content-Type': 'application/json' } : undefined,
    body: body ? JSON.stringify(body) : undefined,
    credentials: 'same-origin',
  });
  return res;
}

// --- Shared ---

export async function identifyUser(verificationId: string): Promise<void> {
  const res = await request('POST', '/identification', { verificationId });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message || `Identification failed (${res.status})`);
  }
}

export async function submitInteraction(): Promise<string> {
  const res = await request('POST', '/submit');
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message || `Submit failed (${res.status})`);
  }
  const data = await res.json();
  return data.redirectTo;
}

// --- Sign-in ---

export async function initInteraction(): Promise<void> {
  const res = await request('PUT', '', { interactionEvent: 'SignIn' });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message || `Failed to initialize sign-in (${res.status})`);
  }
}

function detectIdentifierType(input: string): 'email' | 'username' {
  return input.includes('@') ? 'email' : 'username';
}

export async function verifyPassword(
  identifier: string,
  password: string
): Promise<string> {
  const type = detectIdentifierType(identifier);
  const res = await request('POST', '/verification/password', {
    identifier: { type, value: identifier },
    password,
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    if (res.status === 422) {
      throw new Error('Invalid credentials');
    }
    throw new Error(err.message || `Authentication failed (${res.status})`);
  }
  const data = await res.json();
  return data.verificationId;
}

export class MfaRequiredError extends Error {
  constructor() {
    super('MFA verification required');
    this.name = 'MfaRequiredError';
  }
}

export async function signIn(identifier: string, password: string): Promise<string> {
  await initInteraction();
  const verificationId = await verifyPassword(identifier, password);
  await identifyUser(verificationId);
  const res = await request('POST', '/submit');
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    if (err.code === 'user.missing_mfa') {
      throw new MfaRequiredError();
    }
    throw new Error(err.message || `Submit failed (${res.status})`);
  }
  const data = await res.json();
  return data.redirectTo;
}

// --- Registration ---

export async function initRegistration(): Promise<void> {
  const res = await request('PUT', '', { interactionEvent: 'Register' });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message || `Failed to initialize registration (${res.status})`);
  }
}

export async function sendVerificationCode(email: string): Promise<string> {
  const res = await request('POST', '/verification/verification-code', {
    identifier: { type: 'email', value: email },
    interactionEvent: 'Register',
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    if (res.status === 422) {
      throw new Error('This email is already registered');
    }
    throw new Error(err.message || `Failed to send verification code (${res.status})`);
  }
  const data = await res.json();
  return data.verificationId;
}

export async function verifyCode(
  email: string,
  verificationId: string,
  code: string
): Promise<string> {
  const res = await request('POST', '/verification/verification-code/verify', {
    identifier: { type: 'email', value: email },
    verificationId,
    code,
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    if (res.status === 422) {
      throw new Error('Invalid or expired verification code');
    }
    throw new Error(err.message || `Verification failed (${res.status})`);
  }
  const data = await res.json();
  return data.verificationId;
}

export async function addProfile(type: string, value: string): Promise<void> {
  const res = await request('POST', '/profile', { type, value });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message || `Failed to update profile (${res.status})`);
  }
}

/** Phase 1: init registration + send verification email. Returns verificationId for phase 2. */
export async function startRegistration(email: string): Promise<string> {
  await initRegistration();
  return sendVerificationCode(email);
}

/** Phase 2: verify code, set password, create user, submit. Returns redirect URL. */
export async function completeRegistration(
  email: string,
  password: string,
  verificationId: string,
  code: string
): Promise<string> {
  const verifiedId = await verifyCode(email, verificationId, code);
  await addProfile('password', password);
  await identifyUser(verifiedId);
  return submitInteraction();
}

// --- Forgot Password ---

export async function initForgotPassword(): Promise<void> {
  const res = await request('PUT', '', { interactionEvent: 'ForgotPassword' });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message || `Failed to initialize password reset (${res.status})`);
  }
}

export async function forgotPasswordSendCode(email: string): Promise<string> {
  const res = await request('POST', '/verification/verification-code', {
    identifier: { type: 'email', value: email },
    interactionEvent: 'ForgotPassword',
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    if (res.status === 422) {
      throw new Error('No account found with this email');
    }
    throw new Error(err.message || `Failed to send reset code (${res.status})`);
  }
  const data = await res.json();
  return data.verificationId;
}

export async function forgotPasswordVerifyAndReset(
  email: string,
  verificationId: string,
  code: string,
  newPassword: string,
): Promise<void> {
  const verifiedId = await verifyCode(email, verificationId, code);
  await identifyUser(verifiedId);
  await addProfile('password', newPassword);
  await submitInteraction();
}

export async function startForgotPassword(email: string): Promise<string> {
  await initForgotPassword();
  return forgotPasswordSendCode(email);
}

// --- MFA Verification ---

export async function verifyTotp(code: string): Promise<string> {
  const res = await request('POST', '/verification/totp/verify', { code });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    if (res.status === 422) {
      throw new Error('Invalid code, please try again');
    }
    throw new Error(err.message || `TOTP verification failed (${res.status})`);
  }
  const data = await res.json();
  return data.verificationId;
}

export async function verifyBackupCode(code: string): Promise<string> {
  const res = await request('POST', '/verification/backup-code/verify', { code });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    if (res.status === 422) {
      const msg = err.code === 'backup_code_consumed'
        ? 'This backup code has already been used'
        : 'Invalid backup code';
      throw new Error(msg);
    }
    throw new Error(err.message || `Backup code verification failed (${res.status})`);
  }
  const data = await res.json();
  return data.verificationId;
}

export async function submitMfa(verificationId: string): Promise<string> {
  await identifyUser(verificationId);
  return submitInteraction();
}
