import os from 'os';
import path from 'path';
import {rooms, sendLog} from '../websocket';
import {getWorkspaces, getWorkspaceByLocation, createWorkspace, getWorkspace, getOpenApp} from '../database/cliconfig';
import {mkdir, readdir, stat, readFile, writeFile} from 'node:fs/promises';
import {exec, toCamelCase, toKebabCase} from '../utils';
import { codegenIntegrations, compileMultiplePlugins } from './compiler';
import { sha256 } from 'js-sha256';
import { npmInstall, podInstall } from './projectSetup';
import {FileSystem} from '../ui/src/types';

const homeDir = os.homedir();

export async function openWithFinder(location: string) {
  const command = `open -R ${location}`;
  console.log("Executing: " + command)
  await exec(command)
}

export async function openWithCode(location: string) {
  const command = `code ${location}`;
  console.log("Executing: " + command)
  await exec(command);
}

type AppManifest = {
  name: string;
  uuid: string;
  gitRepo: string|null;
};

type AppIntegration = {
  integrationCode: string;
  excerpt: string; // gitRepo
};

type ApptileConfig = {
  apptileServer: string;
  appconfigServer: string;
};

export async function cloneApp(workspaceId: number, manifest: AppManifest, appIntegrations: AppIntegration[], apptileConfig: ApptileConfig) {
  sendLog("cloning app");
  const workspace = getWorkspace(workspaceId);
  if (workspace) {
    const entries = await readdir(path.resolve(workspace.location), {withFileTypes: true});
    for (let entry of entries) {
      if (entry.isDirectory() && entry.name !== 'ReactNativeTSProjeect') {
        let apptileConfig: any = {};
        try {
          const apptileConfigRaw = await readFile(
            path.resolve(workspace.location, entry.name, 'apptile.config.json'),
            {encoding: 'utf8'}
          );
          apptileConfig = JSON.parse(apptileConfigRaw);
        } catch (err) {
          console.error("Found a folder with invalid or nonexistent apptile.config.json", err);
        }
        const appId = apptileConfig.APP_ID;
        if (manifest.uuid == appId) {
          throw new Error(`clone already exists for ${appId} at ${path.resolve(workspace.location, entry.name)}`);
        }
      }
    }
    const targetName = toKebabCase(manifest.name)
    const appLocation = path.resolve(workspace.location, targetName);
    if (manifest.gitRepo) {
      await exec(
        `git clone --depth=1 ${manifest.gitRepo} ${targetName}`, 
        {cwd: workspace.location}
      );
    } else {
      // unzip to location
      await exec(
        `git clone --depth=1 git@github.com:clearsight-dev/apptile-seed.git ${targetName}`, 
        {cwd: workspace.location}
      );

      const xcodeEnvLocation = path.resolve(workspace.location, targetName, 'ios/.xcode.env');
      let xcodeEnv = await readFile(xcodeEnvLocation, {encoding: 'utf8'});
      xcodeEnv = xcodeEnv.replace('# APPTILE_CLI_NODE_PATH_ENTRY', `export NODE_BINARY=${process.execPath}`); 
      await writeFile(xcodeEnvLocation, xcodeEnv);
      // Update Info.plist
      /*
      const infoPlistLocation = path.resolve(appLocation, 'ios/apptileSeed/Info.plist');
      const rawInfoPlist = await readFile(infoPlistLocation, {encoding: 'utf8'});
      const infoPlist = plist.parse(rawInfoPlist) as any;
      infoPlist.APPTILE_API_ENDPOINT = APPTILE_BACKEND_URL;
      infoPlist.APPTILE_UPDATE_ENDPOINT = APPCONFIG_SERVER_URL;
      const updatedPlist = plist.build(infoPlist);
      await writeFile(infoPlistLocation, updatedPlist);
      */
    }

    const nets = os.networkInterfaces();
    const localIpInterface = Object.values(nets).flatMap(it => it).filter((it: any) => (it.family === 'IPv4') && (it.address !== '127.0.0.1') && (it.netmask !== '255.255.0.0'))
    if (localIpInterface.length !== 1) {
      throw new Error("Could not determine local ip address");
    }
    const ip: string = (localIpInterface[0] as any).address;

    const SDK_PATH = path.resolve(workspace.location, 'ReactNativeTSProjeect');
    const APP_ID = manifest.uuid;
    const CLI_BASE_URL = `${ip}:3100/plugin-server`;
    const APPCONFIG_SERVER_URL = apptileConfig.appconfigServer; 
    let APPTILE_BACKEND_URL = apptileConfig.apptileServer;
    if (APPTILE_BACKEND_URL.includes('api.apptile.local')) {
      APPTILE_BACKEND_URL = `http://${ip}:3001`;
    }

    await writeFile(
      path.resolve(appLocation, 'apptile.config.json'),
      JSON.stringify({
        "-comment-": "This file is generated by cli and will be overwritten",
        "SDK_PATH": SDK_PATH,
        "APP_ID": APP_ID,
        "CLI_BASE_URL": CLI_BASE_URL,
        "APPTILE_BACKEND_URL": APPTILE_BACKEND_URL,
        "APPCONFIG_SERVER_URL": APPCONFIG_SERVER_URL
      }, null, 2)
    );

    await writeFile(
      path.resolve(appLocation, 'apptile.config.js'),
      `// This file is generated by cli and will be overwritten
export const SDK_PATH =  "${SDK_PATH}";
export const APP_ID = "${APP_ID}";
export const CLI_BASE_URL = "${CLI_BASE_URL}";
export const APPTILE_BACKEND_URL = "${APPTILE_BACKEND_URL}";
export const APPCONFIG_SERVER_URL = "${APPCONFIG_SERVER_URL}";
`
    )

    // if appintegrations exist that have repos then update package.json and codegen relevant plugins/navs
    if (appIntegrations.length > 0) {
      await codegenIntegrations(appLocation, appIntegrations);
    }

    const to = path.resolve(workspace.location, targetName);
    const appDelegatePath = path.resolve(to, 'ios/apptileSeed/AppDelegate.mm');
    let appDelegate = await readFile(
      appDelegatePath,
      {encoding: 'utf8'}
    );
    appDelegate = appDelegate.replace('__METRO_IP__', `http://${ip}:8081`);
    await writeFile(appDelegatePath, appDelegate)
    sendLog("Start installation");
    await npmInstall(to);
    await podInstall(to);
  } else {
    sendLog("Couldn't find the workspace to clone into")
    throw new Error("workspace doesn't exist");
  }
}

