/*
 * 기기 무결성/위변조 탐지 레이어 (B1 루트·탈옥 / B9 디버거·개발자모드·USB / B2 앱무결성 토큰 전송부).
 * 근거: docs/security/mobile-security-audit.md 백로그 B1·B2·B9, checklist AT-1/AT-4/AT-6/AT-2.
 *
 * 성격(공공 대민 정책): 탐지 시 "경고 후 제한" — 하드 차단이 아니라 사용자 고지.
 *   오탐(에뮬레이터 개발기기·루팅 오검출)으로 정상 이용자를 잠그지 않는다(graceful).
 * 도입 경로: jail-monkey는 네이티브 모듈 → Expo Go/웹/미설치 기기에서는 방어적 동적 require로
 *   "탐지 불가(안전 가정)" 취급. dev-client/prebuild(EAS)에서 실제 동작. (biometric.ts 동일 패턴)
 * 시크릿 미보관: 앱은 불린/토큰만 다루고 원본 서명·키를 저장하지 않는다.
 */
import { useEffect, useState } from 'react';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
let JailMonkey: any = null;
try {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  JailMonkey = require('jail-monkey');
  // 일부 번들러는 default 래핑 → 안전 정규화
  if (JailMonkey && JailMonkey.default) JailMonkey = JailMonkey.default;
} catch {
  JailMonkey = null;
}

export interface IntegrityResult {
  /** 네이티브 탐지 모듈 사용 가능 여부(불가 시 아래 값은 모두 안전 기본값 false). */
  available: boolean;
  /** 루팅(Android)·탈옥(iOS) 단말. */
  jailBroken: boolean;
  /** 디버거 attach(Android). */
  debugger: boolean;
  /** 후킹(Frida/Xposed 등, Android). */
  hooking: boolean;
  /** USB 디버깅(ADB) 활성(Android). */
  adbEnabled: boolean;
  /** 개발자 옵션 활성(Android). */
  devMode: boolean;
  /** 위치 모의(mock) 허용. */
  mockLocation: boolean;
  /** 종합 위변조 신호(위 항목 중 하나라도 참). */
  compromised: boolean;
}

const SAFE: IntegrityResult = {
  available: false,
  jailBroken: false,
  debugger: false,
  hooking: false,
  adbEnabled: false,
  devMode: false,
  mockLocation: false,
  compromised: false,
};

/** 동기 불린 호출을 방어적으로 실행(예외/미구현 시 false). */
function safeBool(fn: (() => boolean) | undefined): boolean {
  if (typeof fn !== 'function') return false;
  try {
    return !!fn();
  } catch {
    return false;
  }
}

/** Promise 반환 API를 방어적으로 실행(예외/미구현 시 false). */
async function safeBoolAsync(fn: (() => Promise<boolean>) | undefined): Promise<boolean> {
  if (typeof fn !== 'function') return false;
  try {
    return !!(await fn());
  } catch {
    return false;
  }
}

/**
 * 기기 무결성 점검. 네이티브 모듈이 없으면 SAFE(모두 false)로 graceful 반환.
 * 결과는 UI에서 "경고 배너" 등으로 활용(차단 아님).
 */
export async function checkDeviceIntegrity(): Promise<IntegrityResult> {
  if (!JailMonkey) return { ...SAFE };
  try {
    const jailBroken = safeBool(JailMonkey.isJailBroken);
    const hooking = safeBool(JailMonkey.hookDetected);
    const adbEnabled = safeBool(JailMonkey.AdbEnabled);
    const mockLocation = safeBool(JailMonkey.canMockLocation);
    const debugger_ = await safeBoolAsync(JailMonkey.isDebuggedMode);
    const devMode = await safeBoolAsync(JailMonkey.isDevelopmentSettingsMode);
    const compromised = jailBroken || hooking || debugger_;
    return {
      available: true,
      jailBroken,
      debugger: debugger_,
      hooking,
      adbEnabled,
      devMode,
      mockLocation,
      compromised,
    };
  } catch {
    return { ...SAFE };
  }
}

/**
 * React 훅 — 마운트 시 1회 무결성 점검(비차단). 로그인 등 진입 화면에서 경고 노출용.
 */
export function useDeviceIntegrity(): IntegrityResult {
  const [result, setResult] = useState<IntegrityResult>(SAFE);
  useEffect(() => {
    let cancelled = false;
    checkDeviceIntegrity().then((r) => {
      if (!cancelled) setResult(r);
    });
    return () => {
      cancelled = true;
    };
  }, []);
  return result;
}

// ── B2 앱 무결성 토큰(전송부만) ───────────────────────────────────────────────
/*
 * Play Integrity(Android)·App Attest(iOS) 토큰은 각 플랫폼 네이티브 모듈로 "발급"한다.
 * 본 앱은 발급기(provider)를 주입받아 "전송"만 담당한다(api.ts가 헤더로 첨부).
 * 서버 검증 엔드포인트는 backend-dev 인계(_workspace/08_mobile_security_hardening.md).
 * provider 미등록 시 getAttestationToken()은 null → 헤더 미첨부(무해).
 */
type AttestationProvider = () => Promise<string | null>;
let attestationProvider: AttestationProvider | null = null;

/** 네이티브 무결성 토큰 발급기 등록(추후 Play Integrity/App Attest 모듈 연동 시). */
export function setAttestationProvider(provider: AttestationProvider | null): void {
  attestationProvider = provider;
}

/** 등록된 발급기로 무결성 토큰 조회. 미등록/실패 시 null(graceful). */
export async function getAttestationToken(): Promise<string | null> {
  if (!attestationProvider) return null;
  try {
    return await attestationProvider();
  } catch {
    return null;
  }
}
