import { Linking, NativeModules, PermissionsAndroid, Platform } from 'react-native';
import Constants from 'expo-constants';

const PACKAGE =
  Constants.expoConfig?.android?.package ??
  (Constants as { androidPackage?: string }).androidPackage ??
  'app.immotech.call';

type ImmotechCallWatchNative = {
  canDrawOverlays?: () => Promise<boolean>;
  canUseFullScreenIntent?: () => Promise<boolean>;
  openOverlaySettings?: () => Promise<boolean>;
  openAppDetailsSettings?: () => Promise<boolean>;
  openBatterySettings?: () => Promise<boolean>;
  getPendingIncoming?: () => Promise<{
    phone?: string;
    ringing?: boolean;
    inCall?: boolean;
    at?: number;
  } | null>;
  getActivityDeepLink?: () => Promise<string | null>;
  /** User closed incoming peek — suppress reopen until next RINGING. */
  dismissIncomingUi?: () => Promise<boolean>;
  /** Store CRM name so the native ring overlay can show it before the app opens. */
  cacheContactName?: (phone: string, name: string) => Promise<boolean>;
};

function callWatchNative(): ImmotechCallWatchNative | null {
  return (NativeModules as { ImmotechCallWatch?: ImmotechCallWatchNative }).ImmotechCallWatch ?? null;
}

async function withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {
  let timer: ReturnType<typeof setTimeout> | undefined;
  try {
    return await Promise.race([
      Promise.resolve(promise).catch(() => fallback),
      new Promise<T>((resolve) => {
        timer = setTimeout(() => resolve(fallback), ms);
      }),
    ]);
  } finally {
    if (timer) clearTimeout(timer);
  }
}

/** Fire native / Linking without awaiting (avoids stuck UI). */
function launchNow(run: () => Promise<unknown>): void {
  void run().catch(() => {});
}

export type CallPermissionStatus = {
  phoneState: boolean;
  callLog: boolean;
  notifications: boolean;
  overlay: boolean;
  allGranted: boolean;
  runtimeGranted: boolean;
};

const UNKNOWN_STATUS: CallPermissionStatus = {
  phoneState: false,
  callLog: false,
  notifications: false,
  overlay: false,
  allGranted: false,
  runtimeGranted: false,
};

async function checkAndroidPermission(permission: string): Promise<boolean> {
  return withTimeout(PermissionsAndroid.check(permission as never), 1_200, false);
}

