package discovery_test

import (
	"fmt"
	"lunar/aggregation-plugin/common"
	"lunar/aggregation-plugin/discovery"
	sharedDiscovery "lunar/shared-model/discovery"
	"testing"

	"github.com/rs/zerolog/log"
	"github.com/stretchr/testify/assert"
)

func TestExtractAggs(t *testing.T) {
	t.Parallel()
	endpointA := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.org/bar",
	}
	endpointB := sharedDiscovery.Endpoint{
		Method: "POST",
		URL:    "foo.org/bar",
	}
	endpointC := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.org/quu",
	}

	interceptorA := common.Interceptor{
		Type:    "lunar-aiohttp-interceptor",
		Version: "2.0.2",
	}

	interceptorB := common.Interceptor{
		Type:    "lunar-aiohttp-interceptor",
		Version: "2.0.0",
	}

	interceptorC := common.Interceptor{
		Type:    "lunar-requests-interceptor",
		Version: "1.0.1",
	}

	tree, err := common.BuildTree(
		sharedDiscovery.KnownEndpoints{
			Endpoints: []sharedDiscovery.Endpoint{endpointA, endpointB, endpointC},
		},
		2,
	)
	assert.Nil(t, err)

	accessLogs := []discovery.AccessLog{
		{
			ConsumerTag:   "consumerA",
			Timestamp:     1687243938000, // Thu, 20 Jun 2023 06:52:18 GMT
			Duration:      10,
			TotalDuration: 5,
			StatusCode:    200,
			Method:        endpointA.Method,
			URL:           endpointA.URL,
			Interceptor: fmt.Sprintf(
				"%s/%s", interceptorA.Type, interceptorA.Version),
		},
		{
			ConsumerTag:   "consumerA",
			Timestamp:     1687935138000, // Thu, 28 Jun 2023 06:52:18 GMT
			Duration:      5,
			TotalDuration: 3,
			StatusCode:    400,
			Method:        endpointA.Method,
			URL:           endpointA.URL,
			Interceptor: fmt.Sprintf(
				"%s/%s", interceptorA.Type, interceptorA.Version),
		},
		{
			ConsumerTag:   "consumerB",
			Timestamp:     1688021538000, // Thu, 29 Jun 2023 06:52:18 GMT
			Duration:      58,
			TotalDuration: 50,
			StatusCode:    401,
			Method:        endpointB.Method,
			URL:           endpointB.URL,
			Interceptor: fmt.Sprintf(
				"%s/%s", interceptorB.Type, interceptorB.Version),
		},
		{
			Timestamp:     1687675938000, // Thu, 25 Jun 2023 06:52:18 GMT
			Duration:      298,
			TotalDuration: 200,
			StatusCode:    404,
			Method:        endpointC.Method,
			URL:           endpointC.URL,
			Interceptor: fmt.Sprintf(
				"%s/%s", interceptorC.Type, interceptorC.Version),
		},
	}

	wantEndpointAAgg := sharedDiscovery.EndpointAgg{
		MinTime:              1687243938000,
		MaxTime:              1687935138000,
		Count:                2,
		StatusCodes:          map[int]sharedDiscovery.Count{200: 1, 400: 1},
		AverageDuration:      7.5,
		AverageTotalDuration: 4,
	}

	wantEndpointBAgg := sharedDiscovery.EndpointAgg{
		MinTime:              1688021538000,
		MaxTime:              1688021538000,
		Count:                1,
		StatusCodes:          map[int]sharedDiscovery.Count{401: 1},
		AverageDuration:      58,
		AverageTotalDuration: 50,
	}
	wantEndpointCAgg := sharedDiscovery.EndpointAgg{
		MinTime:              1687675938000,
		MaxTime:              1687675938000,
		Count:                1,
		StatusCodes:          map[int]sharedDiscovery.Count{404: 1},
		AverageDuration:      298,
		AverageTotalDuration: 200,
	}

	wantInterceptorAAgg := discovery.InterceptorAgg{
		Timestamp: 1687935138000,
	}

	wantInterceptorBAgg := discovery.InterceptorAgg{
		Timestamp: 1688021538000,
	}

	res := discovery.ExtractAggs(accessLogs, tree)
	resEndpointA, found := res.Endpoints[endpointA]
	assert.True(t, found)
	assert.Equal(t, resEndpointA.Count, wantEndpointAAgg.Count)
	assert.Equal(t, resEndpointA.MinTime, wantEndpointAAgg.MinTime)
	assert.Equal(t, resEndpointA.MaxTime, wantEndpointAAgg.MaxTime)
	assert.Equal(t, resEndpointA.StatusCodes, wantEndpointAAgg.StatusCodes)
	assert.Equal(t, res.Consumers["consumerA"][endpointA], wantEndpointAAgg)
	assert.Equal(t, res.Consumers["consumerB"][endpointB], wantEndpointBAgg)
	assert.Equal(
		t,
		resEndpointA.AverageDuration,
		wantEndpointAAgg.AverageDuration,
	)
	assert.Equal(
		t,
		resEndpointA.AverageTotalDuration,
		wantEndpointAAgg.AverageTotalDuration,
	)

	resEndpointB, found := res.Endpoints[endpointB]
	assert.True(t, found)
	assert.Equal(t, resEndpointB, wantEndpointBAgg)

	resEndpointC, found := res.Endpoints[endpointC]
	assert.True(t, found)
	assert.Equal(t, resEndpointC, wantEndpointCAgg)

	resInterceptorA, found := res.Interceptors[interceptorA]
	assert.True(t, found)
	assert.Equal(t, resInterceptorA.Timestamp,
		wantInterceptorAAgg.Timestamp)

	resInterceptorB, found := res.Interceptors[interceptorB]
	assert.True(t, found)
	assert.Equal(t, resInterceptorB.Timestamp,
		wantInterceptorBAgg.Timestamp)
}

