// Copyright (C) 2026 Acoustic, L.P. All rights reserved.
//
// NOTICE: This file contains material that is confidential and proprietary to
// Acoustic, L.P. and/or other developers. No license is granted under any
// intellectual or industrial property rights of Acoustic, L.P. except as may
// be provided in an agreement with Acoustic, L.P. Any unauthorized copying or
// distribution of content from this file is prohibited.

import {
  withAppBuildGradle,
  withDangerousMod,
  type ConfigPlugin,
} from '@expo/config-plugins'
import * as fs from 'fs'
import * as path from 'path'
import type { ConnectPluginProps } from './withConnectNSE'

// The Gradle snippet that runs the SDK's config.gradle. config.gradle reads
// the consumer app's ConnectConfig.json (`$project.rootDir/../ConnectConfig.json`)
// and writes AppKey / PostMessageUrl / KillSwitchUrl (and the Tealeaf + EOCore
// configs) into the SDK module's `src/main/assets/*` at Gradle configuration
// time — so the collector the app reports to is driven by ConnectConfig.json.
//
// This mirrors the line `scripts/gradleParser.js` appends for the bare-workflow
// sample (Examples/bare-workflow/android/app/build.gradle). The Expo Android
// project is gitignored and regenerated by `expo prebuild`, so the line cannot
// be committed there — this mod re-injects it on every prebuild instead.
//
// The ':react-native-acoustic-connect-beta' Gradle project is provided by
// React Native autolinking and resolves in the Expo app the same way it does
// in bare-workflow.
export const CONFIG_GRADLE_APPLY =
  `apply from: project(':react-native-acoustic-connect-beta')` +
  `.projectDir.getPath() + "/config.gradle"`

/**
 * Append the config.gradle `apply from:` line to an app `build.gradle` body.
 *
 * Idempotent: a no-op when the line is already present, so repeated prebuilds
 * (and a prebuild over an already-patched, non-`--clean` project) don't stack
 * duplicate applies.
 */
export function appendConfigGradleApply(contents: string): string {
  if (contents.includes('/config.gradle')) {
    return contents
  }
  return `${contents}\n\n${CONFIG_GRADLE_APPLY}\n`
}

/**
 * Expo config mod: wire the SDK's config.gradle into the generated Android
 * app build so ConnectConfig.json values reach the native assets at build
 * time. Without it, the Android SDK ships its committed default collector
 * config and the app reports to the wrong endpoint (iOS is unaffected — it
 * uses AcousticConnectRNConfig.json via a separate flow).
 */
export const withConnectAndroidConfig: ConfigPlugin<ConnectPluginProps> = (
  config
) =>
  withAppBuildGradle(config, (cfg) => {
    // The RN/Expo template app build.gradle is Groovy. If a future template
    // switches to the Kotlin DSL the append heuristic no longer applies —
    // warn and skip rather than corrupt the file.
    if (cfg.modResults.language !== 'groovy') {
      console.warn(
        `[react-native-acoustic-connect-beta] Skipping Android config ` +
          `propagation: app/build.gradle is '${cfg.modResults.language}', ` +
          `expected 'groovy'. Add the following line manually:\n` +
          `  ${CONFIG_GRADLE_APPLY}`
      )
      return cfg
    }

    cfg.modResults.contents = appendConfigGradleApply(cfg.modResults.contents)
    return cfg
  })

/** True when ConnectConfig.json at `projectRoot` has Connect.PushEnabled === true. */
function isPushEnabled(projectRoot: string): boolean {
  const configPath = path.join(projectRoot, 'ConnectConfig.json')
  if (!fs.existsSync(configPath)) return false
  try {
    const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8')) as {
      Connect?: { PushEnabled?: unknown }
    }
    return parsed.Connect?.PushEnabled === true
  } catch {
    return false
  }
}

/**
 * Fail fast at prebuild when push is enabled but `android.package` is absent
 * from the configured `google-services.json`. FCM matches its client by package
 * name, so a mismatch makes Gradle fail much later at
 * `:app:processDebugGoogleServices` with the opaque "No matching client found
 * for package name …" — and only after a full prebuild + Gradle config. Surface
 * it here, at the moment the native project is (re)generated, with the exact fix
 * (CA-144135 §10b). `acoustic-connect doctor` performs the same check up front;
 * this is the guard for a developer who runs `expo run:android` directly.
 *
 * Only the genuine MISMATCH throws. A missing package / googleServicesFile is
 * left to `doctor` and the build itself (this mod doesn't duplicate those).
 */
export const withConnectAndroidGoogleServicesMatch: ConfigPlugin<
  ConnectPluginProps
> = (config) =>
  withDangerousMod(config, [
    'android',
    async (cfg) => {
      const projectRoot = cfg._internal?.projectRoot
      if (!projectRoot || !isPushEnabled(projectRoot)) return cfg

      const androidPackage = cfg.android?.package
      if (!androidPackage) return cfg

      // Most Expo + FCM consumers omit android.googleServicesFile and rely on
      // the default ./google-services.json at the project root. Mirror that
      // default (and `acoustic-connect doctor`'s) so the match check isn't
      // silently skipped for the common case.
      const gsFile = cfg.android?.googleServicesFile ?? 'google-services.json'
      const gsPath = path.isAbsolute(gsFile)
        ? gsFile
        : path.join(projectRoot, gsFile)
      if (!fs.existsSync(gsPath)) return cfg // missing file is doctor's / the build's concern

      let parsed: {
        client?: Array<{
          client_info?: { android_client_info?: { package_name?: string } }
        }>
      }
      try {
        parsed = JSON.parse(fs.readFileSync(gsPath, 'utf8'))
      } catch {
        return cfg // malformed google-services.json — doctor/build will report it
      }

      const packages = (parsed.client ?? [])
        .map((c) => c.client_info?.android_client_info?.package_name)
        .filter((p): p is string => typeof p === 'string')

      if (packages.length > 0 && !packages.includes(androidPackage)) {
        throw new Error(
          `[react-native-acoustic-connect-beta] android.package "${androidPackage}" ` +
            `has no matching client in ${gsFile} (it has: ${packages.join(', ')}).\n\n` +
            `FCM matches its client by package name, so the Android build would ` +
            `fail at :app:processDebugGoogleServices. Register "${androidPackage}" in ` +
            `the same Firebase project and re-download google-services.json, or set ` +
            `app.json android.package to one of the registered packages.\n` +
            `Note: changing android.package requires a clean prebuild — ` +
            `\`npx expo prebuild --platform android --clean\`.`
        )
      }
      return cfg
    },
  ])
