/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { exec, spawn } from 'child_process';
import { getIDEType, setCjmpSdkRoot, cjmpDoctorWithSpawn, getDefaultShell } from './utils';
import {
  isValidCjmpSdkRoot,
  resolveCjmpSdkRoot,
  resolveKeelsCommand,
} from './cjmpSdk';

export const PROJECT_TEMPLATE_OPTIONS = ['app', 'library', 'logic-module', 'module'];

export async function createCjmpProject(context: vscode.ExtensionContext): Promise<void> {
  const isSdkReady = await executeSDK(context);
  if (!isSdkReady) {
    return;
  }
  await createProjectTemplate();
}

/**
 * Set SDK-related information
 */
export async function executeSDK(context: vscode.ExtensionContext): Promise<boolean> {
  if (!resolveCjmpSdkRoot()) {
    const workspaceSdkPath = vscode.workspace
      .getConfiguration('CJMP')
      .inspect<string>('sdkPath')
      ?.workspaceValue
      ?.trim();
    const sdkSettingTarget = workspaceSdkPath
      ? vscode.ConfigurationTarget.Workspace
      : vscode.ConfigurationTarget.Global;

    const sdkChoose = await vscode.window.showInformationMessage(
      'Please provide the root path of your CJMP SDK.',
      'Ok',
      'Cancel'
    );

    if (!sdkChoose || sdkChoose === 'Cancel') {
      return false;
    }

    const localPathUri = await vscode.window.showOpenDialog({
      canSelectFolders: true,
      canSelectFiles: false,
      canSelectMany: false,
      openLabel: 'Select Folder for CJMP SDK'
    });

    if (localPathUri && localPathUri.length > 0) {
      const localPath = localPathUri[0].fsPath;

      if (!fs.existsSync(localPath)) {
        vscode.window.showErrorMessage('Invalid path provided or path does not exist.');
        return false;
      }

      if (!isValidCjmpSdkRoot(localPath)) {
        vscode.window.showErrorMessage(
          `The selected folder is not a valid CJMP SDK root: "${localPath}". `
          + 'Select the SDK root containing cjmp-tools/bin, cjmp-tools/third_party, and cjmp-tools/tools.',
        );
        return false;
      }
      await setCjmpSdkRoot(localPath, sdkSettingTarget);
      vscode.window.showInformationMessage(`CJMP SDK root set to: ${localPath}. Running "keels doctor -v" ...`);
      await cjmpDoctorWithSpawn();
    } else {
      return false;
    }
  }
  return true;
}

/**
 * select the path and create a CJMP project.
 */
async function createProjectTemplate(): Promise<void> {
  const localPathUri = await vscode.window.showOpenDialog({
    canSelectFolders: true,
    canSelectFiles: false,
    canSelectMany: false,
    openLabel: 'Select Folder for CJMP Project'
  });

  if (localPathUri && localPathUri.length > 0) {
    const localPath = localPathUri[0].fsPath;

    if (fs.existsSync(localPath)) {
      const projectTemplate = await vscode.window.showQuickPick(PROJECT_TEMPLATE_OPTIONS, {
        placeHolder: 'Select the template',
      });

      if (!projectTemplate) {
        return;
      }

      let logicModuleType;
      if (projectTemplate === 'logic-module') {
        const logicModuleTypes = ['native', 'keels'];
        logicModuleType = await vscode.window.showQuickPick(logicModuleTypes, {
          placeHolder: 'Select the logic-module type',
        });
        if (!logicModuleType) {
          return;
        }
      }

      const projectName = await vscode.window.showInputBox({
        prompt: 'Please enter the name of your project',
        placeHolder: 'e.g., my_cjmp_project'
      });

      if (projectName && projectTemplate) {
        try {
          await createProject(localPath, projectName, projectTemplate, logicModuleType);
          vscode.window.showInformationMessage(`Running "keels create ${projectName}". Please wait...`);
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : String(error);
          vscode.window.showErrorMessage(`Failed to create project: ${errorMessage}`);
        }
      } else {
        vscode.window.showErrorMessage('Project name and template type are required.');
      }
    } else {
      vscode.window.showErrorMessage('Invalid path provided or path does not exist.');
    }
  } else {
    vscode.window.showErrorMessage('No folder selected.');
  }
}

const createProject =
  async (localPath: string, projectName: string, projectTemplate: string, logicModuleType: string | undefined): Promise<void> =>
{
  return new Promise(async (resolve, reject) => {
    try {
      const keelsCommand = resolveKeelsCommand();
      if (!keelsCommand) {
        resolve();
        return;
      }

      const command = getCreateProjectCommand(
        keelsCommand.executablePath,
        projectName,
        projectTemplate,
        logicModuleType,
      );
      if (!command) {
        return;
      }

      const outputChannel = vscode.window.createOutputChannel('CJMP Output');
      outputChannel.show(true);

      const createProcess = process.platform === 'win32'
        ? spawn('cmd', ['/c', command], { cwd: localPath, env: keelsCommand.environment })
        : spawn(getDefaultShell(), ['-c', command], {
          cwd: localPath.replace(/\\/g, '/'),
          env: keelsCommand.environment,
        });

      createProcess.stdout.on('data', (data) => {
        const message = data.toString();
        outputChannel.append(message);
      });

      createProcess.stderr.on('data', (data) => {
        const errorMessage = data.toString();
        outputChannel.append(errorMessage);
      });

      createProcess.on('close', async (code) => {
        if (code === 0) {
          vscode.window.showInformationMessage(`Project "${projectName}" created successfully!`);

          const projectPath = path.join(localPath, projectName);
          await openProjectAndFile(projectPath);
          resolve();
        } else {
          vscode.window.showErrorMessage(`Failed to create project. Exit code: ${code}`);
          reject(new Error(`Failed to create project. Exit code: ${code}`));
        }
      });

      createProcess.on('error', (error) => {
        vscode.window.showErrorMessage(`Failed to execute command: ${error.message}`);
        outputChannel.appendLine(`Error: ${error.message}`);
        reject(error);
      });
    } catch (error) {
      vscode.window.showErrorMessage(`Error creating project: ${error instanceof Error ? error.message : String(error)}`);
      reject(error);
    }
  });
};

export function getCreateProjectCommand(
  keelsCommand: string,
  projectName: string,
  projectTemplate: string,
  logicModuleType: string | undefined,
): string | undefined {
  switch (projectTemplate) {
    case 'logic-module':
      return `${keelsCommand} create -t ${projectTemplate} --app-type ${logicModuleType} ${projectName}`;
    case 'app':
    case 'library':
    case 'module':
      return `${keelsCommand} create -t ${projectTemplate} ${projectName}`;
    default:
      return undefined;
  }
}

async function openProjectAndFile(projectPath: string) {
  try {
    const uri = vscode.Uri.file(projectPath);
    writeLockFile(projectPath);
    await vscode.commands.executeCommand('vscode.openFolder', uri, false);
  } catch (error) {
    vscode.window.showErrorMessage(
      `Failed to open project: ${error instanceof Error ? error.message : String(error)}`
    );
  }
}

function writeLockFile(projectPath: string) {
  const lockPath = path.join(projectPath, '.vscode', '.CJMP_create.lock');
  console.log(lockPath);
  const dir = path.dirname(lockPath);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
  fs.writeFileSync(lockPath, Date.now().toString());
}