func TestExtractAggsWithParametericPathParts(t *testing.T) {
	t.Parallel()
	endpointA := sharedDiscovery.Endpoint{
		Method: "POST",
		URL:    "foo.org/user/{id}",
	}
	endpointB := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.org/bar",
	}

	tree, err := common.BuildTree(
		sharedDiscovery.KnownEndpoints{
			Endpoints: []sharedDiscovery.Endpoint{endpointA, endpointB},
		},
		2,
	)
	assert.Nil(t, err)

	accessLogs := []discovery.AccessLog{
		{
			Timestamp:     1687243938000,
			Duration:      10,
			TotalDuration: 5,
			StatusCode:    200,
			Method:        endpointA.Method,
			URL:           "foo.org/user/20",
		},
		{
			Timestamp:     1687935138000,
			Duration:      5,
			TotalDuration: 3,
			StatusCode:    400,
			Method:        endpointA.Method,
			URL:           "foo.org/user/30",
		},
		{
			Timestamp:     1687935138000,
			Duration:      58,
			TotalDuration: 50,
			StatusCode:    401,
			Method:        endpointB.Method,
			URL:           endpointB.URL,
		},
	}

	wantEndpointAAgg := sharedDiscovery.EndpointAgg{
		MinTime:              1687243938000,
		MaxTime:              1687935138000,
		Count:                2,
		StatusCodes:          map[int]sharedDiscovery.Count{200: 1, 400: 1},
		AverageDuration:      7.5,
		AverageTotalDuration: 4,
	}

	wantEndpointBAgg := sharedDiscovery.EndpointAgg{
		MinTime:              1687935138000,
		MaxTime:              1687935138000,
		Count:                1,
		StatusCodes:          map[int]sharedDiscovery.Count{401: 1},
		AverageDuration:      58,
		AverageTotalDuration: 50,
	}

	res := discovery.ExtractAggs(accessLogs, tree)
	log.Debug().Msgf("res: %+v", res)
	resA, found := res.Endpoints[endpointA]
	assert.True(t, found)
	assert.Equal(t, resA.Count, wantEndpointAAgg.Count)
	assert.Equal(t, resA.MinTime, wantEndpointAAgg.MinTime)
	assert.Equal(t, resA.MaxTime, wantEndpointAAgg.MaxTime)
	assert.Equal(t, resA.StatusCodes, wantEndpointAAgg.StatusCodes)
	assert.Equal(t, resA.AverageDuration, wantEndpointAAgg.AverageDuration)
	assert.Equal(t, resA.AverageTotalDuration, wantEndpointAAgg.AverageTotalDuration)

	resB, found := res.Endpoints[endpointB]
	assert.True(t, found)
	assert.Equal(t, resB, wantEndpointBAgg)
}

func TestExtractAggsWithPathParamsAndOverlappingConstantPart(t *testing.T) {
	t.Parallel()
	endpointA := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.org/user/{id}/profile/{profile_id}",
	}
	endpointB := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.org/user/admin/profile/{admin_profile_id}",
	}

	tree, err := common.BuildTree(
		sharedDiscovery.KnownEndpoints{
			Endpoints: []sharedDiscovery.Endpoint{endpointA, endpointB},
		},
		2,
	)
	assert.Nil(t, err)

	accessLogs := []discovery.AccessLog{
		{
			Timestamp:     1687243938000,
			Duration:      10,
			TotalDuration: 5,
			StatusCode:    200,
			Method:        endpointA.Method,
			URL:           "foo.org/user/20/profile/30",
		},
		{
			Timestamp:     1687935138000,
			Duration:      5,
			TotalDuration: 3,
			StatusCode:    400,
			Method:        endpointA.Method,
			URL:           "foo.org/user/30/profile/40",
		},
		{
			Timestamp:     1687935138000,
			Duration:      58,
			TotalDuration: 50,
			StatusCode:    401,
			Method:        endpointB.Method,
			URL:           "foo.org/user/admin/profile/50",
		},
	}

	wantEndpointAAgg := sharedDiscovery.EndpointAgg{
		MinTime:              1687243938000,
		MaxTime:              1687935138000,
		Count:                2,
		StatusCodes:          map[int]sharedDiscovery.Count{200: 1, 400: 1},
		AverageDuration:      7.5,
		AverageTotalDuration: 4,
	}

	wantEndpointBAgg := sharedDiscovery.EndpointAgg{
		MinTime:              1687935138000,
		MaxTime:              1687935138000,
		Count:                1,
		StatusCodes:          map[int]sharedDiscovery.Count{401: 1},
		AverageDuration:      58,
		AverageTotalDuration: 50,
	}

	res := discovery.ExtractAggs(accessLogs, tree)
	log.Debug().Msgf("res: %+v", res)
	resA, found := res.Endpoints[endpointA]
	assert.True(t, found)
	assert.Equal(t, resA.Count, wantEndpointAAgg.Count)
	assert.Equal(t, resA.MinTime, wantEndpointAAgg.MinTime)
	assert.Equal(t, resA.MaxTime, wantEndpointAAgg.MaxTime)
	assert.Equal(t, resA.StatusCodes, wantEndpointAAgg.StatusCodes)
	assert.Equal(t, resA.AverageDuration, wantEndpointAAgg.AverageDuration)
	assert.Equal(t, resA.AverageTotalDuration, wantEndpointAAgg.AverageTotalDuration)

	resB, found := res.Endpoints[endpointB]
	assert.True(t, found)
	assert.Equal(t, resB, wantEndpointBAgg)
}