export async function listWorkspaces() {
  try {
    const workspaces = getWorkspaces();
    if (workspaces) {
      let result: FileSystem.WorkspaceData[] = [];
      for (let workspace of workspaces) {
        const sdkLocation = path.resolve(workspace.location, 'ReactNativeTSProjeect');

        let {stdout: branchName}  = await exec(`git rev-parse --abbrev-ref HEAD`, {cwd: sdkLocation});
        branchName = branchName.trim();

        // let {stdout: sdkHash} = await exec(`git log -1 --format="%H"`, {cwd: sdkLocation})
        // sdkHash = sdkHash.trim();
        let {stdout: sdkHash} = await exec(
          `git rev-list --left-right --count HEAD...origin/${branchName}`, 
          {cwd: sdkLocation}
        );
        sdkHash = sdkHash.trim();

        let workspaceResult: FileSystem.WorkspaceData = {
          id: workspace.id,
          sdkHash: `${branchName} (${sdkHash})`,
          location: workspace.location,
          repos: [],
        };

        const entries = await readdir(path.resolve(workspace.location), {withFileTypes: true});
        for (let entry of entries) {
          const isDir = entry.isDirectory();
          if (isDir && (entry.name !== 'ReactNativeTSProjeect')) {
            const repo: FileSystem.Repo = {
              name: entry.name,
              fullPath: path.resolve(workspace.location, entry.name),
              gitRepo: '',
              appId: '',
              apptileServer: '',
              appconfigServer: '',
              isOpen: false,
              codePushBundles: {
                ios: [],
                android: []
              }
            };
            // get appid and git branch
            const apptileConfigPath = path.resolve(workspace.location, entry.name, "apptile.config.json");
            try {
              const rawConfig = await readFile(apptileConfigPath, {encoding: 'utf-8'});
              const config = JSON.parse(rawConfig);
              repo.appId = config.APP_ID;
              repo.apptileServer = config.APPTILE_BACKEND_URL;
              repo.appconfigServer = config.APPCONFIG_SERVER_URL;
              repo.isOpen = !!getOpenApp(config.APP_ID);
            } catch(err) {
              console.error("Failed to get appid from repo: ", err);
              repo.appId = "error";
            }

            try {
              const {stdout, stderr} = await exec(
                `git remote get-url origin`, 
                {cwd: path.resolve(workspace.location, entry.name)}
              );
              if (stdout) {
                repo.gitRepo = stdout.trim();
              } else {
                repo.gitRepo = stderr.trim();
              }
              
            } catch(err) {
              console.error("Failed to get git repository origin ", err);
            }


            // get ios and android folder contents for codePushBundles
            try {
              const bundles = await readdir(
                path.resolve(workspace.location, entry.name, 'remoteCode/generated/bundles/ios'),
                {withFileTypes: true}
              );
              for (let bundle of bundles) {
                if (bundle.isDirectory()) {
                  repo.codePushBundles.ios.push({
                    fullPath: path.resolve(bundle.parentPath, bundle.name),
                    timestamp: parseInt(bundle.name)
                  });
                }
              }
            } catch (err) {
              console.error("Failed to get ios codepush bundles");
            }

            try {
              const bundles = await readdir(
                path.resolve(workspace.location, entry.name, 'remoteCode/generated/bundles/android'),
                {withFileTypes: true}
              );
              for (let bundle of bundles) {
                if (bundle.isDirectory()) {
                  repo.codePushBundles.android.push({
                    fullPath: path.resolve(bundle.parentPath, bundle.name),
                    timestamp: parseInt(bundle.name)
                  });
                }
              }
            } catch (err) {
              console.error("Failed to get ios codepush bundles");
            }

            workspaceResult.repos.push(repo);
          }
        }
        result.push(workspaceResult);
      }

      return result;
    } else {
      throw new Error("Failed when getting workspaces");
    }
  } catch (err) {
    console.error("Failed to get workspaces: ", err);
    throw err;
  }
}

