/**
 * Browser/React Native-safe auth API client for platform-service.
 *
 * Replaces hand-rolled auth clients in ChronoMind web, MindLyst web, NomGap, etc.
 * No Node.js dependencies — uses globalThis.fetch and configurable storage.
 *
 * @example
 * ```ts
 * import { createAuthClient } from '@bytelyst/auth-client';
 *
 * const auth = createAuthClient({
 *   baseUrl: 'http://localhost:4003/api',
 *   productId: 'chronomind',
 * });
 *
 * const result = await auth.login('user@example.com', 'password123');
 * console.log(result.user.displayName);
 * ```
 */

import type {
  AuthClient,
  AuthClientConfig,
  AuthProvider,
  AuthResult,
  AuthUser,
  Device,
  LoginEventInfo,
  MfaRequiredResult,
  MfaStatus,
  Passkey,
  SecurityOverview,
  TokenStorage,
  TotpSetupResult,
} from './types.js';

// ── Default localStorage adapter ─────────────────────────────────

/**
 * No-op storage fallback used when `localStorage` is unavailable (e.g. SSR / Node.js).
 * Tokens stored via noopStorage are NOT persisted — they are lost on page reload.
 * For server-side rendering, use cookie-based auth instead of relying on this client.
 */
const noopStorage: TokenStorage = {
  getItem: () => null,
  setItem: () => {},
  removeItem: () => {},
};

function getDefaultStorage(): TokenStorage {
  if (
    typeof globalThis.localStorage !== 'undefined' &&
    typeof globalThis.localStorage?.getItem === 'function'
  ) {
    return globalThis.localStorage;
  }
  return noopStorage;
}

// ── UUID helper (browser + RN safe) ──────────────────────────────

function uuid(): string {
  if (typeof globalThis.crypto?.randomUUID === 'function') {
    return globalThis.crypto.randomUUID();
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = (Math.random() * 16) | 0;
    return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
  });
}

// ── Factory ──────────────────────────────────────────────────────

