import { NativeModules, PermissionsAndroid, Platform } from 'react-native';
import { normalizePhoneFromDevice } from './phone';

export interface DeviceCall {
  id: string;
  phone: string;
  phoneNormalized: string;
  name: string | null;
  type: 'incoming' | 'outgoing' | 'missed';
  timestamp: number;
  durationSeconds: number;
}

export type DeviceCallLogStatus = 'ok' | 'denied' | 'unavailable';

/**
 * True only on Android with the native module present (dev build / APK, not Expo Go).
 * Checks NativeModules directly WITHOUT requiring the JS package: its module
 * factory throws at evaluation time in Expo Go, which crashes the screen even
 * inside try/catch (Metro reports module evaluation errors globally).
 */
export function isDeviceCallLogSupported(): boolean {
  if (Platform.OS !== 'android') return false;
  return NativeModules?.CallLogs != null;
}

function getCallLogsModule(): { load: (limit: number) => Promise<Record<string, unknown>[]> } | null {
  if (!isDeviceCallLogSupported()) return null;
  try {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const mod = require('react-native-call-log');
    return (mod?.default ?? mod) || null;
  } catch {
    return null;
  }
}

/**
 * Android CallLog.Calls.TYPE (+ react-native-call-log strings / rawType):
 * 1 INCOMING, 2 OUTGOING, 3 MISSED, 4 VOICEMAIL, 5 REJECTED, 6 BLOCKED, 7 ANSWERED_EXTERNALLY
 * Library also uses WIFI_INCOMING=100, WIFI_OUTGOING=101.
 */
export function mapDeviceCallType(
  raw: string | number | null | undefined,
  durationSeconds = 0,
): DeviceCall['type'] {
  if (raw == null || raw === '') {
    return 'incoming';
  }

  if (typeof raw === 'number' || (typeof raw === 'string' && /^\d+$/.test(raw.trim()))) {
    const n = Math.trunc(Number(raw));
    if (n === 2 || n === 101) return 'outgoing';
    if (n === 3 || n === 5 || n === 6) return 'missed';
    if (n === 1 || n === 100) {
      // Some OEMs log unanswered inbound as INCOMING + duration 0.
      return durationSeconds > 0 ? 'incoming' : 'missed';
    }
    if (n === 4 || n === 7) return 'incoming';
  }

  const v = String(raw).toUpperCase().replace(/[^A-Z0-9_]/g, '');

  if (v === 'OUTGOING' || v === 'WIFI_OUTGOING' || v === 'OUT') return 'outgoing';
  if (
    v === 'MISSED' ||
    v === 'REJECTED' ||
    v === 'BLOCKED' ||
    v.includes('ABSENCE') ||
    v.includes('MANQUE')
  ) {
    return 'missed';
  }
  if (v === 'INCOMING' || v === 'WIFI_INCOMING' || v === 'IN' || v === 'ANSWERED_EXTERNALLY') {
    return durationSeconds > 0 ? 'incoming' : 'missed';
  }
  if (v === 'VOICEMAIL') return 'incoming';

  // Unknown — do not force "missed" (that made every call look like absence).
  return 'incoming';
}

/** Human call length: « 1min 20s », « 45s ». Empty when unknown. */
export function formatCallDuration(seconds: number): string {
  if (!Number.isFinite(seconds) || seconds <= 0) return '';
  const m = Math.floor(seconds / 60);
  const s = Math.round(seconds % 60);
  return m > 0 ? `${m}min ${s}s` : `${s}s`;
}

/** CRM journal type from device call. */
export function deviceCallToCrmType(type: DeviceCall['type']): 'Sortant' | 'Entrant' {
  return type === 'outgoing' ? 'Sortant' : 'Entrant';
}

/**
 * Reads the native Android call log (requires READ_CALL_LOG).
 * Returns status 'unavailable' on iOS / Expo Go, 'denied' if permission refused.
 */
export async function fetchDeviceCallLog(
  limit = 100,
): Promise<{ status: DeviceCallLogStatus; calls: DeviceCall[] }> {
  const CallLogs = getCallLogsModule();
  if (!CallLogs) {
    return { status: 'unavailable', calls: [] };
  }

  const granted = await PermissionsAndroid.request(
    PermissionsAndroid.PERMISSIONS.READ_CALL_LOG,
  );
  if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
    return { status: 'denied', calls: [] };
  }

  const rows: Record<string, unknown>[] = await CallLogs.load(limit);
  const calls: DeviceCall[] = (rows ?? []).map((row, index) => {
    const phone = String(
      row.phoneNumber ?? row.number ?? row.formattedNumber ?? '',
    );
    const durationSeconds = Number(row.duration ?? 0) || 0;
    // Prefer numeric rawType from native module; fall back to string type.
    const rawType =
      row.rawType != null && row.rawType !== ''
        ? (row.rawType as string | number)
        : ((row.type ?? row.callType ?? null) as string | number | null);
    return {
      id: String(row.id ?? row.timestamp ?? row.date ?? index) + ':' + phone,
      phone,
      phoneNormalized: normalizePhoneFromDevice(phone),
      name: row.name ? String(row.name) : null,
      type: mapDeviceCallType(rawType, durationSeconds),
      timestamp: Number(row.timestamp ?? row.date ?? Date.now()),
      durationSeconds,
    };
  });

  return { status: 'ok', calls };
}
