/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { AbsolutePath } from '../src/core';
import { RealFS } from '../src/io';
import { buildDirTree, createFileStructure } from './fsUtils';
import { ReactNativeFixture } from './ReactNativeFixture';
import tmp from 'tmp';
import pathUtils from 'node:path';

let tmpDir: AbsolutePath;

beforeEach(async () => {
  const dir = tmp.dirSync();
  tmpDir = new AbsolutePath(dir.name);
});

afterEach(() => {
  if (expect.getState().assertionCalls != expect.getState().numPassingAsserts) {
    console.log(buildDirTree(tmpDir.getValue()));
  }
});

it('list init-harmony in the help description', async () => {
  const result = new ReactNativeFixture('.').help();

  expect(result.includes('init-harmony')).toBeTruthy();
});

it('should add harmony boilerplate to existing project', async () => {
  const fs = new RealFS();
  createFileStructure(tmpDir.toString(), {
    '.git': {},
    'package.json': `{
      "name": "my-project-name"
    }`,
    node_modules: {
      '@react-native-oh': {
        'react-native-harmony': {},
        'react-native-harmony-cli': {
          harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
        },
      },
    },
    'metro.config.js': '...',
  });

  new ReactNativeFixture(tmpDir).initHarmony({
    projectRootPath: '.',
    bundleName: 'com.org.project',
  });

  const allFilesInProject = fs
    .readDirentsSync(tmpDir, { recursive: true })
    .flatMap((dirent) =>
      dirent.isDirectory() ? [] : [dirent.path.relativeTo(tmpDir).toString()]
    );
  const filesCountInHarmonyDir = fs
    .readDirentsSync(tmpDir.copyWithNewSegment('harmony'), { recursive: true })
    .flatMap((dirent) => !dirent.isDirectory()).length;
  expect(filesCountInHarmonyDir).toEqual(58);
  expect(allFilesInProject).toEqual(
    expect.arrayContaining(
      [
        'metro.config.js',
        'package.json',
        'harmony/.gitignore',
        'harmony/build-profile.json5',
        'harmony/build-profile.template.json5',
        'harmony/codelinter.json',
        'harmony/hvigorfile.ts',
        'harmony/oh-package.json5',
        'harmony/AppScope/app.json5',
        'harmony/entry/.gitignore',
        'harmony/entry/build-profile.json5',
        'harmony/entry/hvigorfile.ts',
        'harmony/entry/oh-package.json5',
        'harmony/hvigor/hvigor-config.json5',
        'harmony/entry/src/main/module.json5',
        'harmony/entry/src/ohosTest/module.json5',
        'harmony/AppScope/resources/base/element/string.json',
        'harmony/AppScope/resources/base/media/app_icon.png',
        'harmony/entry/src/main/cpp/.gitignore',
        'harmony/entry/src/main/cpp/CMakeLists.txt',
        'harmony/entry/src/main/cpp/PackageProvider.cpp',
        'harmony/entry/src/main/ets/.gitignore',
        'harmony/entry/src/main/ets/PackageProvider.ets',
        'harmony/entry/src/main/resources/.gitignore',
        'harmony/entry/src/main/ets/codegen/.gitignore',
        'harmony/entry/src/main/ets/entryability/EntryAbility.ets',
        'harmony/entry/src/main/ets/pages/.gitkeep',
        'harmony/entry/src/main/ets/pages/Index.ets',
        'harmony/entry/src/main/ets/workers/RNOHWorker.ets',
        'harmony/entry/src/ohosTest/ets/test/List.test.ets',
        'harmony/entry/src/main/resources/base/element/color.json',
        'harmony/entry/src/main/resources/base/element/string.json',
        'harmony/entry/src/main/resources/base/media/startIcon.png',
        'harmony/entry/src/main/resources/base/profile/main_pages.json',
      ].map((e) => pathUtils.normalize(e))
    )
  );
  expect(
    await fs.readText(tmpDir.copyWithNewSegment('metro.config.js'))
  ).toContain('createHarmonyMetroConfig');
  expect(
    await fs.readText(
      tmpDir.copyWithNewSegment('harmony', 'hvigor', 'hvigor-config.json5')
    )
  ).toContain('rnoh-hvigor-plugin-0.0.0.tgz');
});

it("should fail if react-native-harmony isn't installed", () => {
  createFileStructure(tmpDir.toString(), {});

  const result = new ReactNativeFixture(tmpDir).initHarmony({
    projectRootPath: '.',
    bundleName: 'com.org.project',
  }).stderr;

  expect(result).toContain('react-native-harmony does not exist');
});

it('should fail if harmony dir exists', () => {
  createFileStructure(tmpDir.toString(), {
    harmony: {},
    node_modules: {
      '@react-native-oh': {
        'react-native-harmony': {},
        'react-native-harmony-cli': {
          harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
        },
      },
    },
  });

  const result = new ReactNativeFixture(tmpDir).initHarmony({
    projectRootPath: '.',
    bundleName: 'com.org.project',
  }).stderr;

  expect(result).toContain('The "harmony" directory is already initialized');
});

it('should let the user know metro.config.js has been modified', () => {
  createFileStructure(tmpDir.toString(), {
    '.git': {},
    'metro.config.js': '...',
    'package.json': '{ "name": "foobar" }',
    node_modules: {
      '@react-native-oh': {
        'react-native-harmony': {},
        'react-native-harmony-cli': {
          harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
        },
      },
    },
  });

  const result = new ReactNativeFixture(tmpDir).initHarmony({
    projectRootPath: '.',
    bundleName: 'com.org.project',
  }).stderr;

  expect(result).toContain('Overwritten "./metro.config.js"');
});

