// Copyright 2021 Google LLC
//
// 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.

// Script to record test expectation fixtures and save them to disk.

package main

import (
	"context"
	"fmt"
	"log"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector/integrationtest"
	"github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector/integrationtest/protos"
	"github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector/integrationtest/testcases"
	"github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock"
)

type FakeTesting struct {
	testing.TB
}

func (t *FakeTesting) Logf(format string, args ...interface{}) {
	log.Printf(format, args...)
}
func (t *FakeTesting) Errorf(format string, args ...interface{}) {
	panic(fmt.Errorf(format, args...))
}
func (t *FakeTesting) FailNow() {
	t.Errorf("FailNow()")
}
func (t *FakeTesting) Helper()      {}
func (t *FakeTesting) Name() string { return "record fixtures" }

type fixtureRecorder map[string]string

func (fr fixtureRecorder) checkDuplicate(test testcases.TestCase) error {
	if duplicate, ok := fr[test.ExpectFixturePath]; ok {
		return fmt.Errorf("test %s writes to the same fixture %s as test %s", test.Name, test.ExpectFixturePath, duplicate)
	}
	fr[test.ExpectFixturePath] = test.Name
	return nil
}

func main() {
	ctx := context.Background()
	endTime := time.Now()
	startTime := endTime.Add(-time.Second)
	t := &FakeTesting{}

	// track the output fixtures to catch tests overwriting another test's fixtures
	fr := make(fixtureRecorder)

	fr.recordLogs(ctx, t, endTime)
	fr.recordMetrics(ctx, t, startTime, endTime)
	fr.recordTraces(ctx, t, startTime, endTime)
}

func (fr fixtureRecorder) recordTraces(ctx context.Context, t *FakeTesting, startTime, endTime time.Time) {
	testServer, err := cloudmock.NewTracesTestServer()
	if err != nil {
		panic(err)
	}
	//nolint:errcheck
	go testServer.Serve()
	defer testServer.Shutdown()

	for _, test := range testcases.TracesTestCases {
		if test.Skip {
			continue
		}

		require.NoError(t, fr.checkDuplicate(test))

		func() {
			traces := test.LoadOTLPTracesInput(t, startTime, endTime)
			inMemoryOTelExporter, err := integrationtest.NewInMemoryOTelExporter()
			require.NoError(t, err)
			//nolint:errcheck
			defer inMemoryOTelExporter.Shutdown(ctx)
			testServerExporter := integrationtest.NewTraceTestExporter(ctx, t, testServer, test.CreateTraceConfig(), inMemoryOTelExporter.MeterProvider)

			require.NoError(t, testServerExporter.PushTraces(ctx, traces), "failed to export logs to local test server")
			require.NoError(t, testServerExporter.Shutdown(ctx))

			selfObsMetrics, err := inMemoryOTelExporter.Proto(ctx)
			require.NoError(t, err)
			fixture := &protos.TraceExpectFixture{
				BatchWriteSpansRequest:   testServer.CreateBatchWriteSpansRequests(),
				SelfObservabilityMetrics: selfObsMetrics,
				UserAgent:                testcases.UserAgentRemoveRuntimeInfo(testServer.UserAgent()),
			}
			test.SaveRecordedTraceFixtures(t, fixture)
		}()
	}
}

func (fr fixtureRecorder) recordLogs(ctx context.Context, t *FakeTesting, timestamp time.Time) {
	testServer, err := cloudmock.NewLoggingTestServer()
	if err != nil {
		panic(err)
	}
	go testServer.Serve()
	defer testServer.Shutdown()

	for _, test := range testcases.LogsTestCases {
		if test.Skip {
			continue
		}

		require.NoError(t, fr.checkDuplicate(test))

		func() {
			logs := test.LoadOTLPLogsInput(t, timestamp)
			inMemoryOTelExporter, err := integrationtest.NewInMemoryOTelExporter()
			require.NoError(t, err)
			//nolint:errcheck
			defer inMemoryOTelExporter.Shutdown(ctx)
			testServerExporter := integrationtest.NewLogTestExporter(ctx, t, testServer, test.CreateLogConfig(), test.ConfigureLogsExporter, inMemoryOTelExporter.MeterProvider)

			require.NoError(t, testServerExporter.PushLogs(ctx, logs), "failed to export logs to local test server")
			require.NoError(t, testServerExporter.Shutdown(ctx))
			selfObsMetrics, err := inMemoryOTelExporter.Proto(ctx)
			require.NoError(t, err)
			fixture := &protos.LogExpectFixture{
				WriteLogEntriesRequests:  testServer.CreateWriteLogEntriesRequests(),
				SelfObservabilityMetrics: selfObsMetrics,
				UserAgent:                testcases.UserAgentRemoveRuntimeInfo(testServer.UserAgent()),
			}
			test.SaveRecordedLogFixtures(t, fixture)
		}()
	}
}

func (fr fixtureRecorder) recordMetrics(ctx context.Context, t *FakeTesting, startTime, endTime time.Time) {
	for _, test := range testcases.MetricsTestCases {
		if test.Skip {
			continue
		}

		testServer, err := cloudmock.NewMetricTestServer()
		if err != nil {
			panic(err)
		}
		//nolint:errcheck
		go testServer.Serve()

		require.NoError(t, fr.checkDuplicate(test))

		func() {
			metrics := test.LoadOTLPMetricsInput(t, startTime, endTime)
			inMemoryOTelExporter, err := integrationtest.NewInMemoryOTelExporter()
			testServerExporter := integrationtest.NewMetricTestExporter(ctx, t, testServer, test.CreateCollectorMetricConfig(), inMemoryOTelExporter.MeterProvider)
			require.NoError(t, err)
			//nolint:errcheck
			defer inMemoryOTelExporter.Shutdown(ctx)

			err = testServerExporter.PushMetrics(ctx, metrics)
			if !test.ExpectErr {
				require.NoError(t, err, "failed to export metrics to local test server")
			} else {
				require.Error(t, err, "didn't record expected error")
			}
			require.NoError(t, testServerExporter.Shutdown(ctx))

			selfObsMetrics, err := inMemoryOTelExporter.Proto(ctx)
			require.NoError(t, err)
			fixture := &protos.MetricExpectFixture{
				CreateMetricDescriptorRequests:  testServer.CreateMetricDescriptorRequests(),
				CreateTimeSeriesRequests:        testServer.CreateTimeSeriesRequests(),
				CreateServiceTimeSeriesRequests: testServer.CreateServiceTimeSeriesRequests(),
				SelfObservabilityMetrics:        selfObsMetrics,
				UserAgent:                       testcases.UserAgentRemoveRuntimeInfo(testServer.UserAgent()),
			}
			test.SaveRecordedMetricFixtures(t, fixture)
		}()

		testServer.Shutdown()
	}
}
