/* eslint-disable @typescript-eslint/no-var-requires */
/**
 * Expo Config-Plugin — wires the Android VpnService (DNS-Filter) +
 * AccessibilityService (URL filter Layer 2) into AndroidManifest.xml at
 * prebuild time.
 *
 * Was es macht:
 *   1) Sorgt für `xmlns:tools` auf <manifest>.
 *   2) Registriert <service .RebreakVpnService> mit
 *      foregroundServiceType="specialUse" (+ PROPERTY_SPECIAL_USE_FGS_SUBTYPE=vpn)
 *      + intent-filter android.net.VpnService + permission BIND_VPN_SERVICE.
 *      (`systemExempted` wurde verworfen: es verlangt auf Android 16/API 36 zur
 *      startForeground-Zeit den AKTIVEN-VPN-Zustand [anyOf android:activate_vpn],
 *      der erst ~ms nach establish() kommt → SecurityException/Crash. `specialUse`
 *      hat keine Laufzeit-Vorbedingung; braucht FOREGROUND_SERVICE_SPECIAL_USE +
 *      einmalige Play-„Special Use"-Erklärung.)
 *   3) Registriert <service .RebreakAccessibilityService> mit
 *      android:permission=BIND_ACCESSIBILITY_SERVICE + intent-filter
 *      android.accessibilityservice.AccessibilityService + meta-data
 *      android.accessibilityservice → @xml/accessibility_service_config.
 *
 * Wird aus app.config.ts via `plugins: ['./plugins/with-rebreak-protection-android']`
 * registriert. Idempotent — kann beliebig oft via `expo prebuild` laufen.
 *
 * Native Source: `modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/`
 *  - VpnService:           expo.modules.rebreakprotection.vpn.RebreakVpnService
 *  - AccessibilityService: expo.modules.rebreakprotection.accessibility.RebreakAccessibilityService
 */

const {
  withAndroidManifest,
  withStringsXml,
  withDangerousMod,
  AndroidConfig,
} = require('@expo/config-plugins');
const fs = require('fs');
const path = require('path');

const VPN_SERVICE_CLASS =
  'expo.modules.rebreakprotection.vpn.RebreakVpnService';
const A11Y_SERVICE_CLASS =
  'expo.modules.rebreakprotection.accessibility.RebreakAccessibilityService';
const ADMIN_RECEIVER_CLASS =
  'expo.modules.rebreakprotection.admin.RebreakDeviceAdminReceiver';
const BOOT_RECEIVER_CLASS =
  'expo.modules.rebreakprotection.vpn.RebreakVpnBootReceiver';

// ─── 1) tools-Namespace auf <manifest> ──────────────────────────────────────

function ensureToolsNamespace(manifest) {
  if (!manifest.manifest.$) manifest.manifest.$ = {};
  if (!manifest.manifest.$['xmlns:tools']) {
    manifest.manifest.$['xmlns:tools'] = 'http://schemas.android.com/tools';
  }
}

// ─── 2) <service>-Tag für RebreakVpnService ─────────────────────────────────

function ensureVpnService(manifest) {
  const application = AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);

  if (!application.service) application.service = [];

  const alreadyDeclared = application.service.some(
    (svc) => svc.$ && svc.$['android:name'] === VPN_SERVICE_CLASS,
  );
  if (alreadyDeclared) return;

  application.service.push({
    $: {
      'android:name': VPN_SERVICE_CLASS,
      'android:permission': 'android.permission.BIND_VPN_SERVICE',
      // specialUse statt systemExempted: systemExempted verlangt auf Android 16
      // (API 36) zur startForeground-Zeit, dass die App bereits der AKTIVE VPN ist
      // (anyOf [android:activate_vpn]) — dieser Zustand kommt aber erst ~ms nach
      // establish() (Race) → SecurityException. specialUse hat keine Laufzeit-
      // Vorbedingung und geht deterministisch durch (braucht nur die Permission
      // FOREGROUND_SERVICE_SPECIAL_USE + die PROPERTY unten + Play-Special-Use-Decl).
      'android:foregroundServiceType': 'specialUse',
      'android:exported': 'false',
    },
    property: [
      {
        $: {
          'android:name': 'android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE',
          'android:value': 'vpn',
        },
      },
    ],
    'intent-filter': [
      {
        action: [{ $: { 'android:name': 'android.net.VpnService' } }],
      },
    ],
  });
}

