/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import org.springframework.batch.core.JobParameter
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext

import static io.opentelemetry.api.trace.SpanKind.INTERNAL
import static java.util.Collections.emptyMap

abstract class CustomSpanEventTest extends AgentInstrumentationSpecification {
  static final boolean VERSION_GREATER_THAN_4_0 = Boolean.getBoolean("testLatestDeps")

  abstract runJob(String jobName, Map<String, JobParameter> params = emptyMap())

  def "should be able to call Span.current() and add custom info to spans"() {
    when:
    runJob("customSpanEventsItemsJob")

    then:
    assertTraces(1) {
      trace(0, 7) {
        span(0) {
          name "BatchJob customSpanEventsItemsJob"
          kind INTERNAL
          events(2)
          event(0) {
            eventName "job.before"
          }
          event(1) {
            eventName "job.after"
          }
        }
        span(1) {
          name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep"
          kind INTERNAL
          childOf span(0)

          // CompositeChunkListener has broken ordering that causes listeners that do not override order() to appear first at all times
          // because of that a custom ChunkListener will always see a Step span when using spring-batch versions [3, 4)
          // that bug was fixed in 4.0
          if (VERSION_GREATER_THAN_4_0) {
            events(2)
            event(0) {
              eventName "step.before"
            }
            event(1) {
              eventName "step.after"
            }
          } else {
            events(4)
            event(0) {
              eventName "step.before"
            }
            event(1) {
              eventName "chunk.before"
            }
            event(2) {
              eventName "chunk.after"
            }
            event(3) {
              eventName "step.after"
            }
          }
        }
        span(2) {
          name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.Chunk"
          kind INTERNAL
          childOf span(1)

          // CompositeChunkListener has broken ordering that causes listeners that do not override order() to appear first at all times
          // because of that a custom ChunkListener will always see a Step span when using spring-batch versions [3, 4)
          // that bug was fixed in 4.0
          if (VERSION_GREATER_THAN_4_0) {
            events(2)
            event(0) {
              eventName "chunk.before"
            }
            event(1) {
              eventName "chunk.after"
            }
          } else {
            events(0)
          }
        }

        itemSpans(it)
      }
    }
  }

  // Spring Batch Java & XML configs have slightly different ordering from JSR config
  protected void itemSpans(TraceAssert trace) {
    trace.with {
      span(3) {
        name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead"
        kind INTERNAL
        childOf span(2)
        events(2)
        event(0) {
          eventName "item.read.before"
        }
        event(1) {
          eventName "item.read.after"
        }
      }
      // second read that returns null and signifies end of stream
      span(4) {
        name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead"
        kind INTERNAL
        childOf span(2)
        // spring batch does not call ItemReadListener after() methods when read() returns end-of-stream
        events(1)
        event(0) {
          eventName "item.read.before"
        }
      }
      span(5) {
        name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemProcess"
        kind INTERNAL
        childOf span(2)
        events(2)
        event(0) {
          eventName "item.process.before"
        }
        event(1) {
          eventName "item.process.after"
        }
      }
      span(6) {
        name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemWrite"
        kind INTERNAL
        childOf span(2)
        events(2)
        event(0) {
          eventName "item.write.before"
        }
        event(1) {
          eventName "item.write.after"
        }
      }
    }
  }
}

class JavaConfigCustomSpanEventTest extends CustomSpanEventTest implements ApplicationConfigTrait {
  @Override
  ConfigurableApplicationContext createApplicationContext() {
    new AnnotationConfigApplicationContext(SpringBatchApplication)
  }
}

class XmlConfigCustomSpanEventTest extends CustomSpanEventTest implements ApplicationConfigTrait {
  @Override
  ConfigurableApplicationContext createApplicationContext() {
    new ClassPathXmlApplicationContext("spring-batch.xml")
  }
}

class JsrConfigCustomSpanEventTest extends CustomSpanEventTest implements JavaxBatchConfigTrait {

  // JSR config has different item span ordering
  protected void itemSpans(TraceAssert trace) {
    trace.with {
      span(3) {
        name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead"
        kind INTERNAL
        childOf span(2)
        events(2)
        event(0) {
          eventName "item.read.before"
        }
        event(1) {
          eventName "item.read.after"
        }
      }
      span(4) {
        name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemProcess"
        kind INTERNAL
        childOf span(2)
        events(2)
        event(0) {
          eventName "item.process.before"
        }
        event(1) {
          eventName "item.process.after"
        }
      }
      // second read that returns null and signifies end of stream
      span(5) {
        name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead"
        kind INTERNAL
        childOf span(2)
        // spring batch does not call ItemReadListener after() methods when read() returns end-of-stream
        events(1)
        event(0) {
          eventName "item.read.before"
        }
      }
      span(6) {
        name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemWrite"
        kind INTERNAL
        childOf span(2)
        events(2)
        event(0) {
          eventName "item.write.before"
        }
        event(1) {
          eventName "item.write.after"
        }
      }
    }
  }
}
