// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only

package console

import (
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/mock/gomock"

	"akvorado/common/helpers"
	"akvorado/common/schema"
	"akvorado/console/query"
)

func TestSankeyQuerySQL(t *testing.T) {
	cases := []struct {
		Description string
		Pos         helpers.Pos
		Input       graphSankeyHandlerInput
		Expected    []templateQuery
	}{
		{
			Description: "two dimensions, no filters, l3 bps",
			Pos:         helpers.Mark(),
			Input: graphSankeyHandlerInput{
				graphCommonHandlerInput{
					Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
					End:   time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
					Dimensions: []query.Column{
						query.NewColumn("SrcAS"),
						query.NewColumn("ExporterName"),
					},
					Limit:  5,
					Filter: query.Filter{},
					Units:  "l3bps",
				},
			},
			Expected: []templateQuery{
				{
					Context: inputContext{
						Start:  time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
						End:    time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
						Points: 20,
						Units:  "l3bps",
					},
					Template: `WITH
 source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
 (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range,
 rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5)
SELECT
 {{ .Units }}/range AS xps,
 [if(SrcAS IN (SELECT SrcAS FROM rows), concat(toString(SrcAS), ': ', dictGetOrDefault('asns', 'name', SrcAS, '???')), 'Other'),
  if(ExporterName IN (SELECT ExporterName FROM rows), ExporterName, 'Other')] AS dimensions
FROM source
WHERE {{ .Timefilter }}
GROUP BY dimensions
ORDER BY xps DESC`,
				},
			},
		}, {
			Description: "two dimensions, no filters, l3 bps, limitType by max",
			Pos:         helpers.Mark(),
			Input: graphSankeyHandlerInput{
				graphCommonHandlerInput{
					Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
					End:   time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
					Dimensions: []query.Column{
						query.NewColumn("SrcAS"),
						query.NewColumn("ExporterName"),
					},
					Limit:     5,
					LimitType: "max",
					Filter:    query.Filter{},
					Units:     "l3bps",
				},
			},
			Expected: []templateQuery{
				{
					Context: inputContext{
						Start:  time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
						End:    time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
						Points: 20,
						Units:  "l3bps",
					},
					Template: `WITH
 source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
 (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range,
 rows AS (SELECT SrcAS, ExporterName FROM ( SELECT SrcAS, ExporterName, {{ .Units }} AS sum_at_time FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ) GROUP BY SrcAS, ExporterName ORDER BY MAX(sum_at_time) DESC LIMIT 5)
SELECT
 {{ .Units }}/range AS xps,
 [if(SrcAS IN (SELECT SrcAS FROM rows), concat(toString(SrcAS), ': ', dictGetOrDefault('asns', 'name', SrcAS, '???')), 'Other'),
  if(ExporterName IN (SELECT ExporterName FROM rows), ExporterName, 'Other')] AS dimensions
FROM source
WHERE {{ .Timefilter }}
GROUP BY dimensions
ORDER BY xps DESC`,
				},
			},
		}, {
			Description: "two dimensions, no filters, l2 bps",
			Pos:         helpers.Mark(),
			Input: graphSankeyHandlerInput{
				graphCommonHandlerInput{
					Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
					End:   time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
					Dimensions: []query.Column{
						query.NewColumn("SrcAS"),
						query.NewColumn("ExporterName"),
					},
					Limit:  5,
					Filter: query.Filter{},
					Units:  "l2bps",
				},
			},
			Expected: []templateQuery{
				{
					Context: inputContext{
						Start:  time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
						End:    time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
						Points: 20,
						Units:  "l2bps",
					},
					Template: `WITH
 source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
 (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range,
 rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5)
SELECT
 {{ .Units }}/range AS xps,
 [if(SrcAS IN (SELECT SrcAS FROM rows), concat(toString(SrcAS), ': ', dictGetOrDefault('asns', 'name', SrcAS, '???')), 'Other'),
  if(ExporterName IN (SELECT ExporterName FROM rows), ExporterName, 'Other')] AS dimensions
FROM source
WHERE {{ .Timefilter }}
GROUP BY dimensions
ORDER BY xps DESC`,
				},
			},
		}, {
			Description: "two dimensions, no filters, pps",
			Pos:         helpers.Mark(),
			Input: graphSankeyHandlerInput{
				graphCommonHandlerInput{
					Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
					End:   time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
					Dimensions: []query.Column{
						query.NewColumn("SrcAS"),
						query.NewColumn("ExporterName"),
					},
					Limit:  5,
					Filter: query.Filter{},
					Units:  "pps",
				},
			},
			Expected: []templateQuery{
				{
					Context: inputContext{
						Start:  time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
						End:    time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
						Points: 20,
						Units:  "pps",
					},
					Template: `WITH
 source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
 (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range,
 rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5)
SELECT
 {{ .Units }}/range AS xps,
 [if(SrcAS IN (SELECT SrcAS FROM rows), concat(toString(SrcAS), ': ', dictGetOrDefault('asns', 'name', SrcAS, '???')), 'Other'),
  if(ExporterName IN (SELECT ExporterName FROM rows), ExporterName, 'Other')] AS dimensions
FROM source
WHERE {{ .Timefilter }}
GROUP BY dimensions
ORDER BY xps DESC`,
				},
			},
		}, {
			Description: "two dimensions, with filter",
			Pos:         helpers.Mark(),
			Input: graphSankeyHandlerInput{
				graphCommonHandlerInput{
					Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
					End:   time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
					Dimensions: []query.Column{
						query.NewColumn("SrcAS"),
						query.NewColumn("ExporterName"),
					},
					Limit:  10,
					Filter: query.NewFilter("DstCountry = 'FR'"),
					Units:  "l3bps",
				},
			},
			Expected: []templateQuery{
				{
					Context: inputContext{
						Start:  time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
						End:    time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
						Points: 20,
						Units:  "l3bps",
					},
					Template: `WITH
 source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
 (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }} AND (DstCountry = 'FR')) AS range,
 rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} AND (DstCountry = 'FR') GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 10)
SELECT
 {{ .Units }}/range AS xps,
 [if(SrcAS IN (SELECT SrcAS FROM rows), concat(toString(SrcAS), ': ', dictGetOrDefault('asns', 'name', SrcAS, '???')), 'Other'),
  if(ExporterName IN (SELECT ExporterName FROM rows), ExporterName, 'Other')] AS dimensions
FROM source
WHERE {{ .Timefilter }} AND (DstCountry = 'FR')
GROUP BY dimensions
ORDER BY xps DESC`,
				},
			},
		},
	}
	for _, tc := range cases {
		tc.Input.schema = schema.NewMock(t)
		if err := query.Columns(tc.Input.Dimensions).Validate(tc.Input.schema); err != nil {
			t.Fatalf("%sValidate() error:\n%+v", tc.Pos, err)
		}
		if err := tc.Input.Filter.Validate(tc.Input.schema); err != nil {
			t.Fatalf("%sValidate() error:\n%+v", tc.Pos, err)
		}
		t.Run(tc.Description, func(t *testing.T) {
			got, _ := tc.Input.toSQL()
			if diff := helpers.Diff(got, tc.Expected); diff != "" {
				t.Errorf("%stoSQL (-got, +want):\n%s", tc.Pos, diff)
			}
		})
	}
}

