/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.utils

import org.assertj.core.api.Assertions.assertThat
import org.intellij.lang.annotations.Language
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class JsonUtilsTest {

  @get:Rule val tempFolder = TemporaryFolder()

  @Test
  fun fromPackageJson_withInvalidJson_returnsNull() {
    val invalidJson = createJsonFile("""¯\_(ツ)_/¯""")

    assertThat(JsonUtils.fromPackageJson(invalidJson)).isNull()
  }

  @Test
  fun fromPackageJson_withEmptyJson_returnsEmptyObject() {
    val invalidJson = createJsonFile("""{}""")

    val parsed = JsonUtils.fromPackageJson(invalidJson)

    assertThat(parsed).isNotNull()
    assertThat(parsed?.codegenConfig).isNull()
  }

  @Test
  fun fromPackageJson_withOldJsonConfig_returnsAnEmptyLibrary() {
    val oldJsonConfig =
        createJsonFile(
            """
      {
        "name": "yet another npm package",
        "codegenConfig": {
          "libraries": [
            {
              "name": "an awesome library",
              "jsSrcsDir": "../js/",
              "android": {}
            }
          ]
        }
      }
      """
                .trimIndent())

    val parsed = JsonUtils.fromPackageJson(oldJsonConfig)!!

    assertThat(parsed.codegenConfig?.name).isNull()
    assertThat(parsed.codegenConfig?.jsSrcsDir).isNull()
    assertThat(parsed.codegenConfig?.android).isNull()
  }

  @Test
  fun fromPackageJson_withValidJson_parsesCorrectly() {
    val validJson =
        createJsonFile(
            """
      {
        "name": "yet another npm package",
        "codegenConfig": {
          "name": "an awesome library",
          "jsSrcsDir": "../js/",
          "android": {
            "javaPackageName": "com.awesome.library"
          },
          "ios": {
            "other ios only keys": "which are ignored during parsing"
          }
        }
      }
      """
                .trimIndent())

    val parsed = JsonUtils.fromPackageJson(validJson)!!

    assertThat("an awesome library").isEqualTo(parsed.codegenConfig!!.name)
    assertThat("../js/").isEqualTo(parsed.codegenConfig!!.jsSrcsDir)
    assertThat("com.awesome.library").isEqualTo(parsed.codegenConfig!!.android!!.javaPackageName)
  }

  @Test
  fun fromReactNativePackageJson_withInvalidJson_returnsNull() {
    val invalidJson = createJsonFile("""¯\_(ツ)_/¯""")

    assertThat(JsonUtils.fromPackageJson(invalidJson)).isNull()
  }

  @Test
  fun fromReactNativePackageJson_withEmptyJson_returnsEmptyObject() {
    val invalidJson = createJsonFile("""{}""")

    val parsed = JsonUtils.fromPackageJson(invalidJson)

    assertThat(parsed).isNotNull()
    assertThat(parsed?.version).isNull()
  }

  @Test
  fun fromReactNativePackageJson_withValidJson_parsesJsonCorrectly() {
    val validJson =
        createJsonFile(
            """
      {
        "version": "1000.0.0"
      }
      """
                .trimIndent())
    val parsed = JsonUtils.fromPackageJson(validJson)!!

    assertThat("1000.0.0").isEqualTo(parsed.version)
  }

  @Test
  fun fromAutolinkingConfigJson_withInvalidJson_returnsNull() {
    val invalidJson = createJsonFile("""¯\_(ツ)_/¯""")

    assertThat(JsonUtils.fromAutolinkingConfigJson(invalidJson)).isNull()
  }

  @Test
  fun fromAutolinkingConfigJson_withSimpleJson_returnsIt() {
    val validJson =
        createJsonFile(
            """
      {
        "reactNativeVersion": "1000.0.0"
      }
      """
                .trimIndent())
    val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!!

    assertThat("1000.0.0").isEqualTo(parsed.reactNativeVersion)
  }

  @Test
  fun fromAutolinkingConfigJson_withProjectSpecified_canParseIt() {
    val validJson =
        createJsonFile(
            """
      {
        "reactNativeVersion": "1000.0.0",
        "project": {
          "ios": {
            "sourceDir": "./packages/rn-tester",
            "xcodeProject": {
              "name": "RNTesterPods.xcworkspace",
              "isWorkspace": true
            },
            "automaticPodsInstallation": false
          },
          "android": {
            "sourceDir": "./packages/rn-tester",
            "appName": "RN-Tester",
            "packageName": "com.facebook.react.uiapp",
            "applicationId": "com.facebook.react.uiapp",
            "mainActivity": ".RNTesterActivity",
            "watchModeCommandParams": [
              "--mode HermesDebug"
            ],
            "dependencyConfiguration": "implementation"
          }
        }
      }
      """
                .trimIndent())
    val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!!

    assertThat("./packages/rn-tester").isEqualTo(parsed.project!!.android!!.sourceDir)
    assertThat("RN-Tester").isEqualTo(parsed.project!!.android!!.appName)
    assertThat("com.facebook.react.uiapp").isEqualTo(parsed.project!!.android!!.packageName)
    assertThat("com.facebook.react.uiapp").isEqualTo(parsed.project!!.android!!.applicationId)
    assertThat(".RNTesterActivity").isEqualTo(parsed.project!!.android!!.mainActivity)
    assertThat("--mode HermesDebug")
        .isEqualTo(parsed.project!!.android!!.watchModeCommandParams!![0])
    assertThat("implementation").isEqualTo(parsed.project!!.android!!.dependencyConfiguration)
  }

  @Test
  fun fromAutolinkingConfigJson_withInfoLogs_sanitizeAndParseIt() {
    @Suppress("JsonStandardCompliance")
    val validJson =
        createJsonFile(
            """
      
      > AwesomeProject@0.0.1 npx
      > rnc-cli config
      
       {
        "reactNativeVersion": "1000.0.0",
        "project": {
          "ios": {
            "sourceDir": "./packages/rn-tester",
            "xcodeProject": {
              "name": "RNTesterPods.xcworkspace",
              "isWorkspace": true
            },
            "automaticPodsInstallation": false
          },
          "android": {
            "sourceDir": "./packages/rn-tester",
            "appName": "RN-Tester",
            "packageName": "com.facebook.react.uiapp",
            "applicationId": "com.facebook.react.uiapp",
            "mainActivity": ".RNTesterActivity",
            "watchModeCommandParams": [
              "--mode HermesDebug"
            ],
            "dependencyConfiguration": "implementation"
          }
        }
      } 
      """
                .trimIndent())
    val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!!

    assertThat("./packages/rn-tester").isEqualTo(parsed.project!!.android!!.sourceDir)
    assertThat("RN-Tester").isEqualTo(parsed.project!!.android!!.appName)
    assertThat("com.facebook.react.uiapp").isEqualTo(parsed.project!!.android!!.packageName)
    assertThat("com.facebook.react.uiapp").isEqualTo(parsed.project!!.android!!.applicationId)
    assertThat(".RNTesterActivity").isEqualTo(parsed.project!!.android!!.mainActivity)
    assertThat("--mode HermesDebug")
        .isEqualTo(parsed.project!!.android!!.watchModeCommandParams!![0])
    assertThat("implementation").isEqualTo(parsed.project!!.android!!.dependencyConfiguration)
  }

  @Test
  fun fromAutolinkingConfigJson_withDependenciesSpecified_canParseIt() {
    val validJson =
        createJsonFile(
            """
      {
        "reactNativeVersion": "1000.0.0",
        "dependencies": {
          "@react-native/oss-library-example": {
            "root": "./node_modules/@react-native/oss-library-example",
            "name": "@react-native/oss-library-example",
            "platforms": {
              "ios": {
                "podspecPath": "./node_modules/@react-native/oss-library-example/OSSLibraryExample.podspec",
                "version": "0.0.1",
                "configurations": [],
                "scriptPhases": []
              },
              "android": {
                "sourceDir": "./node_modules/@react-native/oss-library-example/android",
                "packageImportPath": "import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;",
                "packageInstance": "new OSSLibraryExamplePackage()",
                "buildTypes": ["staging", "debug", "release"],
                "libraryName": "OSSLibraryExampleSpec",
                "componentDescriptors": [
                  "SampleNativeComponentComponentDescriptor"
                ],
                "cmakeListsPath": "./node_modules/@react-native/oss-library-example/android/build/generated/source/codegen/jni/CMakeLists.txt",
                "cxxModuleCMakeListsModuleName": null,
                "cxxModuleCMakeListsPath": null,
                "cxxModuleHeaderName": null,
                "dependencyConfiguration": "implementation",
                "isPureCxxDependency": false
              }
            }
          }
        }
      }
      """
                .trimIndent())
    val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!!

    assertThat("./node_modules/@react-native/oss-library-example")
        .isEqualTo(parsed.dependencies!!["@react-native/oss-library-example"]!!.root)
    assertThat("@react-native/oss-library-example")
        .isEqualTo(parsed.dependencies!!["@react-native/oss-library-example"]!!.name)
    assertThat("react-native_oss-library-example")
        .isEqualTo(parsed.dependencies!!["@react-native/oss-library-example"]!!.nameCleansed)
    assertThat("./node_modules/@react-native/oss-library-example/android")
        .isEqualTo(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .sourceDir)
    assertThat("import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;")
        .isEqualTo(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .packageImportPath)
    assertThat("new OSSLibraryExamplePackage()")
        .isEqualTo(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .packageInstance)
    assertThat(listOf("staging", "debug", "release"))
        .isEqualTo(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .buildTypes)
    assertThat("OSSLibraryExampleSpec")
        .isEqualTo(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .libraryName)
    assertThat(listOf("SampleNativeComponentComponentDescriptor"))
        .isEqualTo(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .componentDescriptors)
    assertThat(
            "./node_modules/@react-native/oss-library-example/android/build/generated/source/codegen/jni/CMakeLists.txt")
        .isEqualTo(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .cmakeListsPath)
    assertThat(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .cxxModuleHeaderName)
        .isNull()
    assertThat(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .cxxModuleCMakeListsPath)
        .isNull()
    assertThat(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .cxxModuleCMakeListsModuleName)
        .isNull()
    assertThat("implementation")
        .isEqualTo(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .dependencyConfiguration)
    assertThat(
            parsed.dependencies!!["@react-native/oss-library-example"]!!
                .platforms!!
                .android!!
                .isPureCxxDependency!!)
        .isFalse()
  }

  private fun createJsonFile(@Language("JSON") input: String) =
      tempFolder.newFile().apply { writeText(input) }
}