export async function getPluginBundle(appId: string) {
  // Get opened app. If its not open return error
  const openedApp = getOpenApp(appId);
  if (!openedApp) {
    throw new Error("Open the app first.")
  } else {
    // Get location from opened app
    const appLocation = openedApp.repoPath;
    // list all plugins from the folder
    // get hash for plugins
    const remoteCode = path.resolve(appLocation, 'remoteCode')

    const pluginsRoot = path.resolve(remoteCode, 'plugins');
    const plugins: string[] = [];
    try {
      const pluginEntries = await readdir(pluginsRoot, {withFileTypes: true});
      for (let entry of pluginEntries) {
        if (entry.isDirectory()) {
          plugins.push(entry.name);
        }
      }
    } catch (err: any) {
      if (err?.code !== 'ENOENT') {
        console.error("Unhandelable error", err);
        throw err;
      }
    }

    const generatedRoot = path.resolve(remoteCode, 'generated');
    const generatedFolderName = sha256(plugins.join('_')).toString();
    let bundlePath = path.resolve(generatedRoot, generatedFolderName, 'dist/bundle.js');

    try {
      await stat(bundlePath);
    } catch (err: any) {
      if (err?.code === 'ENOENT') {
        const compileResult = await compileMultiplePlugins(remoteCode, plugins, generatedFolderName);
        if (!compileResult.errors) {
          bundlePath = path.resolve(generatedRoot, 'dist/bundle.js');
        } else {
          console.error('COMPILATION FAILED: ', compileResult)
          throw new Error(compileResult.message);
        }
      } else {
        throw err;
      }
    }

    return bundlePath;
  }
}

