package discovery_test

import (
	"lunar/aggregation-plugin/common"
	"lunar/aggregation-plugin/discovery"
	sharedDiscovery "lunar/shared-model/discovery"
	"lunar/toolkit-core/utils"
	"testing"

	"github.com/stretchr/testify/assert"
)

func endpointAggA() sharedDiscovery.EndpointAgg {
	return sharedDiscovery.EndpointAgg{
		MinTime:              1687762338000, // Thu, 26 Jun 2023 06:52:18 GMT
		MaxTime:              1687848738000, // Thu, 27 Jun 2023 06:52:18 GMT
		Count:                2,
		StatusCodes:          map[int]sharedDiscovery.Count{200: 1, 201: 1},
		AverageDuration:      9.5,
		AverageTotalDuration: 4.5,
	}
}

func endpointAggB() sharedDiscovery.EndpointAgg {
	return sharedDiscovery.EndpointAgg{
		MinTime:              1687935138000, // Thu, 28 Jun 2023 06:52:18 GMT
		MaxTime:              1688021538000, // Thu, 29 Jun 2023 06:52:18 GMT
		Count:                3,
		StatusCodes:          map[int]sharedDiscovery.Count{200: 2, 404: 1},
		AverageDuration:      12.0,
		AverageTotalDuration: 6.0,
	}
}

func endpointAggC() sharedDiscovery.EndpointAgg {
	return sharedDiscovery.EndpointAgg{
		MinTime:              1687762338000, // Thu, 26 Jun 2023 06:52:18 GMT,
		MaxTime:              1688021538000, // Thu, 29 Jun 2023 06:52:18 GMT
		Count:                2,
		StatusCodes:          map[int]sharedDiscovery.Count{218: 2},
		AverageDuration:      11.0,
		AverageTotalDuration: 5.5,
	}
}

func endpointAggEmpty() sharedDiscovery.EndpointAgg {
	return sharedDiscovery.EndpointAgg{
		MinTime:              0,
		MaxTime:              0,
		Count:                0,
		StatusCodes:          make(map[int]sharedDiscovery.Count),
		AverageDuration:      0.0,
		AverageTotalDuration: 0.0,
	}
}

func interceptorAggA() discovery.InterceptorAgg {
	return discovery.InterceptorAgg{
		Timestamp: endpointAggA().MaxTime,
	}
}

func interceptorAggB() discovery.InterceptorAgg {
	return discovery.InterceptorAgg{
		Timestamp: endpointAggB().MaxTime,
	}
}

func interceptorAggC() discovery.InterceptorAgg {
	return discovery.InterceptorAgg{
		Timestamp: endpointAggB().MaxTime,
	}
}

func interceptorAggEmpty() discovery.InterceptorAgg {
	return discovery.InterceptorAgg{
		Timestamp: 0,
	}
}

