import fs, { stat } from 'fs';
import path from 'path';
import { commands, ExtensionContext, ExtensionMode, Uri } from 'vscode';
import StatusBar from './status-bar/StatusBar';
import DevServer, { DevServerStatus } from './server/DevServer';
import { getProjectFolder, getWorkspaceFolder, traverseSubFolders, isWorkspacesRoot } from './utils/folders';
import { detectScriptsVersion } from '@sculpt-ui/server';
import SculptServer, { SculptServerStatus } from '@sculpt-ui/source-server/lib/SculptServer';
import telemetry from './telemetry';
import { Request, RequestHandler, Response } from 'express';
import { SourceRef } from '@sculpt-ui/core/lib/types';
import { selectCode } from './utils/vscode-file';
import { log, logError } from './utils/vscode-output';

process.env.REACT_EDITOR = 'code';

let statusBar: StatusBar;
let sculptServer: SculptServer;
let devServer: DevServer | undefined = undefined;
let gStoragePath = '';

const urlPathPrefix = '/_sculpt';
const localDevMode: string = 'dev-local';

const clientHandler: (x: string) => RequestHandler = (assetsFolder: string) => (req: Request, res: Response) => {
  let reqPathBase = urlPathPrefix;
  if (req.params.key && req.params.key !== 'static') {
    reqPathBase += `/${req.params.key}`;
  }
  const subPath = req.originalUrl.substr(reqPathBase.length + 1);
  const filePath = path.resolve(assetsFolder, 'sculpt-client', subPath);
  fs.access(filePath, fs.constants.F_OK, err => {
    if (err) {
      if (req.accepts(['html', 'text/html'])) {
        res.sendFile('sculpt-client/index.html', { root: assetsFolder });
      } else {
        res.status(404).end();
      }
    } else {
      res.sendFile(`sculpt-client/${subPath}`, { root: assetsFolder });
    }
  });
};

const extensionsHandler: (x: string, packagesFolder: string) => RequestHandler = (
  assetsFolder: string,
  packagesFolder: string,
) => (req: Request, res: Response) => {
  const subPath = req.originalUrl.substr(req.originalUrl.indexOf('/extensions'));
  if (process.env.mode === localDevMode && subPath.startsWith('extension/styles/dist')) {
    res.sendFile(subPath.replace('extension/dist', 'extension'), {
      root: packagesFolder,
    });
  } else {
    res.sendFile(subPath, { root: assetsFolder });
  }
};

const ideService = {
  getServerInfo: async () => ({
    rootPath: process.cwd(),
    message: 'Sculpt is alive!',
    ide: 'VS Code',
  }),
  selectInEditor: async (id: string, sourceRef: SourceRef) => {
    selectCode(
      sourceRef.filePath,
      {
        line: sourceRef.endLine ? sourceRef.endLine - 1 : sourceRef.line,
        character: sourceRef.endColumn ? sourceRef.endColumn - 1 : 0,
      },
      { line: sourceRef.line - 1, character: sourceRef.column ? sourceRef.column - 1 : 0 },
    );
  },
};

export async function activate(context: ExtensionContext) {
  const { subscriptions, extensionPath, globalStoragePath } = context;
  gStoragePath = globalStoragePath;
  telemetry.activate(context);
  telemetry.addCommonProperties({ extensionMode: ExtensionMode[context.extensionMode] });
  telemetry.sendEvent('activate');
  if (!statusBar) {
    statusBar = new StatusBar();
    statusBar.subscribe(subscriptions);
  }

  const assetsFolder = Uri.file(path.join(extensionPath, 'assets')).fsPath;
  const packagesFolder = Uri.file(path.join(extensionPath, '../packages')).fsPath;

  // IDEServer always activated
  const workspaceFolder = getWorkspaceFolder();
  log(process.cwd());
  if (!sculptServer) {
    sculptServer = new SculptServer({
      clientHandler: clientHandler(assetsFolder),
      ideService: ideService,
      onLog: message => log(message),
      showErrorMessage: message => logError(message),
      extensionsHandler: extensionsHandler(assetsFolder, packagesFolder),
      onStatusChanged: (status: SculptServerStatus) => statusBar.setConnected(status === SculptServerStatus.connected),
    });
  }
  const sculptServerUrl = await sculptServer.start();
  if (globalStoragePath && workspaceFolder) {
    if (!fs.existsSync(globalStoragePath)) {
      fs.mkdirSync(globalStoragePath, { recursive: true });
    }
    const settingsFile = path.join(globalStoragePath, 'sculpt-ide.json');
    /* TODO: Use a lock file here */
    let ideConfig: { [folder: string]: string } = {};
    if (fs.existsSync(settingsFile)) {
      try {
        ideConfig = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
      } catch (err) {
        console.error(
          'Could not correctly read settings file. Presumably corrupt and will be overwritten.',
          settingsFile,
        );
      }
    }
    ideConfig[workspaceFolder] = sculptServerUrl;
    fs.writeFileSync(settingsFile, JSON.stringify(ideConfig, null, 2), 'utf8');
  }
  const folder = getProjectFolder(undefined, workspaceFolder);
  if (!folder) {
    return;
  }
  const rootDetectedScript = detectScriptsVersion(folder);
  let scriptsAvailable = isWorkspacesRoot(folder) ? rootDetectedScript === 'sculpt' : !!rootDetectedScript;
  if (!scriptsAvailable) {
    traverseSubFolders(
      folder,
      (dir: string) => {
        const detected = !!detectScriptsVersion(dir);
        if (detected) {
          scriptsAvailable = true;
        }
        return detected;
      },
      3,
      ['node_modules', '.vscode', '.git'],
    );
  }
  if (scriptsAvailable) {
    statusBar.setRunAvailable(true);
    if (!devServer) {
      devServer = new DevServer(
        extensionPath,
        sculptServerUrl,
        status => {
          statusBar.setRunning(status === DevServerStatus.started || status === DevServerStatus.stopping);
          statusBar.setInTransition(status === DevServerStatus.starting || status === DevServerStatus.stopping);
        },
        (message: string, duration?: number) => {
          statusBar.setTemporaryMessage(message, duration);
        },
      );
    }

    const start = async () => {
      const result = await devServer?.start();
      if (result) sculptServer.tryToInitProject(result);
    };

    subscriptions.push(
      commands.registerCommand('sculpt-ui.start', start),
      commands.registerCommand('sculpt-ui.stop', devServer.stop),
    );
  }
}

// this method is called when your extension is deactivated
export function deactivate() {
  const workspaceFolder = getWorkspaceFolder();
  if (gStoragePath && workspaceFolder) {
    if (!fs.existsSync(gStoragePath)) {
      fs.mkdirSync(gStoragePath, { recursive: true });
    }
    const settingsFile = path.join(gStoragePath, 'sculpt-ide.json');
    let ideConfig: { [folder: string]: string } = {};
    if (fs.existsSync(settingsFile)) {
      try {
        ideConfig = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
      } catch (err) {
        console.error(
          'Could not correctly read settings file. Presumably corrupt and will be overwritten.',
          settingsFile,
        );
      }
    }
    delete ideConfig[workspaceFolder];
    fs.writeFileSync(settingsFile, JSON.stringify(ideConfig, null, 2), 'utf8');

    if (devServer) {
      devServer.stop();
    }
    if (sculptServer) {
      sculptServer.stop();
    }
    telemetry.sendEvent('deactivate');
    telemetry.deactivate();
  }
}