export async function compilePluginBundle(appId: string) {
  const openedApp = getOpenApp(appId);
  if (!openedApp) {
    throw new Error("Open the app first.");
  } else {
    const appLocation = openedApp.repoPath;
    const remoteCode = path.resolve(appLocation, 'remoteCode');
    const pluginsRoot = path.resolve(remoteCode, 'plugins');
    const pluginEntries = await readdir(pluginsRoot, {withFileTypes: true});
    const plugins: string[] = [];
    for (let entry of pluginEntries) {
      if (entry.isDirectory()) {
        plugins.push(entry.name);
      }
    }

    const generatedFolderName = sha256(plugins.join('_')).toString();
    const result = await compileMultiplePlugins(remoteCode, plugins, generatedFolderName);
    return result;
  }
}

export async function getLocalBundles(appLocation: string) {
  const iosBundlesFolder = path.resolve(appLocation, 'remoteCode/generated/bundles/ios');
  let iosBundles: Array<{fullPath: string; timestamp: number;}> = [];
  const androidBundlesFolder = path.resolve(appLocation, 'remoteCode/generated/bundles/android');
  let androidBundles: Array<{fullPath: string; timestamp: number;}> = [];
  try {
    const entries = await readdir(
      iosBundlesFolder,
      {withFileTypes: true}
    );

    for (let entry of entries) {
      iosBundles.push({
        fullPath: path.resolve(iosBundlesFolder, entry.name, 'bundle.zip'),
        timestamp: parseInt(entry.name)
      });
    }
  } catch (err) {
    console.error("Failed to get local ios bundles: ", err);
  }

  try {
    const entries = await readdir(
      androidBundlesFolder,
      {withFileTypes: true}
    );

    for (let entry of entries) {
      androidBundles.push({
        fullPath: path.resolve(androidBundlesFolder, entry.name, 'bundle.zip'),
        timestamp: parseInt(entry.name)
      });
    }
  } catch (err) {
    console.error("Failed to get local ios bundles: ", err);
  }

  return {
    ios: iosBundles, 
    android: androidBundles
  };
}

export async function getAppLocation(workspaceId: number, appId: string) {
  const workspace = getWorkspace(workspaceId);
  if (workspace) {
    const entries = await readdir(workspace.location, {withFileTypes: true});
    let appLocation = '';
    for (let entry of entries) {
      if (!['ReactNativeTSProjeect', '.DS_Store'].includes(entry.name)) {
        try {
          const apptileConfigPath = path.resolve(workspace.location, entry.name, 'apptile.config.json');
          const rawConfig = await readFile(apptileConfigPath, {encoding: 'utf8'});
          const config = JSON.parse(rawConfig);
          if (appId === config.APP_ID) {
            appLocation = path.resolve(workspace.location, entry.name);
            break;
          }
        } catch (err) {
          console.error("Failed to read apptile config", err);
        }
      }
    }
    return appLocation;
  } else {
    console.error("Could not find workspace");
    throw new Error("Not found")
  }
}