// ─── 3) <service>-Tag für RebreakAccessibilityService ───────────────────────

function ensureAccessibilityService(manifest) {
  const application = AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);

  if (!application.service) application.service = [];

  const alreadyDeclared = application.service.some(
    (svc) => svc.$ && svc.$['android:name'] === A11Y_SERVICE_CLASS,
  );
  if (alreadyDeclared) return;

  application.service.push({
    $: {
      'android:name': A11Y_SERVICE_CLASS,
      'android:permission': 'android.permission.BIND_ACCESSIBILITY_SERVICE',
      'android:label': '@string/accessibility_service_summary',
      // ReBreak-Logo in der Bedienungshilfen-Liste, damit der User die Zeile
      // klar als ReBreak erkennt (sonst generisches/Default-Icon).
      'android:icon': '@mipmap/ic_launcher',
      'android:exported': 'true',
    },
    'intent-filter': [
      {
        action: [
          {
            $: {
              'android:name':
                'android.accessibilityservice.AccessibilityService',
            },
          },
        ],
      },
    ],
    'meta-data': [
      {
        $: {
          'android:name': 'android.accessibilityservice',
          'android:resource': '@xml/accessibility_service_config',
        },
      },
    ],
  });
}

// ─── 3b) RECEIVE_BOOT_COMPLETED permission ──────────────────────────────────

function ensureBootPermission(manifest) {
  if (!manifest.manifest['uses-permission']) {
    manifest.manifest['uses-permission'] = [];
  }
  const PERM = 'android.permission.RECEIVE_BOOT_COMPLETED';
  const exists = manifest.manifest['uses-permission'].some(
    (p) => p.$ && p.$['android:name'] === PERM,
  );
  if (!exists) {
    manifest.manifest['uses-permission'].push({ $: { 'android:name': PERM } });
  }
}

// ─── 3c) Device-Admin- + Boot-Receiver ──────────────────────────────────────
// Device-Admin: macht die App OS-seitig nicht-direkt-deinstallierbar — greift ab
//   Boot, ohne dass Prozess/a11y laufen müssen. Deaktivierung nur via 24h-Cooldown
//   im App-Code (removeDeviceAdmin).
// Boot-Receiver: startet VPN+a11y nach Reboot/Package-Replace neu, damit der
//   Tamper-Lock nicht erst nach manuellem App-Start hochkommt.

function ensureReceivers(manifest) {
  const application = AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);
  if (!application.receiver) application.receiver = [];

  if (
    !application.receiver.some(
      (r) => r.$ && r.$['android:name'] === ADMIN_RECEIVER_CLASS,
    )
  ) {
    application.receiver.push({
      $: {
        'android:name': ADMIN_RECEIVER_CLASS,
        'android:permission': 'android.permission.BIND_DEVICE_ADMIN',
        'android:exported': 'true',
      },
      'meta-data': [
        {
          $: {
            'android:name': 'android.app.device_admin',
            'android:resource': '@xml/device_admin',
          },
        },
      ],
      'intent-filter': [
        {
          action: [
            {
              $: { 'android:name': 'android.app.action.DEVICE_ADMIN_ENABLED' },
            },
          ],
        },
      ],
    });
  }

  if (
    !application.receiver.some(
      (r) => r.$ && r.$['android:name'] === BOOT_RECEIVER_CLASS,
    )
  ) {
    application.receiver.push({
      $: {
        'android:name': BOOT_RECEIVER_CLASS,
        'android:enabled': 'true',
        'android:exported': 'true',
      },
      'intent-filter': [
        {
          $: { 'android:priority': '999' },
          action: [
            { $: { 'android:name': 'android.intent.action.BOOT_COMPLETED' } },
            { $: { 'android:name': 'android.intent.action.QUICKBOOT_POWERON' } },
            { $: { 'android:name': 'com.htc.intent.action.QUICKBOOT_POWERON' } },
            { $: { 'android:name': 'android.intent.action.MY_PACKAGE_REPLACED' } },
          ],
        },
      ],
    });
  }
}