export async function checkCallWatchPermissions(): Promise<CallPermissionStatus> {
  if (Platform.OS !== 'android') {
    return {
      phoneState: true,
      callLog: true,
      notifications: true,
      overlay: true,
      allGranted: true,
      runtimeGranted: true,
    };
  }

  const phoneState = await checkAndroidPermission(PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE);
  const callLog = await checkAndroidPermission(PermissionsAndroid.PERMISSIONS.READ_CALL_LOG);
  let notifications = true;
  if (Platform.Version >= 33) {
    notifications = await checkAndroidPermission(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
  }

  let overlay = false;
  const native = callWatchNative();
  if (native?.canDrawOverlays) {
    overlay = await withTimeout(native.canDrawOverlays(), 800, false);
  }

  const runtimeGranted = phoneState && callLog && notifications;
  return {
    phoneState,
    callLog,
    notifications,
    overlay,
    runtimeGranted,
    allGranted: runtimeGranted,
  };
}

/**
 * Best-effort runtime prompts. Short timeouts — never block the Settings screen.
 * On Samsung, prefer opening app details instead (see refreshCallAuthorizations).
 */
export async function requestCallWatchPermissions(): Promise<boolean> {
  if (Platform.OS !== 'android') return false;

  const perms: string[] = [
    PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE,
    PermissionsAndroid.PERMISSIONS.READ_CALL_LOG,
  ];
  const readPhoneNumbers = (PermissionsAndroid.PERMISSIONS as { READ_PHONE_NUMBERS?: string })
    .READ_PHONE_NUMBERS;
  if (readPhoneNumbers && Platform.Version >= 26) {
    perms.push(readPhoneNumbers);
  }
  if (Platform.Version >= 33) {
    perms.push(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
  }

  try {
    await withTimeout(
      PermissionsAndroid.requestMultiple(perms as never),
      8_000,
      {} as Awaited<ReturnType<typeof PermissionsAndroid.requestMultiple>>,
    );
  } catch {
    // ignore
  }

  const status = await withTimeout(checkCallWatchPermissions(), 2_000, UNKNOWN_STATUS);
  return status.runtimeGranted;
}

export async function openAppPermissionSettings(): Promise<void> {
  const native = callWatchNative();
  if (native?.openAppDetailsSettings) {
    launchNow(() => native.openAppDetailsSettings!());
    return;
  }
  launchNow(async () => {
    try {
      await Linking.openSettings();
    } catch {
      try {
        await Linking.openURL(`package:${PACKAGE}`);
      } catch {
        // ignore
      }
    }
  });
}

export async function openOverlayPermissionSettings(): Promise<void> {
  if (Platform.OS !== 'android') return;
  const native = callWatchNative();
  if (native?.openOverlaySettings) {
    launchNow(() => native.openOverlaySettings!());
    return;
  }
  launchNow(() => openAppPermissionSettings());
}

export async function openBatteryOptimizationSettings(): Promise<void> {
  if (Platform.OS !== 'android') return;
  const native = callWatchNative();
  if (native?.openBatterySettings) {
    launchNow(() => native.openBatterySettings!());
    return;
  }
  launchNow(() => openAppPermissionSettings());
}

export async function openFullScreenIntentSettings(): Promise<void> {
  if (Platform.OS !== 'android') return;
  launchNow(() => openAppPermissionSettings());
}

/**
 * Activate call ID: runtime prompts first (timed), then app details if still missing.
 * Never open settings + requestMultiple at the same time (Samsung hang).
 */
export async function refreshCallAuthorizations(): Promise<CallPermissionStatus> {
  await requestCallWatchPermissions();
  let status = await withTimeout(checkCallWatchPermissions(), 2_000, UNKNOWN_STATUS);
  if (!status.runtimeGranted) {
    openAppPermissionSettings();
  } else if (!status.overlay) {
    openOverlayPermissionSettings();
  }
  return status;
}

export async function getPendingIncomingCall(): Promise<{
  phone?: string;
  ringing?: boolean;
  inCall?: boolean;
  at?: number;
} | null> {
  if (Platform.OS !== 'android') return null;
  const native = callWatchNative();
  if (!native?.getPendingIncoming) return null;
  return withTimeout(native.getPendingIncoming(), 1_500, null);
}

export async function getActivityCallDeepLink(): Promise<string | null> {
  if (Platform.OS !== 'android') return null;
  const native = callWatchNative();
  if (!native?.getActivityDeepLink) return null;
  return withTimeout(native.getActivityDeepLink(), 1_500, null);
}

/**
 * Remember « numéro → nom CRM » natively so the ring overlay shows the client
 * name on the next call, before React Native is even started.
 */
export async function cacheCrmContactName(phone: string, name: string): Promise<void> {
  if (Platform.OS !== 'android') return;
  const native = callWatchNative();
  if (!native?.cacheContactName) return;
  const p = phone?.trim();
  const n = name?.trim();
  if (!p || !n) return;
  await withTimeout(native.cacheContactName(p, n), 1_000, true);
}

/** Fermer on « Appel entrant » — cancel notif + stop poll/native relaunch for this call. */
export async function dismissNativeIncomingUi(): Promise<void> {
  if (Platform.OS !== 'android') return;
  const native = callWatchNative();
  if (!native?.dismissIncomingUi) return;
  await withTimeout(native.dismissIncomingUi(), 1_500, true);
}
