import { sleep } from '@/utils';
import { ApiError } from '@/lib/api/errors';
import { OTP_LOCKED_CODE } from '../constants';
import type { AuthApi, AuthTokens, Me, RoleCode } from '../types';

const MOCK_LATENCY_MS = 400;
const DEV_OTP_CODE = '123456';
const MAX_WRONG_ATTEMPTS = 3;

/**
 * Which `Me` the mock returns, so a developer can exercise all three role-router branches
 * without a backend. Flip this to `nurse_unverified` or `no_role` while USE_AUTH_MOCK is true.
 */
type MockScenario = 'customer' | 'nurse_unverified' | 'no_role';
const MOCK_SCENARIO: MockScenario = 'customer';

const SCENARIO_ROLES: Record<MockScenario, RoleCode[]> = {
  customer: ['customer'],
  nurse_unverified: ['nurse'],
  no_role: [],
};

// Mutable so `selectRole` can grant a role to the fresh (`no_role`) user mid-session.
let grantedRoles: RoleCode[] = [...SCENARIO_ROLES[MOCK_SCENARIO]];
let wrongAttempts = 0;

function fakeTokens(): AuthTokens {
  return {
    // A structurally-valid unsigned JWT with a far-future `exp` so `isTokenAlive`
    // (middleware / server-seed) accepts it. Payload: { exp: 4102444800 }.
    accessToken:
      'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.',
    refreshToken: 'mock-refresh-token',
    accessExpiresAt: '2100-01-01T00:00:00Z',
    refreshExpiresAt: '2100-01-01T00:00:00Z',
    isNewUser: grantedRoles.length === 0,
    roles: grantedRoles,
  };
}

function currentMe(): Me {
  return {
    id: 1,
    phone: '0912*****00',
    firstName: null,
    lastName: null,
    gender: null,
    isActive: true,
    roles: grantedRoles,
    hasCustomerProfile: false,
    hasNurseProfile: false,
    nurseVerificationStatus: 'not_started',
  };
}

/**
 * In-memory mock behind the AuthApi seam — the offline fallback for the login flow.
 * `verifyOtp` accepts the fixed dev code `123456` and locks after MAX_WRONG_ATTEMPTS wrong
 * tries (reset by requesting a new code), mirroring the real domain 4xx states.
 */
export const authMockApi: AuthApi = {
  requestOtp: async () => {
    await sleep(MOCK_LATENCY_MS);
    wrongAttempts = 0;
    return { otpSent: true, resendAvailableInSeconds: 120 };
  },

  verifyOtp: async ({ code }) => {
    await sleep(MOCK_LATENCY_MS);
    if (wrongAttempts >= MAX_WRONG_ATTEMPTS) {
      throw new ApiError(400, 'Too many attempts. Request a new code.', OTP_LOCKED_CODE);
    }
    if (code !== DEV_OTP_CODE) {
      wrongAttempts += 1;
      if (wrongAttempts >= MAX_WRONG_ATTEMPTS) {
        throw new ApiError(400, 'Too many attempts. Request a new code.', OTP_LOCKED_CODE);
      }
      throw new ApiError(400, 'The code is incorrect or has expired.');
    }
    wrongAttempts = 0;
    return fakeTokens();
  },

  refresh: async () => {
    await sleep(MOCK_LATENCY_MS);
    return fakeTokens();
  },

  logout: async () => {
    await sleep(MOCK_LATENCY_MS);
    grantedRoles = [...SCENARIO_ROLES[MOCK_SCENARIO]];
  },

  getMe: async () => {
    await sleep(MOCK_LATENCY_MS);
    return currentMe();
  },

  selectRole: async ({ role }) => {
    await sleep(MOCK_LATENCY_MS);
    if (!grantedRoles.includes(role)) grantedRoles = [...grantedRoles, role];
    return currentMe();
  },
};