// ─── 4) String resources für a11y-service ───────────────────────────────────

const A11Y_DESCRIPTION_TEXT =
  'Sichert deinen Schutz gegen impulsives Abschalten ab: Solange App-Lock aktiv ist, kann das ReBreak-VPN nicht in den Einstellungen deaktiviert und die App nicht deinstalliert werden. Das Blockieren von Glücksspielseiten selbst übernimmt das VPN — diese Berechtigung sichert es nur. Du kannst den Schutz jederzeit über die Abkühlphase in der App beenden.';
const A11Y_SUMMARY_TEXT =
  'ReBreak Schutz';

function withA11yStringResource(config) {
  return withStringsXml(config, (cfg) => {
    cfg.modResults = AndroidConfig.Strings.setStringItem(
      [
        {
          $: { name: 'accessibility_service_description', translatable: 'false' },
          _: A11Y_DESCRIPTION_TEXT,
        },
      ],
      cfg.modResults,
    );
    cfg.modResults = AndroidConfig.Strings.setStringItem(
      [
        {
          $: { name: 'accessibility_service_summary', translatable: 'false' },
          _: A11Y_SUMMARY_TEXT,
        },
      ],
      cfg.modResults,
    );
    return cfg;
  });
}

// ─── 5) XML-config für AccessibilityService ─────────────────────────────────
// Kopiert die Source-of-Truth aus dem Modul-Verzeichnis statt einen
// hardcoded String zu pflegen — so bleibt Plugin + Service-Config immer sync.

const MODULE_A11Y_XML = path.resolve(
  __dirname,
  '../modules/rebreak-protection/android/src/main/res/xml/accessibility_service_config.xml',
);

function withA11yConfigXml(config) {
  return withDangerousMod(config, [
    'android',
    async (cfg) => {
      const xmlDir = path.join(
        cfg.modRequest.platformProjectRoot,
        'app/src/main/res/xml',
      );
      fs.mkdirSync(xmlDir, { recursive: true });
      fs.copyFileSync(
        MODULE_A11Y_XML,
        path.join(xmlDir, 'accessibility_service_config.xml'),
      );
      return cfg;
    },
  ]);
}

// ─── 5b) XML-config für Device-Admin (@xml/device_admin) ────────────────────

const MODULE_DEVICE_ADMIN_XML = path.resolve(
  __dirname,
  '../modules/rebreak-protection/android/src/main/res/xml/device_admin.xml',
);

function withDeviceAdminXml(config) {
  return withDangerousMod(config, [
    'android',
    async (cfg) => {
      const xmlDir = path.join(
        cfg.modRequest.platformProjectRoot,
        'app/src/main/res/xml',
      );
      fs.mkdirSync(xmlDir, { recursive: true });
      fs.copyFileSync(
        MODULE_DEVICE_ADMIN_XML,
        path.join(xmlDir, 'device_admin.xml'),
      );
      return cfg;
    },
  ]);
}

// ─── Composition ────────────────────────────────────────────────────────────

function withRebreakProtectionAndroid(config) {
  config = withAndroidManifest(config, (cfg) => {
    ensureToolsNamespace(cfg.modResults);
    ensureBootPermission(cfg.modResults);
    ensureVpnService(cfg.modResults);
    ensureAccessibilityService(cfg.modResults);
    ensureReceivers(cfg.modResults);
    return cfg;
  });
  config = withA11yStringResource(config);
  config = withA11yConfigXml(config);
  config = withDeviceAdminXml(config);
  return config;
}

module.exports = withRebreakProtectionAndroid;
