import {
  AndroidConfig,
  ConfigPlugin,
  withAndroidManifest,
  withDangerousMod,
  withGradleProperties
} from '@expo/config-plugins';
import { ExpoConfig } from 'expo/config';
import { promises as fs } from 'fs';
import { SmartechBaseProps } from './smartechBaseProps';
import { Helper } from './helper';
import { appBuildGradlePath, AutoFetchLocationKey, dependency, hanselTestDeviceSnippet, repositorySnippet, smartechHanselEncryptionKey, smartechInitSnippet, smartechInitSnippetJava, smartechInitSnippetKotlin, hanselTestDeviceUpdatedSnippet, smartechTestDeviceSnippet } from './androidConstants';
import path from 'path';

const { addMetaDataItemToMainApplication, getMainApplicationOrThrow } = AndroidConfig.Manifest;


const modifyMainApplication = async (filePath: string, props: SmartechBaseProps) => {

  const fileExtension = path.extname(filePath);
  let fileContent = await fs.readFile(filePath, 'utf-8');
  let isKotlinFile = props.android.isKotlinProject;

  // adding the import statement
  const importStatement = isKotlinFile ? smartechInitSnippet.createImportStatementKotlin(props) : smartechInitSnippet.createImportStatementJava(props);

  if (!fileContent.includes(importStatement)) {
    const packageIndex = fileContent.indexOf('package');
    const insertIndex = fileContent.indexOf('\n', packageIndex) + 1;
    fileContent = `${fileContent.slice(0, insertIndex)}\n${importStatement}\n${fileContent.slice(insertIndex)}`;
  }

  let onCreateMethodRegex = isKotlinFile ? smartechInitSnippet.onCreateMethodRegexKotlin : smartechInitSnippet.onCreateMethodRegexJava;
  let newArchConditionRegex = isKotlinFile ? smartechInitSnippet.newArchConditionRegexKotlin : smartechInitSnippet.newArchConditionRegexJava;

  const onCreateMatch = fileContent.match(onCreateMethodRegex);

  if (!onCreateMatch) {
    throw new Error('Could not find the onCreate method in MainApplication.');
  }

  let onCreateBody = onCreateMatch[0].trim();

  const areLogsEnabled = props.android.isLogEnabled;
  const smartechInitCodeBlock = props.android.isKotlinProject ? smartechInitSnippetKotlin(props).trim() : smartechInitSnippetJava(props).trim();

  // If the flag is true, insert the custom code inside the New Architecture check.
  if (props.isNewArchEnabled) {
    const newArchCheckRegex = smartechInitSnippet.newArchCheckRegex;
    const newArchCheckMatch = onCreateBody.match(newArchCheckRegex);

    if (newArchCheckMatch) {
      const newArchCheckBody = newArchCheckMatch[1].trim();
      const modifiedNewArchCheckBody = `${newArchCheckBody}\n   ${smartechInitCodeBlock}    \n`;
      onCreateBody = onCreateBody.replace(newArchCheckBody, modifiedNewArchCheckBody);
    } else {
      onCreateBody += `\n  ${smartechInitCodeBlock}  \n`;
    }
  } else {
    onCreateBody += `\n   ${smartechInitCodeBlock}\n`;
  }

  // Replace the original onCreate method with the modified one
  fileContent = fileContent.replace(onCreateMethodRegex, onCreateBody);

  // Save the modified content back to the file
  await fs.writeFile(filePath, fileContent, 'utf-8');
};

// Expo Config Plugin to modify MainApplication.kt or MainApplication.java
const addSmartechInitCode: ConfigPlugin<SmartechBaseProps> = (
  config,
  props
) => {
  return withDangerousMod(config, [
    'android',
    async (config) => {

      const isKotlinFile = props.android.isKotlinProject;
      const packageName = config.android?.package ?? '';
      const mainApplicationJavaPath = `./android/app/src/main/java/${packageName.replace(/\./g, '/')}/MainApplication.java`;
      const mainApplicationKotlinPath = `./android/app/src/main/java/${packageName.replace(/\./g, '/')}/MainApplication.kt`;
      const mainApplicationPath = isKotlinFile ? mainApplicationKotlinPath : mainApplicationJavaPath;

      await modifyMainApplication(mainApplicationPath, props);

      return config;
    },
  ]);
};