func TestCombineAggregation(t *testing.T) {
	endpointA := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.com/bar",
	}
	endpointB := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.com/bar2",
	}
	interceptorA := common.Interceptor{
		Type:    "lunar-aiohttp-interceptor",
		Version: "2.0.2",
	}
	endpointAggA := endpointAggA()
	endpointAggB := endpointAggB()

	interceptorAggA := interceptorAggA()
	interceptorAggB := interceptorAggB()

	consumerA := sharedDiscovery.EndpointMapping{
		endpointA: endpointAggA,
	}
	consumerB := sharedDiscovery.EndpointMapping{
		endpointB: endpointAggB,
	}

	agg1 := discovery.Agg{
		Endpoints: utils.Map[sharedDiscovery.Endpoint, sharedDiscovery.EndpointAgg]{
			endpointA: endpointAggA,
		},
		Interceptors: utils.Map[common.Interceptor, discovery.InterceptorAgg]{
			interceptorA: interceptorAggA,
		},
		Consumers: utils.Map[string, sharedDiscovery.EndpointMapping]{
			"consumerGroupA": consumerA,
		},
	}

	agg2 := discovery.Agg{
		Endpoints: utils.Map[sharedDiscovery.Endpoint, sharedDiscovery.EndpointAgg]{
			endpointA: endpointAggB,
		},
		Interceptors: utils.Map[common.Interceptor, discovery.InterceptorAgg]{
			interceptorA: interceptorAggB,
		},
		Consumers: utils.Map[string, sharedDiscovery.EndpointMapping]{
			"consumerGroupB": consumerB,
		},
	}

	combined := discovery.CombineAggregation(agg1, agg2)

	combinedEndpointA, found := combined.Endpoints[endpointA]
	assert.True(t, found)
	assert.Equal(t, combinedEndpointA.AverageTotalDuration, float32(5.4))
	assert.Equal(t, combinedEndpointA.MinTime, endpointAggA.MinTime)
	assert.Equal(t, combinedEndpointA.MaxTime, endpointAggB.MaxTime)
	assert.Equal(t, combinedEndpointA.Count, endpointAggA.Count+endpointAggB.Count)
	assert.Equal(
		t,
		combinedEndpointA.StatusCodes[200],
		endpointAggA.StatusCodes[200]+endpointAggB.StatusCodes[200],
	)
	assert.Equal(t, combinedEndpointA.StatusCodes[404], endpointAggB.StatusCodes[404])

	combinedInterceptorA, found := combined.Interceptors[interceptorA]
	assert.True(t, found)
	assert.Equal(t, combinedInterceptorA.Timestamp, interceptorAggB.Timestamp)

	combinedConsumerA, found := combined.Consumers["consumerGroupA"]
	assert.True(t, found)
	assert.Equal(t, combinedConsumerA, consumerA)

	combinedConsumerB, found := combined.Consumers["consumerGroupB"]
	assert.True(t, found)
	assert.Equal(t, combinedConsumerB, consumerB)
}

func TestCombineAggMapsInitial(t *testing.T) {
	t.Parallel()
	// at first the state is just an empty map
	empty := discovery.Agg{
		Interceptors: make(map[common.Interceptor]discovery.InterceptorAgg),
		Endpoints:    make(map[sharedDiscovery.Endpoint]sharedDiscovery.EndpointAgg),
	}

	endpointA := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.com/bar",
	}

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

	a := discovery.Agg{
		Endpoints: map[sharedDiscovery.Endpoint]sharedDiscovery.EndpointAgg{
			endpointA: endpointAggA(),
		},
		Interceptors: map[common.Interceptor]discovery.InterceptorAgg{
			interceptorA: interceptorAggA(),
		},
	}

	combined := discovery.CombineAggregation(empty, a)

	combinedEndpointA, found := combined.Endpoints[endpointA]
	assert.True(t, found)
	assert.Equal(t, combinedEndpointA, endpointAggA())

	combinedInterceptorA, found := combined.Interceptors[interceptorA]
	assert.True(t, found)
	assert.Equal(t, combinedInterceptorA, interceptorAggA())
}

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

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

	interceptorB := common.Interceptor{
		Type:    "lunar-requests-interceptor",
		Version: "2.0.3",
	}

	a := discovery.Agg{
		Endpoints: map[sharedDiscovery.Endpoint]sharedDiscovery.EndpointAgg{
			endpointA: endpointAggA(),
		},
		Interceptors: map[common.Interceptor]discovery.InterceptorAgg{
			interceptorA: interceptorAggA(),
		},
	}
	b := discovery.Agg{
		Endpoints: map[sharedDiscovery.Endpoint]sharedDiscovery.EndpointAgg{
			endpointA: endpointAggB(),
			endpointB: endpointAggC(),
		},
		Interceptors: map[common.Interceptor]discovery.InterceptorAgg{
			interceptorA: interceptorAggB(),
			interceptorB: interceptorAggC(),
		},
	}

	combined := discovery.CombineAggregation(a, b)

	combinedEndpointA, found := combined.Endpoints[endpointA]
	assert.True(t, found)
	assert.Equal(t, combinedEndpointA.Count, sharedDiscovery.Count(5))
	assert.Equal(t, combinedEndpointA.MinTime, int64(endpointAggA().MinTime))
	assert.Equal(t, combinedEndpointA.MaxTime, int64(endpointAggB().MaxTime))
	endpointAStatusCodes := map[int]sharedDiscovery.Count{200: 3, 201: 1, 404: 1}
	assert.Equal(t,
		combinedEndpointA.StatusCodes,
		endpointAStatusCodes,
	)
	assert.Equal(t, combinedEndpointA.AverageDuration, float32(11.0))
	assert.Equal(t, combinedEndpointA.AverageTotalDuration, float32(5.4))

	combinedEndpointB, found := combined.Endpoints[endpointB]
	assert.True(t, found)
	assert.Equal(t, combinedEndpointB, endpointAggC())

	combinedInterceptorA, found := combined.Interceptors[interceptorA]
	assert.True(t, found)
	assert.Equal(t, combinedInterceptorA.Timestamp,
		interceptorAggB().Timestamp)
}

