/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.tomee.microprofile.tck.opentelemetry;

import io.opentelemetry.semconv.HttpAttributes;
import org.apache.xbean.asm9.ClassReader;
import org.apache.xbean.asm9.ClassVisitor;
import org.apache.xbean.asm9.ClassWriter;
import org.apache.xbean.asm9.MethodVisitor;
import org.apache.xbean.asm9.Opcodes;
import org.apache.ziplock.JarLocation;
import org.jboss.arquillian.container.spi.client.deployment.DeploymentDescription;
import org.jboss.arquillian.container.spi.event.container.BeforeDeploy;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.asset.ByteArrayAsset;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;

import java.io.InputStream;

import static org.apache.xbean.asm9.Opcodes.ASM9;

public class OpenTelemetryTCKDeploymentProcessor {

    public void observeDeployment(@Observes final BeforeDeploy beforeDeploy) {
        DeploymentDescription deployment = beforeDeploy.getDeployment();
        Archive<?> testableArchive = deployment.getTestableArchive();
        if (testableArchive != null) {
            process(testableArchive);
        } else {
            process(deployment.getArchive());
        }
    }

    private void process(Archive<?> archive) {
        if (archive instanceof WebArchive webapp) {
            webapp.addAsLibrary(JarLocation.jarLocation(HttpAttributes.class)) // required for some tck classes
                    .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");

            applyJaxRsClientAsyncTestVisibilityHack(webapp);

        }
    }

    /*
     * This test fails under Java 17+ because the @PostConstruct and @PreDestroy methods are declared 'private',
     * and CXF's InjectionUtils cannot access them reflectively. To work around this, we modify the class on the fly,
     * making these methods public. This is acceptable here, as it's not part of the core setup logic of the TCK test,
     * which passes once the access issue is resolved.
     */
    private void applyJaxRsClientAsyncTestVisibilityHack(WebArchive webapp) {
        final String className = "/WEB-INF/classes/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTestEndpoint.class";

        if (webapp.contains(className)) {
            try (InputStream originalClass = webapp.get(className).getAsset().openStream()) {

                final ClassReader reader = new ClassReader(originalClass);
                final ClassWriter writer = new ClassWriter(reader, 0);

                final ClassVisitor visitor = new ClassVisitor(ASM9, writer) {
                    @Override
                    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                        // Check if it's openClient or closeClient
                        if (name.equals("openClient") || name.equals("closeClient")) {
                            // Remove private flag and add public flag
                            access = (access & ~Opcodes.ACC_PRIVATE) | Opcodes.ACC_PUBLIC;
                        }

                        return super.visitMethod(access, name, descriptor, signature, exceptions);
                    }

                };

                reader.accept(visitor, 0);
                final byte[] modifiedClass = writer.toByteArray();

                webapp.delete(className);
                webapp.add(new ByteArrayAsset(modifiedClass), className);

            } catch (Exception ignored) {

            }
        }

    }
}