export function createAuthClient(config: AuthClientConfig): AuthClient {
  const { baseUrl, productId, timeoutMs = 15_000 } = config;
  const storage = config.storage ?? getDefaultStorage();
  const prefix = config.storagePrefix ?? productId;

  const KEYS = {
    accessToken: `${prefix}-auth-token`,
    refreshToken: `${prefix}-refresh-token`,
  } as const;

  // ── Token management ────────────────────────────

  function getAccessToken(): string | null {
    return storage.getItem(KEYS.accessToken);
  }

  function getRefreshToken(): string | null {
    return storage.getItem(KEYS.refreshToken);
  }

  function setTokens(accessToken: string, refreshToken: string): void {
    storage.setItem(KEYS.accessToken, accessToken);
    storage.setItem(KEYS.refreshToken, refreshToken);
  }

  function clearTokens(): void {
    storage.removeItem(KEYS.accessToken);
    storage.removeItem(KEYS.refreshToken);
  }

  function isAuthenticated(): boolean {
    return getAccessToken() !== null;
  }

  // ── HTTP helper ─────────────────────────────────

  async function request<T>(
    path: string,
    method: string,
    body?: unknown,
    opts?: { skipAuth?: boolean }
  ): Promise<T> {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'x-product-id': productId,
      'x-request-id': uuid(),
    };

    if (!opts?.skipAuth) {
      const token = getAccessToken();
      if (token) headers['Authorization'] = `Bearer ${token}`;
    }

    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);

    try {
      const res = await globalThis.fetch(`${baseUrl}${path}`, {
        method,
        headers,
        body: body ? JSON.stringify(body) : undefined,
        signal: controller.signal,
      });

      if (!res.ok) {
        const data = await res.json().catch(() => ({}));
        throw new Error(
          (data as Record<string, string>).message ||
            (data as Record<string, string>).error ||
            `HTTP ${res.status}`
        );
      }

      if (res.status === 204) return undefined as T;
      return res.json() as Promise<T>;
    } finally {
      clearTimeout(timer);
    }
  }

  // ── Singleton refresh guard ─────────────────────

  let _refreshPromise: Promise<boolean> | null = null;

  async function refreshAccessToken(): Promise<boolean> {
    if (_refreshPromise) return _refreshPromise;

    _refreshPromise = (async () => {
      const rt = getRefreshToken();
      if (!rt) return false;

      try {
        const data = await request<{ accessToken: string; refreshToken: string }>(
          '/auth/refresh',
          'POST',
          { refreshToken: rt },
          { skipAuth: true }
        );
        setTokens(data.accessToken, data.refreshToken);
        return true;
      } catch {
        clearTokens();
        return false;
      }
    })();

    try {
      return await _refreshPromise;
    } finally {
      _refreshPromise = null;
    }
  }

  // ── Auth operations ─────────────────────────────

  async function login(email: string, password: string): Promise<AuthResult | MfaRequiredResult> {
    const result = await request<AuthResult | MfaRequiredResult>('/auth/login', 'POST', {
      email,
      password,
      productId,
    });
    if ('mfaRequired' in result && result.mfaRequired) {
      return result;
    }
    const authResult = result as AuthResult;
    setTokens(authResult.accessToken, authResult.refreshToken);
    return authResult;
  }

  async function register(
    email: string,
    password: string,
    displayName: string
  ): Promise<AuthResult> {
    const result = await request<AuthResult>('/auth/register', 'POST', {
      email,
      password,
      displayName,
      productId,
    });
    setTokens(result.accessToken, result.refreshToken);
    return result;
  }

  async function getMe(): Promise<AuthUser> {
    return request<AuthUser>('/auth/me', 'GET');
  }

  // ── Password management ─────────────────────────

  async function forgotPassword(email: string): Promise<{ message: string }> {
    return request<{ message: string }>('/auth/forgot-password', 'POST', {
      email,
      productId,
    });
  }

  async function resetPassword(token: string, newPassword: string): Promise<{ message: string }> {
    return request<{ message: string }>('/auth/reset-password', 'POST', {
      token,
      newPassword,
    });
  }

  async function changePassword(
    currentPassword: string,
    newPassword: string
  ): Promise<{ message: string }> {
    return request<{ message: string }>('/auth/change-password', 'POST', {
      currentPassword,
      newPassword,
    });
  }

  // ── Account management ──────────────────────────

  async function deleteAccount(password: string): Promise<{ message: string }> {
    const result = await request<{ message: string }>('/auth/account', 'DELETE', {
      password,
    });
    clearTokens();
    return result;
  }

  // ── Email verification ──────────────────────────

  async function verifyEmail(token: string): Promise<{ message: string }> {
    return request<{ message: string }>('/auth/verify-email', 'POST', { token });
  }

  async function resendVerification(email: string): Promise<{ message: string }> {
    return request<{ message: string }>('/auth/resend-verification', 'POST', {
      email,
      productId,
    });
  }

  // ── OAuth / Social login (Phase 1C) ────────────────

  async function loginWithOAuth(
    provider: string,
    idToken: string
  ): Promise<AuthResult | MfaRequiredResult> {
    const result = await request<AuthResult | MfaRequiredResult>(
      `/auth/oauth/${provider}`,
      'POST',
      { idToken, productId },
      { skipAuth: true }
    );
    if ('mfaRequired' in result && result.mfaRequired) {
      return result;
    }
    const authResult = result as AuthResult;
    setTokens(authResult.accessToken, authResult.refreshToken);
    return authResult;
  }

  async function loginWithGoogle(idToken: string): Promise<AuthResult | MfaRequiredResult> {
    return loginWithOAuth('google', idToken);
  }

  async function loginWithMicrosoft(idToken: string): Promise<AuthResult | MfaRequiredResult> {
    return loginWithOAuth('microsoft', idToken);
  }

  async function loginWithApple(idToken: string): Promise<AuthResult | MfaRequiredResult> {
    return loginWithOAuth('apple', idToken);
  }

  // ── Provider management (Phase 1C) ─────────────────

  async function getProviders(): Promise<AuthProvider[]> {
    const data = await request<{ providers: AuthProvider[] }>('/auth/providers', 'GET');
    return data.providers;
  }

  async function linkProvider(provider: string, idToken: string): Promise<void> {
    await request<void>('/auth/providers/link', 'POST', { provider, idToken });
  }

  async function unlinkProvider(provider: string): Promise<void> {
    await request<void>(`/auth/providers/${provider}`, 'DELETE');
  }

  // ── MFA (Phase 2D) ─────────────────────────────────

  async function verifyMfa(
    challengeToken: string,
    code: string,
    method: 'totp' | 'recovery'
  ): Promise<AuthResult> {
    const result = await request<AuthResult>('/auth/mfa/verify', 'POST', {
      challengeToken,
      code,
      method,
    });
    setTokens(result.accessToken, result.refreshToken);
    return result;
  }

  async function setupTotp(): Promise<TotpSetupResult> {
    return request<TotpSetupResult>('/auth/mfa/setup', 'POST');
  }

  async function verifyTotpSetup(code: string): Promise<void> {
    await request<void>('/auth/mfa/verify-setup', 'POST', { code });
  }

  async function disableMfa(code: string): Promise<void> {
    await request<void>('/auth/mfa/disable', 'POST', { code });
  }

  async function getMfaStatus(): Promise<MfaStatus> {
    return request<MfaStatus>('/auth/mfa/status', 'GET');
  }

  async function regenerateRecoveryCodes(): Promise<{ codes: string[] }> {
    return request<{ codes: string[] }>('/auth/mfa/recovery/regenerate', 'POST');
  }

  // ── Passkeys (Phase 3) ─────────────────────────────

  async function getPasskeyRegisterOptions(): Promise<unknown> {
    return request<unknown>('/auth/passkeys/register/options', 'POST');
  }

  async function verifyPasskeyRegistration(response: unknown): Promise<void> {
    await request<void>('/auth/passkeys/register/verify', 'POST', response);
  }

  async function getPasskeyAuthOptions(): Promise<unknown> {
    return request<unknown>('/auth/passkeys/authenticate/options', 'POST', undefined, {
      skipAuth: true,
    });
  }

  async function verifyPasskeyAuth(response: unknown): Promise<AuthResult> {
    const result = await request<AuthResult>(
      '/auth/passkeys/authenticate/verify',
      'POST',
      response,
      { skipAuth: true }
    );
    setTokens(result.accessToken, result.refreshToken);
    return result;
  }

  async function listPasskeys(): Promise<Passkey[]> {
    const data = await request<{ passkeys: Passkey[] }>('/auth/passkeys', 'GET');
    return data.passkeys;
  }

  async function deletePasskey(id: string): Promise<void> {
    await request<void>(`/auth/passkeys/${id}`, 'DELETE');
  }

  // ── Devices (Phase 3) ──────────────────────────────

  async function listDevices(): Promise<Device[]> {
    const data = await request<{ devices: Device[] }>('/auth/devices', 'GET');
    return data.devices;
  }

  async function trustDevice(
    fingerprint: string,
    trustLevel: 'trusted' | 'remembered',
    deviceInfo?: Record<string, string>
  ): Promise<void> {
    await request<void>('/auth/devices/trust', 'POST', { fingerprint, trustLevel, deviceInfo });
  }

  async function revokeDevice(fingerprint: string): Promise<void> {
    await request<void>(`/auth/devices/${fingerprint}`, 'DELETE');
  }

  async function revokeAllDevices(): Promise<void> {
    await request<void>('/auth/devices/revoke-all', 'POST');
  }

  // ── Admin security (Phase 5B) ──────────────────────

  async function getSecurityOverview(): Promise<SecurityOverview> {
    return request<SecurityOverview>('/auth/security/overview', 'GET');
  }

  async function unlockUser(userId: string): Promise<void> {
    await request<void>(`/auth/users/${userId}/unlock`, 'POST');
  }

  async function exportAuthData(): Promise<unknown> {
    return request<unknown>('/auth/export', 'GET');
  }

  async function cancelDeletion(): Promise<{ message: string }> {
    return request<{ message: string }>('/auth/account/cancel-deletion', 'POST');
  }

  // ── Step-up auth ────────────────────────────────────

  async function stepUp(method: string, credential: string): Promise<{ stepUpToken: string }> {
    return request<{ stepUpToken: string }>('/auth/step-up', 'POST', { method, credential });
  }

  // ── Login history ───────────────────────────────────

  async function getLoginHistory(limit = 50): Promise<LoginEventInfo[]> {
    const data = await request<{ events: LoginEventInfo[] }>(
      `/auth/login-events?limit=${limit}`,
      'GET'
    );
    return data.events;
  }

  // ── Admin security ──────────────────────────────────

  async function getAdminLoginEvents(opts?: {
    userId?: string;
    suspicious?: boolean;
    limit?: number;
  }): Promise<LoginEventInfo[]> {
    const params = new URLSearchParams();
    if (opts?.userId) params.set('userId', opts.userId);
    if (opts?.suspicious) params.set('suspicious', 'true');
    if (opts?.limit) params.set('limit', String(opts.limit));
    const qs = params.toString();
    const data = await request<{ events: LoginEventInfo[] }>(
      `/auth/login-events/admin${qs ? `?${qs}` : ''}`,
      'GET'
    );
    return data.events;
  }

  async function getAdminDevices(userId: string): Promise<Device[]> {
    const data = await request<{ devices: Device[] }>(`/auth/devices/user/${userId}`, 'GET');
    return data.devices;
  }

  return {
    getAccessToken,
    getRefreshToken,
    setTokens,
    clearTokens,
    isAuthenticated,
    login,
    register,
    getMe,
    refreshAccessToken,
    forgotPassword,
    resetPassword,
    changePassword,
    deleteAccount,
    verifyEmail,
    resendVerification,
    // OAuth / Social login (Phase 1C)
    loginWithGoogle,
    loginWithMicrosoft,
    loginWithApple,
    // Provider management (Phase 1C)
    getProviders,
    linkProvider,
    unlinkProvider,
    // MFA (Phase 2D)
    verifyMfa,
    setupTotp,
    verifyTotpSetup,
    disableMfa,
    getMfaStatus,
    regenerateRecoveryCodes,
    // Passkeys (Phase 3)
    getPasskeyRegisterOptions,
    verifyPasskeyRegistration,
    getPasskeyAuthOptions,
    verifyPasskeyAuth,
    listPasskeys,
    deletePasskey,
    // Devices (Phase 3)
    listDevices,
    trustDevice,
    revokeDevice,
    revokeAllDevices,
    // Admin security (Phase 5B)
    getSecurityOverview,
    unlockUser,
    exportAuthData,
    cancelDeletion,
    // Step-up auth
    stepUp,
    // Login history
    getLoginHistory,
    // Admin queries
    getAdminLoginEvents,
    getAdminDevices,
  };
}