export async function ensureDefaultWorkspace() {
  // await new Promise((resolve) => {
  //   setTimeout(() => resolve({}), 5000)}
  // ); debugger
  console.log("ensuring default workspace")
  // ensure that workspaces table has an entry linking path to ~/apptile-cli-home
  const defaultWorkspaceLocation = path.resolve(homeDir, 'apptile-cli-home');
  const workspace = getWorkspaceByLocation(defaultWorkspaceLocation);
  if (!workspace) {
    console.log("Creating database entry")
    createWorkspace(defaultWorkspaceLocation);
  }

  let create = false;
  try {
    await readdir(defaultWorkspaceLocation);
  } catch (err: any) {
    if (err?.code === 'ENOENT') {
      console.log("default cli home folder doesn't exist")
      create = true;
    } else {
      console.error("Failed to verify existence of default workspace", err);
      throw err;
    }
  }

  let clone = false;
  const defaultSDKLocation = path.resolve(defaultWorkspaceLocation, 'ReactNativeTSProjeect');
  if (!create) {
    try {
      await stat(defaultSDKLocation);
    } catch(err: any) {
      if (err?.code === 'ENOENT') {
        console.log("Default SDK clone doesn't exist")
        clone = true;
      } else {
        console.error("Failed to verify existence of sdk");
        throw err;
      }
    }
  }

  const expectedRemote = 'git@github.com:clearsight-dev/ReactNativeTSProjeect.git';
  let remoteLocation: string = '';
  if (create) {
    console.log("Creating ~/apptile-cli-home")
    await mkdir(defaultWorkspaceLocation);
    console.log("Cloning ReactNativeTSProjeect SDK")
    const {stdout, stderr} = await exec(
      `git clone --depth=1 --branch=scion/v3-roadmap ${expectedRemote}`, 
      {cwd: defaultWorkspaceLocation}
    );

      remoteLocation = expectedRemote;
      console.log(stdout);
      console.log(stderr);
  } else if (clone) {
    console.log("Cloning SDK into existing ~/apptile-cli-home folder")
    const {stdout, stderr} = await exec(
      `git clone --depth=1 --branch=scion/v3-roadmap ${expectedRemote}`, 
      {cwd: defaultWorkspaceLocation}
    );

    remoteLocation = expectedRemote;
    console.log(stdout);
    console.log(stderr);
  } else {
    console.log("Checking validity of sdk in default workspace at: ", defaultSDKLocation);
    const {stdout, stderr}  = await exec(
      `git remote get-url origin`,
      {cwd: defaultSDKLocation}
    );

    remoteLocation = stdout.trim();
  }

  if (remoteLocation !== expectedRemote) {
    throw new Error(`workspace is corrupted. Expceted sdk to be cloned from ${expectedRemote} but found ${remoteLocation}`);
  }

  return `Paste the following in your browser console to get started: 
localStorage.setItem('plugin-server-url', 'http://localhost:3100/plugin-server'); localStorage.setItem('enablevim', 'yes'); window.location.reload();
`;
}

type ProjectConfig = {
  SDK_PATH: string;
  APP_ID: string;
  CLI_BASE_URL: string;
  APPTILE_BACKEND_URL: string;
  APPCONFIG_SERVER_URL: string;
};

function narrowProjectConfig(data: any): data is ProjectConfig {
  return (
    (typeof(data?.SDK_PATH) === "string") && 
    (typeof(data?.APP_ID) === "string") && 
    (typeof(data?.CLI_BASE_URL) === "string") && 
    (typeof(data?.APPTILE_BACKEND_URL) === "string") && 
    (typeof(data?.APPCONFIG_SERVER_URL) === "string") 
  );
}

export async function getAppConfigsFromFS(appId: string): Promise<ProjectConfig> {
  const openedApp = getOpenApp(appId);
  if (openedApp) {
    const configPath = path.resolve(openedApp.repoPath, 'apptile.config.json');
    const rawConfig = await readFile(configPath, {encoding: 'utf8'});
    const config = JSON.parse(rawConfig);
    if (narrowProjectConfig(config)) {
      return config;
    } else {
      throw new Error("Could not validate the apptile.config.json file's type");
    }
  } else {
    throw new Error("Could not find opened app");
  }
}
