/*
 * Copyright The OpenTelemetry Authors
 *
 * Licensed 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
 *
 *      https://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.
 */
import { SpanKind, SpanStatusCode } from '@opentelemetry/api';
import { TraceState, hexToBase64 } from '@opentelemetry/core';
import { Resource } from '@opentelemetry/resources';
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
import * as assert from 'assert';
import { createExportTraceServiceRequest, ESpanKind, EStatusCode } from '../src';

function createExpectedSpanJson(useHex: boolean){
  const traceId = useHex? '00000000000000000000000000000001' : hexToBase64('00000000000000000000000000000001');
  const spanId = useHex? '0000000000000002' : hexToBase64('0000000000000002');
  const parentSpanId = useHex? '0000000000000001' : hexToBase64('0000000000000001');
  const linkSpanId = useHex? '0000000000000003' : hexToBase64('0000000000000003');
  const linkTraceId = useHex? '00000000000000000000000000000002' : hexToBase64('00000000000000000000000000000002');

  return  {
    resourceSpans: [
      {
        resource: {
          attributes: [
            {
              key: 'resource-attribute',
              value: { stringValue: 'resource attribute value' },
            },
          ],
          droppedAttributesCount: 0,
        },
        schemaUrl: undefined,
        scopeSpans: [
          {
            scope: { name: 'myLib', version: '0.1.0' },
            spans: [
              {
                traceId: traceId,
                spanId: spanId,
                parentSpanId: parentSpanId,
                name: 'span-name',
                kind: ESpanKind.SPAN_KIND_CLIENT,
                links: [
                  {
                    droppedAttributesCount: 0,
                    spanId: linkSpanId,
                    traceId: linkTraceId,
                    attributes: [
                      {
                        key: 'link-attribute',
                        value: {
                          stringValue: 'string value'
                        }
                      }
                    ]
                  }
                ],
                // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
                startTimeUnixNano: 1640715557342725388,
                // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
                endTimeUnixNano: 1640715558642725388,
                events: [
                  {
                    droppedAttributesCount: 0,
                    attributes: [
                      {
                        key: 'event-attribute',
                        value: {
                          stringValue: 'some string value'
                        }
                      }
                    ],
                    name: 'some event',
                    timeUnixNano: 1640715558542725400
                  }
                ],
                attributes: [
                  {
                    key: 'string-attribute',
                    value: { stringValue: 'some attribute value' },
                  },
                ],
                droppedAttributesCount: 0,
                droppedEventsCount: 0,
                droppedLinksCount: 0,
                status: {
                  code: EStatusCode.STATUS_CODE_OK,
                  message: undefined,
                },
              },
            ],
            schemaUrl: 'http://url.to.schema',
          },
        ],
      },
    ],
  };
}

describe('Trace', () => {
  describe('createExportTraceServiceRequest', () => {
    let resource: Resource;
    let span: ReadableSpan;

    beforeEach(() => {
      resource = new Resource({
        'resource-attribute': 'resource attribute value',
      });
      span = {
        spanContext: () => ({
          spanId: '0000000000000002',
          traceFlags: 1,
          traceId: '00000000000000000000000000000001',
          isRemote: false,
          traceState: new TraceState(''),
        }),
        parentSpanId: '0000000000000001',
        attributes: { 'string-attribute': 'some attribute value' },
        duration: [1, 300000000],
        endTime: [1640715558, 642725388],
        ended: true,
        events: [
          {
            name: 'some event',
            time: [1640715558, 542725388],
            attributes: {
              'event-attribute': 'some string value'
            }
          }
        ],
        instrumentationLibrary: {
          name: 'myLib',
          version: '0.1.0',
          schemaUrl: 'http://url.to.schema',
        },
        kind: SpanKind.CLIENT,
        links: [
          {
            context: {
              spanId: '0000000000000003',
              traceId: '00000000000000000000000000000002',
              traceFlags: 1,
              isRemote: false,
              traceState: new TraceState('')
            },
            attributes: {
              'link-attribute': 'string value'
            }
          }
        ],
        name: 'span-name',
        resource,
        startTime: [1640715557, 342725388],
        status: {
          code: SpanStatusCode.OK,
        },
      };
    });

    it('returns null on an empty list', () => {
      assert.deepStrictEqual(createExportTraceServiceRequest([], true), { resourceSpans: [] });
    });

    it('serializes a span with useHex = true', () => {
      const exportRequest = createExportTraceServiceRequest([span], true);
      assert.ok(exportRequest);
      assert.deepStrictEqual(exportRequest, createExpectedSpanJson(true));
    });

    it('serializes a span with useHex = false', () => {
      const exportRequest = createExportTraceServiceRequest([span], false);
      assert.ok(exportRequest);
      assert.deepStrictEqual(exportRequest, createExpectedSpanJson(false));
    });

    it('serializes a span without a parent with useHex = true', () => {
      (span as any).parentSpanId = undefined;
      const exportRequest = createExportTraceServiceRequest([span], true);
      assert.ok(exportRequest);
      assert.strictEqual(exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].parentSpanId, undefined);
    });

    it('serializes a span without a parent with useHex = false', () => {
      (span as any).parentSpanId = undefined;
      const exportRequest = createExportTraceServiceRequest([span], false);
      assert.ok(exportRequest);
      assert.strictEqual(exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].parentSpanId, undefined);
    });

    describe('status code', () => {
      it('error', () => {
        span.status.code = SpanStatusCode.ERROR;
        span.status.message = 'error message';
        const exportRequest = createExportTraceServiceRequest([span], true);
        assert.ok(exportRequest);
        const spanStatus = exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].status;
        assert.strictEqual(spanStatus?.code, EStatusCode.STATUS_CODE_ERROR);
        assert.strictEqual(spanStatus?.message, 'error message');
      });

      it('unset', () => {
        span.status.code = SpanStatusCode.UNSET;
        const exportRequest = createExportTraceServiceRequest([span], true);
        assert.ok(exportRequest);
        assert.strictEqual(exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].status.code, EStatusCode.STATUS_CODE_UNSET);
      });
    });

    describe('span kind', () => {
      it('consumer', () => {
        (span as any).kind = SpanKind.CONSUMER;
        const exportRequest = createExportTraceServiceRequest([span], true);
        assert.ok(exportRequest);
        assert.strictEqual(exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_CONSUMER);
      });
      it('internal', () => {
        (span as any).kind = SpanKind.INTERNAL;
        const exportRequest = createExportTraceServiceRequest([span], true);
        assert.ok(exportRequest);
        assert.strictEqual(exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_INTERNAL);
      });
      it('producer', () => {
        (span as any).kind = SpanKind.PRODUCER;
        const exportRequest = createExportTraceServiceRequest([span], true);
        assert.ok(exportRequest);
        assert.strictEqual(exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_PRODUCER);
      });
      it('server', () => {
        (span as any).kind = SpanKind.SERVER;
        const exportRequest = createExportTraceServiceRequest([span], true);
        assert.ok(exportRequest);
        assert.strictEqual(exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_SERVER);
      });
      it('unspecified', () => {
        (span as any).kind = undefined;
        const exportRequest = createExportTraceServiceRequest([span], true);
        assert.ok(exportRequest);
        assert.strictEqual(exportRequest.resourceSpans?.[0].scopeSpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_UNSPECIFIED);
      });
    });
  });
});