func TestCombineAggMapsWhenOneMapValuesAreEmpty(
	t *testing.T,
) {
	t.Parallel()
	endpointA := sharedDiscovery.Endpoint{
		Method: "GET",
		URL:    "foo.com/bar",
	}
	interceptorA := common.Interceptor{
		Type:    "lunar-aiohttp-interceptor",
		Version: "2.0.2",
	}
	endpointAggA := endpointAggA()
	endpointAggEmpty := endpointAggEmpty()

	interceptorAggA := interceptorAggA()
	interceptorAggEmpty := interceptorAggEmpty()
	a := discovery.Agg{
		Endpoints: map[sharedDiscovery.Endpoint]sharedDiscovery.EndpointAgg{
			endpointA: endpointAggA,
		},
		Interceptors: map[common.Interceptor]discovery.InterceptorAgg{
			interceptorA: interceptorAggA,
		},
	}
	b := discovery.Agg{
		Endpoints: map[sharedDiscovery.Endpoint]sharedDiscovery.EndpointAgg{
			endpointA: endpointAggEmpty,
		},
		Interceptors: map[common.Interceptor]discovery.InterceptorAgg{
			interceptorA: interceptorAggEmpty,
		},
	}
	combined := discovery.CombineAggregation(a, b)

	combinedEndpointA, found := combined.Endpoints[endpointA]
	assert.True(t, found)
	// The only field which is equal to the empty map is the min time since
	// the zero-value for `int` is... 0.
	assert.Equal(t, combinedEndpointA.MinTime, endpointAggEmpty.MinTime)
	assert.Equal(t, combinedEndpointA.MaxTime, endpointAggA.MaxTime)
	assert.Equal(t, combinedEndpointA.StatusCodes, endpointAggA.StatusCodes)
	assert.Equal(t, combinedEndpointA.AverageDuration, endpointAggA.AverageDuration)
	assert.Equal(t, combinedEndpointA.AverageTotalDuration, endpointAggA.AverageTotalDuration)

	combinedInterceptorA, found := combined.Interceptors[interceptorA]
	assert.True(t, found)
	assert.Equal(t,
		combinedInterceptorA.Timestamp,
		interceptorAggA.Timestamp,
	)
}

func TestCombineAggMapsWhenBothEmptyItReturnsEmptyMap(t *testing.T) {
	t.Parallel()
	// at first the state is just an empty map
	empty := discovery.Agg{
		Endpoints:    make(map[sharedDiscovery.Endpoint]sharedDiscovery.EndpointAgg),
		Interceptors: make(map[common.Interceptor]discovery.InterceptorAgg),
		Consumers:    make(map[string]sharedDiscovery.EndpointMapping),
	}

	combined := discovery.CombineAggregation(empty, empty)

	assert.Equal(t, combined, empty)
}
