import NativeRNEncryptedStorage from 'react-native-encrypted-storage/src/specs/v1/NativeRNEncryptedStorage';

export interface RawStorageCheckResult {
  rawBase64: string | null;
  rawByteLength: number;
  plaintextHits: string[];
  looksEncrypted: boolean;
}

const BASE64_CHARS =
  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

function decodeBase64ToBytes(base64: string): Uint8Array {
  if (typeof globalThis.atob === 'function') {
    const binary = globalThis.atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
      bytes[i] = binary.charCodeAt(i);
    }
    return bytes;
  }

  // Hermes / HarmonyOS RN 无 atob,使用纯 JS 解码
  const normalized = base64.replace(/[\r\n\s]/g, '');
  const len = normalized.length;
  if (len === 0 || len % 4 !== 0) {
    throw new Error('Invalid base64 input');
  }

  let padding = 0;
  if (normalized[len - 1] === '=') {
    padding++;
  }
  if (normalized[len - 2] === '=') {
    padding++;
  }

  const outputLength = (len * 3) / 4 - padding;
  const bytes = new Uint8Array(outputLength);
  let byteIndex = 0;

  const decodeChar = (char: string): number => {
    if (char === '=') {
      return 0;
    }
    const value = BASE64_CHARS.indexOf(char);
    if (value < 0) {
      throw new Error('Invalid base64 input');
    }
    return value;
  };

  for (let i = 0; i < len; i += 4) {
    const a = decodeChar(normalized[i]);
    const b = decodeChar(normalized[i + 1]);
    const c = decodeChar(normalized[i + 2]);
    const d = decodeChar(normalized[i + 3]);

    bytes[byteIndex++] = (a << 2) | (b >> 4);
    if (byteIndex < outputLength) {
      bytes[byteIndex++] = ((b & 0x0f) << 4) | (c >> 2);
    }
    if (byteIndex < outputLength) {
      bytes[byteIndex++] = ((c & 0x03) << 6) | d;
    }
  }

  return bytes;
}

function stringToUtf8Bytes(text: string): Uint8Array {
  if (typeof TextEncoder !== 'undefined') {
    return new TextEncoder().encode(text);
  }
  const codes: number[] = [];
  for (let i = 0; i < text.length; i++) {
    const code = text.charCodeAt(i);
    if (code < 0x80) {
      codes.push(code);
    } else if (code < 0x800) {
      codes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
    } else {
      codes.push(
        0xe0 | (code >> 12),
        0x80 | ((code >> 6) & 0x3f),
        0x80 | (code & 0x3f),
      );
    }
  }
  return new Uint8Array(codes);
}

function containsBytes(haystack: Uint8Array, needle: Uint8Array): boolean {
  if (needle.length === 0 || haystack.length < needle.length) {
    return false;
  }
  outer: for (let i = 0; i <= haystack.length - needle.length; i++) {
    for (let j = 0; j < needle.length; j++) {
      if (haystack[i + j] !== needle[j]) {
        continue outer;
      }
    }
    return true;
  }
  return false;
}

/** Reads raw preferences bytes (base64) via test-only TurboModule helper. */
export async function readRawStoredValueBase64(key: string): Promise<string | null> {
  const native = NativeRNEncryptedStorage as {
    getRawStoredValueBase64?: (storageKey: string) => Promise<string | null>;
  };
  if (typeof native.getRawStoredValueBase64 !== 'function') {
    throw new Error(
      'getRawStoredValueBase64 不可用:设备/HAP 中的 encrypted_storage.har 版本过旧。' +
        '请在 ohos/ 执行 npm run build:har && npm pack,再在 example_auto 执行 npm install,' +
        '然后重新 bundle JS 并安装 HAP。',
    );
  }
  return native.getRawStoredValueBase64(key);
}

/** Checks whether any plaintext marker appears in raw stored bytes or base64 text. */
export async function inspectRawStorageForPlaintext(
  key: string,
  plaintextMarkers: string[],
): Promise<RawStorageCheckResult> {
  const rawBase64 = await readRawStoredValueBase64(key);
  if (rawBase64 == null) {
    return {
      rawBase64: null,
      rawByteLength: 0,
      plaintextHits: [],
      looksEncrypted: false,
    };
  }

  const rawBytes = decodeBase64ToBytes(rawBase64);
  const plaintextHits: string[] = [];

  for (const marker of plaintextMarkers) {
    if (marker.length === 0) {
      continue;
    }
    if (rawBase64.includes(marker)) {
      plaintextHits.push(marker);
      continue;
    }
    const markerBytes = stringToUtf8Bytes(marker);
    if (containsBytes(rawBytes, markerBytes)) {
      plaintextHits.push(marker);
    }
  }

  // AES-GCM layout: iv(12) + ciphertext + authTag(16)
  const looksEncrypted = rawBytes.length >= 28 && plaintextHits.length === 0;

  return {
    rawBase64,
    rawByteLength: rawBytes.length,
    plaintextHits,
    looksEncrypted,
  };
}