// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight;

import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.fixtures.*;
import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl;
import com.intellij.testFramework.junit5.RunInEdt;
import com.intellij.testFramework.rules.TempDirectoryExtension;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.org.objectweb.asm.ClassWriter;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RunInEdt(writeIntent = true)
public class ClsResolveTest {
  @RegisterExtension
  public TempDirectoryExtension tempDir = new TempDirectoryExtension();

  private JavaCodeInsightTestFixture myFixture;

  @BeforeEach
  public void setUp() throws Exception {
    Path jar = generateMutantJar();
    DefaultLightProjectDescriptor descriptor = new DefaultLightProjectDescriptor() {
      @Override
      public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
        super.configureModule(module, model, contentEntry);
        PsiTestUtil.addLibrary(model, "mutant", jar.getParent().toString(), jar.getFileName().toString());
      }
    };
    TestFixtureBuilder<IdeaProjectTestFixture> builder = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder(descriptor,
                                                                                                                              "ClsREsolveTest");
    myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(builder.getFixture(), new LightTempDirTestFixtureImpl(true));
    myFixture.setUp();
  }

  //<editor-fold desc="Test data generation">
  private Path generateMutantJar() {
    try {
      Path file = tempDir.newFile("mutant.jar").toPath();
      try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(file))) {
        writeEntry(jar, "mutant1/aB.class", classBytes("mutant1.aB", "m1"));
        writeEntry(jar, "mutant1/ab.class", classBytes("mutant1.ab", "m2"));
        writeEntry(jar, "MUTANT2/C.class", classBytes("MUTANT2.C", "m3"));
        writeEntry(jar, "mutant2/D.class", classBytes("mutant2.D", "m4"));
      }
      return file;
    }
    catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  private static void writeEntry(ZipOutputStream zip, String name, byte[] data) throws IOException {
    ZipEntry entry = new ZipEntry(name);
    zip.putNextEntry(entry);
    zip.write(data);
    zip.closeEntry();
  }

  private static byte[] classBytes(String name, String method) {
    ClassWriter cw = new ClassWriter(0);
    cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.', '/'), null, "java/lang/Object", null);

    // Default constructor
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
    mv.visitCode();
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
    mv.visitInsn(Opcodes.RETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();

    // Static void method
    mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, method, "()V", null, null);
    mv.visitCode();
    mv.visitInsn(Opcodes.RETURN);
    mv.visitMaxs(0, 0);
    mv.visitEnd();

    cw.visitEnd();
    return cw.toByteArray();
  }
  //</editor-fold>

  @AfterEach
  public void tearDown() throws Exception {
    myFixture.tearDown();
    myFixture = null;
  }

  @Test
  public void resolveIntoMutantJar() {
    myFixture.configureByText(
      "test.java",
      """
        class test {{
          mutant1.aB.m1();
          mutant1.ab.m2();
          MUTANT2.C.m3();
          mutant2.D.m4();
        }}""");
    myFixture.checkHighlighting();
  }
}