func TestSankeyHandler(t *testing.T) {
	_, h, mockConn, _ := NewMock(t, DefaultConfiguration())

	expectedSQL := []struct {
		Xps        float64  `ch:"xps"`
		Dimensions []string `ch:"dimensions"`
	}{
		// [(random.randrange(100, 10000), x)
		//  for x in set([(random.choice(asn),
		//                 random.choice(providers),
		//                 random.choice(routers)) for x in range(30)])]
		{9677, []string{"AS100", "Other", "router1"}},
		{9472, []string{"AS300", "provider1", "Other"}},
		{7593, []string{"AS300", "provider2", "router1"}},
		{7234, []string{"AS200", "provider1", "Other"}},
		{6006, []string{"AS100", "provider1", "Other"}},
		{5988, []string{"Other", "provider1", "Other"}},
		{4675, []string{"AS200", "provider3", "Other"}},
		{4348, []string{"AS200", "Other", "router2"}},
		{3999, []string{"AS100", "provider3", "Other"}},
		{3978, []string{"AS100", "provider3", "router2"}},
		{3623, []string{"Other", "Other", "router1"}},
		{3080, []string{"AS300", "provider3", "router2"}},
		{2915, []string{"AS300", "Other", "router1"}},
		{2623, []string{"AS100", "provider1", "router1"}},
		{2482, []string{"AS200", "provider2", "router2"}},
		{2234, []string{"AS100", "provider2", "Other"}},
		{1360, []string{"AS200", "Other", "router1"}},
		{975, []string{"AS300", "Other", "Other"}},
		{717, []string{"AS200", "provider3", "router2"}},
		{621, []string{"Other", "Other", "Other"}},
		{159, []string{"Other", "provider1", "router1"}},
	}
	mockConn.EXPECT().
		Select(gomock.Any(), gomock.Any(), gomock.Any()).
		SetArg(1, expectedSQL).
		Return(nil)

	helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{
		{
			URL: "/api/v0/console/graph/sankey",
			JSONInput: gin.H{
				"start":      time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
				"end":        time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
				"dimensions": []string{"SrcAS", "InIfProvider", "ExporterName"},
				"limit":      10,
				"filter":     "DstCountry = 'FR'",
				"units":      "l3bps",
			},
			JSONOutput: gin.H{
				// Raw data
				"rows": [][]string{
					{"AS100", "Other", "router1"},
					{"AS300", "provider1", "Other"},
					{"AS300", "provider2", "router1"},
					{"AS200", "provider1", "Other"},
					{"AS100", "provider1", "Other"},
					{"Other", "provider1", "Other"},
					{"AS200", "provider3", "Other"},
					{"AS200", "Other", "router2"},
					{"AS100", "provider3", "Other"},
					{"AS100", "provider3", "router2"},
					{"Other", "Other", "router1"},
					{"AS300", "provider3", "router2"},
					{"AS300", "Other", "router1"},
					{"AS100", "provider1", "router1"},
					{"AS200", "provider2", "router2"},
					{"AS100", "provider2", "Other"},
					{"AS200", "Other", "router1"},
					{"AS300", "Other", "Other"},
					{"AS200", "provider3", "router2"},
					{"Other", "Other", "Other"},
					{"Other", "provider1", "router1"},
				},
				"xps": []int{
					9677,
					9472,
					7593,
					7234,
					6006,
					5988,
					4675,
					4348,
					3999,
					3978,
					3623,
					3080,
					2915,
					2623,
					2482,
					2234,
					1360,
					975,
					717,
					621,
					159,
				},
				// For graph
				"nodes": []string{
					"SrcAS: AS100",
					"InIfProvider: Other",
					"ExporterName: router1",
					"SrcAS: AS300",
					"InIfProvider: provider1",
					"ExporterName: Other",
					"InIfProvider: provider2",
					"SrcAS: AS200",
					"SrcAS: Other",
					"InIfProvider: provider3",
					"ExporterName: router2",
				},
				"links": []gin.H{
					{
						"source": "InIfProvider: provider1", "target": "ExporterName: Other",
						"xps": 9472 + 7234 + 6006 + 5988,
					},
					{
						"source": "InIfProvider: Other", "target": "ExporterName: router1",
						"xps": 9677 + 3623 + 2915 + 1360,
					},
					{
						"source": "SrcAS: AS100", "target": "InIfProvider: Other",
						"xps": 9677,
					},
					{
						"source": "SrcAS: AS300", "target": "InIfProvider: provider1",
						"xps": 9472,
					},
					{
						"source": "InIfProvider: provider3", "target": "ExporterName: Other",
						"xps": 4675 + 3999,
					},
					{
						"source": "SrcAS: AS100", "target": "InIfProvider: provider1",
						"xps": 6006 + 2623,
					},
					{
						"source": "SrcAS: AS100", "target": "InIfProvider: provider3",
						"xps": 3999 + 3978,
					},
					{
						"source": "InIfProvider: provider3", "target": "ExporterName: router2",
						"xps": 3978 + 3080 + 717,
					},
					{
						"source": "InIfProvider: provider2", "target": "ExporterName: router1",
						"xps": 7593,
					},
					{
						"source": "SrcAS: AS300", "target": "InIfProvider: provider2",
						"xps": 7593,
					},
					{
						"source": "SrcAS: AS200", "target": "InIfProvider: provider1",
						"xps": 7234,
					},
					{
						"source": "SrcAS: Other", "target": "InIfProvider: provider1",
						"xps": 5988 + 159,
					},
					{
						"source": "SrcAS: AS200", "target": "InIfProvider: Other",
						"xps": 4348 + 1360,
					},
					{
						"source": "SrcAS: AS200", "target": "InIfProvider: provider3",
						"xps": 4675 + 717,
					},
					{
						"source": "InIfProvider: Other", "target": "ExporterName: router2",
						"xps": 4348,
					},
					{
						"source": "SrcAS: Other", "target": "InIfProvider: Other",
						"xps": 3623 + 621,
					},
					{
						"source": "SrcAS: AS300", "target": "InIfProvider: Other",
						"xps": 2915 + 975,
					},
					{
						"source": "SrcAS: AS300", "target": "InIfProvider: provider3",
						"xps": 3080,
					},
					{
						"source": "InIfProvider: provider1", "target": "ExporterName: router1",
						"xps": 2623 + 159,
					},
					{
						"source": "InIfProvider: provider2", "target": "ExporterName: router2",
						"xps": 2482,
					},
					{
						"source": "SrcAS: AS200", "target": "InIfProvider: provider2",
						"xps": 2482,
					},
					{
						"source": "InIfProvider: provider2", "target": "ExporterName: Other",
						"xps": 2234,
					},
					{
						"source": "SrcAS: AS100", "target": "InIfProvider: provider2",
						"xps": 2234,
					},
					{
						"source": "InIfProvider: Other", "target": "ExporterName: Other",
						"xps": 975 + 621,
					},
				},
			},
		},
	})
}