const addMetaDataToManifest: ConfigPlugin<SmartechBaseProps> = (config, props) => {
  return withAndroidManifest(config, async config => {

    const androidManifest = config.modResults;
    const mainApplication = getMainApplicationOrThrow(androidManifest);
    // Adding Smartech MetaData to Manifest
    const smartechMetaData = props.android.smartechMetaData

    if (smartechMetaData) {
      smartechMetaData.forEach(item => {
        addMetaDataItemToMainApplication(mainApplication, item.name, item.value)
      });
    }

    // If autoFetchLocation is enabled then add it inside the manifest file,

    if (props.android.autoFetchLocation != undefined) {
      addMetaDataItemToMainApplication(mainApplication, AutoFetchLocationKey, props.android.autoFetchLocation ? "1" : "0")
    }

    /**
     *  Check Hansel is enabled or not, If not then we dont 
     *  add the meta data to manifest.
     */
    const isHanselEnabled = props.android.smartechNudges.smartechNudgesEnabled;

    if (isHanselEnabled) {
      const hanselMetaData = props.android.smartechNudges.smartechNudgesMetaData;
      hanselMetaData.forEach(item => {
        addMetaDataItemToMainApplication(mainApplication, item.name, item.value);
      })

      // check for the Encryption value from
      addMetaDataItemToMainApplication(mainApplication, smartechHanselEncryptionKey, `${props.android.smartechNudges.useEncryption}`);
    }

    return config;
  });
};

// Helper function to check if the dependency exists in the contents
function containsDependency(contents: string, dependency: string) {
  const regex = new RegExp(`api\\s+"${dependency}[^"]*"`);
  return regex.test(contents);
}


// Set custom Android build.gradle modifications
const addSmartechDependency: ConfigPlugin<SmartechBaseProps> = (config, props) => {
  return withDangerousMod(config, [
    'android',
    async (config) => {

      const appBuildGradleContents = await fs.readFile(appBuildGradlePath, 'utf-8');

      let modifiedAppContents = appBuildGradleContents.replace(
        'dependencies {',
        `${repositorySnippet}\n\ndependencies {`
      );

      // Adding Smartech Base dependency in build.gradle
      modifiedAppContents = Helper.Android.addDependency(modifiedAppContents, dependency.smartechBase, props.android.SMARTECH_BASE_SDK_VERSION)
      await fs.writeFile(appBuildGradlePath, modifiedAppContents, 'utf-8');
      return config;
    }
  ]);
};


const modifyMainActivityForTestDevice = (content: string, addTestDeviceHansel: boolean, addTestDeviceSmartech: boolean, isKotlin: boolean): { modifiedContent: string, importsAdded: string[] } => {

  const superOnCreate = 'super.onCreate';
  const superIndex = content.indexOf(superOnCreate);
  const insertIndex = content.indexOf(')', superIndex) + 1;

  if (superIndex === -1) {
    console.warn('super.onCreate not found in MainActivity');
    return { modifiedContent: content, importsAdded: [] };
  }

  let modified = content;

  // Implementing the AddTestDevice code
  const linesToInsert: string[] = [];
  const importsAdded: string[] = [];

  const hanselSnippetUpdated = hanselTestDeviceUpdatedSnippet(isKotlin);
  const smartechSnippet = smartechTestDeviceSnippet(isKotlin);

  if (addTestDeviceHansel) {
    linesToInsert.push(hanselSnippetUpdated.methodStatement);
    importsAdded.push(hanselSnippetUpdated.importStatement);
  }

  if (addTestDeviceSmartech) {
    linesToInsert.push(smartechSnippet.methodStatement);
    importsAdded.push(smartechSnippet.importStatement);
  }

  // Add lines to onCreate if any
  if (linesToInsert.length > 0) {
    const indent = content.substring(0, superIndex).match(/\s*$/)?.[0] || '    ';
    const formattedLines = linesToInsert
      .map(line => `${indent}${line}`)
      .join('\n');
    modified = [
      modified.slice(0, insertIndex),
      `\n${formattedLines}`,
      modified.slice(insertIndex),
    ].join('');
  }
  return { modifiedContent: modified, importsAdded };
}

const addTestDevice: ConfigPlugin<SmartechBaseProps> = (config, props) => {

  return withDangerousMod(config, [
    'android',
    async (config) => {

      const isKotlin = props.android.isKotlinProject;

      const isHanselEnabled = props.android.smartechNudges.smartechNudgesEnabled;
      const addTestDeviceForHansel = props.android.smartechNudges.addTestDevice ?? false;
      const addTestDeviceForSmartech = props.android.addTestDevice ?? false;

      const packageName = config.android?.package ?? '';
      const mainActivityDir = path.resolve(
        config.modRequest.projectRoot,
        `android/app/src/main/java/${packageName.replace(/\./g, '/')}`
      );
      const mainActivityPath = path.join(mainActivityDir, isKotlin ? 'MainActivity.kt' : 'MainActivity.java')

      // Check if the file exists
      try {
        let content = await fs.readFile(mainActivityPath, 'utf-8');

        const { modifiedContent, importsAdded } = modifyMainActivityForTestDevice(
          content,
          (isHanselEnabled && addTestDeviceForHansel),
          addTestDeviceForSmartech,
          isKotlin
        );

        // Add imports at the top if any
        if (importsAdded.length > 0) {
          const packageIndex = modifiedContent.indexOf('package');
          if (packageIndex === -1) throw new Error('Package statement not found');
          const insertIndex = modifiedContent.indexOf('\n', packageIndex) + 1;
          content = [
            modifiedContent.slice(0, insertIndex),
            importsAdded.join('\n') + '\n',
            modifiedContent.slice(insertIndex),
          ].join('');
        } else {
          content = modifiedContent;
        }

        await fs.writeFile(mainActivityPath, content, 'utf-8');
      } catch (err) {
        console.error(`MainActivity file does not exist at path: ${mainActivityPath}`);
      }
      return config;
    },
  ]);
};