it('should let the user know a new directory is created', () => {
  createFileStructure(tmpDir.toString(), {
    'package.json': '{ "name": "foobar" }',
    node_modules: {
      '@react-native-oh': {
        'react-native-harmony': {},
        'react-native-harmony-cli': {
          harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
        },
      },
    },
  });

  const result = new ReactNativeFixture(tmpDir).initHarmony({
    projectRootPath: '.',
    bundleName: 'com.org.project',
  }).stdout;

  expect(result).toContain('Created "./harmony"');
});

it("should print metro.config.js to the console instead of overwriting this file if user doesn't use git", () => {
  createFileStructure(tmpDir.toString(), {
    'metro.config.js': '...',
    'package.json': '{ "name": "foobar" }',
    node_modules: {
      '@react-native-oh': {
        'react-native-harmony': {},
        'react-native-harmony-cli': {
          harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
        },
      },
    },
  });

  const result = new ReactNativeFixture(tmpDir).initHarmony({
    projectRootPath: '.',
    bundleName: 'com.org.project',
  }).stdout;

  expect(result).toContain('createHarmonyMetroConfig');
});

describe('app bundle name', () => {
  it('should use provided bundle name', () => {
    const fs = new RealFS();
    createFileStructure(tmpDir.toString(), {
      'metro.config.js': '...',
      'package.json': '{ "name": "foobar" }',
      node_modules: {
        '@react-native-oh': {
          'react-native-harmony': {},
          'react-native-harmony-cli': {
            harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
          },
        },
      },
    });

    new ReactNativeFixture(tmpDir).initHarmony({
      projectRootPath: '.',
      bundleName: 'com.org.project',
      appName: 'native-project-name',
    }).stdout;
    const appJson5 = fs.readTextSync(
      tmpDir.copyWithNewSegment('harmony', 'AppScope', 'app.json5')
    );
    expect(appJson5).toContain(`"bundleName": "com.org.project"`);
  });

  it('should fail if bundle name is invalid', () => {
    const fs = new RealFS();
    createFileStructure(tmpDir.toString(), {
      'metro.config.js': '...',
      'package.json': '{ "name": "foobar" }',
      node_modules: {
        '@react-native-oh': {
          'react-native-harmony': {},
          'react-native-harmony-cli': {
            harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
          },
        },
      },
    });

    const result = new ReactNativeFixture(tmpDir).initHarmony({
      projectRootPath: '.',
      bundleName: '@-invalid-name',
    }).stderr;

    expect(result).toContain('Bundle name validation failed');
  });
});

describe('custom RNOH package name', () => {
  it('should import from provided RNOH NPM package name', () => {
    const fs = new RealFS();
    createFileStructure(tmpDir.toString(), {
      '.git': {},
      'metro.config.js': '...',
      'package.json': '{ "name": "foobar" }',
      node_modules: {
        '@rnoh': {
          'blazingly-fast-react-native-harmony': {},
          'react-native-harmony-cli': {
            harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
          },
        },
      },
    });

    new ReactNativeFixture(tmpDir).initHarmony({
      projectRootPath: '.',
      bundleName: 'com.org.project',
      rnohNpmPackageName: '@rnoh/blazingly-fast-react-native-harmony',
      rnohCliNpmPackageName: '@rnoh/react-native-harmony-cli',
    }).stderr;

    [
      tmpDir.copyWithNewSegment('metro.config.js'),
      tmpDir.copyWithNewSegment('harmony', 'oh-package.json5'),
    ].forEach((path) => {
      expect(fs.readTextSync(path)).toContain(
        '@rnoh/blazingly-fast-react-native-harmony'
      );
    });
  });

  it('should import hvigor plugin from CLI package', () => {
    const fs = new RealFS();
    createFileStructure(tmpDir.toString(), {
      '.git': {},
      'metro.config.js': '...',
      'package.json': '{ "name": "foobar" }',
      node_modules: {
        '@rnoh': {
          'blazingly-fast-react-native-harmony': {},
          'blazingly-fast-react-native-harmony-cli': {
            harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
          },
        },
      },
    });

    new ReactNativeFixture(tmpDir).initHarmony({
      projectRootPath: '.',
      bundleName: 'com.org.project',
      rnohNpmPackageName: '@rnoh/blazingly-fast-react-native-harmony',
      rnohCliNpmPackageName: '@rnoh/blazingly-fast-react-native-harmony-cli',
    }).stderr;

    expect(
      fs.readTextSync(
        tmpDir.copyWithNewSegment('harmony', 'hvigor', 'hvigor-config.json5')
      )
    ).toContain('@rnoh/blazingly-fast-react-native-harmony-cli');
  });
});

describe('follow up instructions', () => {
  it('should print instructions how to run project on a physical device', () => {
    const fs = new RealFS();
    createFileStructure(tmpDir.toString(), {
      '.git': {},
      'metro.config.js': '...',
      'package.json': '{ "name": "foobar" }',
      node_modules: {
        '@rnoh': {
          'blazingly-fast-react-native-harmony': {},

          'react-native-harmony-cli': {
            harmony: { 'rnoh-hvigor-plugin-0.0.0.tgz': '...' },
          },
        },
      },
    });

    const stdout = new ReactNativeFixture(tmpDir).initHarmony({
      projectRootPath: '.',
      bundleName: 'com.org.project',
      rnohNpmPackageName: '@rnoh/blazingly-fast-react-native-harmony',
      rnohCliNpmPackageName: '@rnoh/react-native-harmony-cli',
    }).stdout;

    expect(stdout).toContain('Run on a physical device');
  });
});