// Main plugin function to create xml directory and copy the native.xml file
const withAndroidXMLFilesBackup: ConfigPlugin<SmartechBaseProps> = (config, props) => {

  try {
    const allowBackup = config.android?.allowBackup ?? false;
    // If allowBackup is not true, skip the rest of the operations
    if (!allowBackup) {
      return config;
    }

    const targetSdkVersion = config.plugins?.find(
      ([pluginName]) => pluginName === 'expo-build-properties'
    )?.[1]?.android?.targetSdkVersion || 31; // Fallback to 31 if not defined

    config = withDangerousMod(config, ['android', async (config) => {
      // Path to the React Native app's assets/native.xml file
      const projectRoot = config.modRequest.projectRoot;
      // Read the backup folder path from the config file
      const backupAssetFolder = props.android.backupXMLFiles;

      if (!backupAssetFolder || backupAssetFolder.length === 0) {
        console.log('backupXMLFiles path is either undefined or empty');
        return config;
      }

      // Construct the full path to the backup folder
      const backupFolder = path.join(projectRoot, backupAssetFolder);

      // Ensure the folder exists
      await ensureDirectoryExists(backupFolder);

      const sourceFile = path.join(backupAssetFolder, 'backup.xml');
      const sourceFile31 = path.join(backupAssetFolder, 'backup_31.xml');

      // Path to the Android res/xml directory
      const xmlDir = path.join(projectRoot, 'android', 'app', 'src', 'main', 'res', 'xml');

      // Ensure the xml directory exists
      ensureDirectoryExists(xmlDir);

      // Destination path for the native.xml file inside res/xml
      const destinationFile = path.join(xmlDir, 'backup.xml');
      copyFile(sourceFile, destinationFile);

      if (targetSdkVersion >= 31) {
        // Destination path for the native.xml file inside res/xml
        const destinationFile31 = path.join(xmlDir, 'backup_31.xml');
        copyFile(sourceFile31, destinationFile31);
      }

      return config;
    }]);

    // Modify the AndroidManifest.xml
    config = withAndroidManifest(config, (config) => {
      const application = config.modResults.manifest.application?.[0];
      if (application) {
        application['$']['android:fullBackupContent'] = '@xml/backup'
        if (targetSdkVersion >= 31) {
          application['$']['android:dataExtractionRules'] = '@xml/backup_31'
        }
      }

      return config;
    });

  } catch (error) {
    console.error("Error in withAndroidXMLFilesBackup function")
  }
  return config;

};

// Utility to ensure directory exists asynchronously
const ensureDirectoryExists = async (directory: string): Promise<void> => {
  try {
    await fs.access(directory);
  } catch (error) {
    await fs.mkdir(directory, { recursive: true });
  }
};

// Utility to copy a file asynchronously if it doesn't already exist
const copyFile = async (source: string, destination: string): Promise<void> => {
  try {
    await fs.access(source);
    try {
      await fs.access(destination);
      console.log(`${destination} already exists. Skipping file copy.`);
    } catch (error) {
      // Destination does not exist, proceed to copy
      await fs.copyFile(source, destination);
    }
  } catch (error) {
    console.warn(`Source file ${source} does not exist`);
  }
};

export const withNetcoreAndroid: ConfigPlugin<SmartechBaseProps> = (config, props) => {
  try {
    config = withAndroidXMLFilesBackup(config, props);
    config = addMetaDataToManifest(config, props);
    config = updateGradlePropertise(config, props);
    config = addSmartechDependency(config, props);
    config = addSmartechInitCode(config, props);
    config = addTestDevice(config, props);
  } catch (error) {
    console.error('Failed to add smartech base expo plugin custom code with error:', error);
  }
  return config;
};

/**
 * 
 * This method will add the Smartech gradle dependencies to the build.gradle.
 * 
 * @param config Expo Config
 * @param props  SmartechBaseProps
 * @returns modified config
 */
const updateGradlePropertise = (config: ExpoConfig, props: SmartechBaseProps) => {
  return withGradleProperties(config, async (config) => {
    const customProperties = [
      { key: 'SMARTECH_BASE_SDK_VERSION', value: props.android.SMARTECH_BASE_SDK_VERSION }
    ];

    // Update the modResults with custom properties
    for (const property of customProperties) {
      if (!config.modResults.some((item: any) => item.type === 'property' && item.key === property.key)) {
        config.modResults.push({
          type: 'property',
          key: property.key,
          value: property.value,
        });
      }
    }

    return config;
  });
};
