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

package query_test

import (
	"testing"

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

func TestUnmarshalQueryColumn(t *testing.T) {
	cases := []struct {
		Input    string
		Expected schema.ColumnKey
		Error    bool
	}{
		{"DstAddr", schema.ColumnDstAddr, false},
		{"TimeReceived", 0, true},
		{"Nothing", 0, true},
	}
	for _, tc := range cases {
		var qc query.Column
		if err := qc.UnmarshalText([]byte(tc.Input)); err != nil {
			t.Fatalf("UnmarshalText(%q) error:\n%+v", tc.Input, err)
		}
		err := qc.Validate(schema.NewMock(t))
		if err != nil && !tc.Error {
			t.Fatalf("Validate(%q) error:\n%+v", tc.Input, err)
		}
		if err == nil && tc.Error {
			t.Fatalf("Validate(%q) did not error", tc.Input)
		}
		if err != nil {
			continue
		}
		if diff := helpers.Diff(qc.Key(), tc.Expected); diff != "" {
			t.Fatalf("UnmarshalText(%q) (-got, +want):\n%s", tc.Input, diff)
		}
	}
}

func TestQueryColumnSQLSelect(t *testing.T) {
	sch := schema.NewMock(t)
	cases := []struct {
		Input    schema.ColumnKey
		Expected string
	}{
		{
			Input:    schema.ColumnSrcAddr,
			Expected: `replaceRegexpOne(IPv6NumToString(SrcAddr), '^::ffff:', '')`,
		}, {
			Input:    schema.ColumnDstAddrNAT,
			Expected: `replaceRegexpOne(IPv6NumToString(DstAddrNAT), '^::ffff:', '')`,
		}, {
			Input:    schema.ColumnExporterAddress,
			Expected: `replaceRegexpOne(IPv6NumToString(ExporterAddress), '^::ffff:', '')`,
		}, {
			Input:    schema.ColumnDstAS,
			Expected: `concat(toString(DstAS), ': ', dictGetOrDefault('asns', 'name', DstAS, '???'))`,
		}, {
			Input:    schema.ColumnDst2ndAS,
			Expected: `concat(toString(Dst2ndAS), ': ', dictGetOrDefault('asns', 'name', Dst2ndAS, '???'))`,
		}, {
			Input:    schema.ColumnProto,
			Expected: `dictGetOrDefault('protocols', 'name', Proto, '???')`,
		}, {
			Input:    schema.ColumnEType,
			Expected: `if(EType = 2048, 'IPv4', if(EType = 34525, 'IPv6', '???'))`,
		}, {
			Input:    schema.ColumnOutIfSpeed,
			Expected: `toString(OutIfSpeed)`,
		}, {
			Input:    schema.ColumnDstVlan,
			Expected: `toString(DstVlan)`,
		}, {
			Input:    schema.ColumnExporterName,
			Expected: `ExporterName`,
		}, {
			Input:    schema.ColumnPacketSizeBucket,
			Expected: `PacketSizeBucket`,
		}, {
			Input:    schema.ColumnDstASPath,
			Expected: `arrayStringConcat(DstASPath, ' ')`,
		}, {
			Input:    schema.ColumnDstCommunities,
			Expected: `arrayStringConcat(arrayConcat(arrayMap(c -> concat(toString(bitShiftRight(c, 16)), ':', toString(bitAnd(c, 0xffff))), DstCommunities), arrayMap(c -> concat(toString(bitAnd(bitShiftRight(c, 64), 0xffffffff)), ':', toString(bitAnd(bitShiftRight(c, 32), 0xffffffff)), ':', toString(bitAnd(c, 0xffffffff))), DstLargeCommunities)), ' ')`,
		}, {
			Input:    schema.ColumnDstMAC,
			Expected: `MACNumToString(DstMAC)`,
		}, {
			Input:    schema.ColumnInIfBoundary,
			Expected: `toString(InIfBoundary)`,
		}, {
			Input:    schema.ColumnMPLSLabels,
			Expected: `arrayStringConcat(MPLSLabels, ' ')`,
		}, {
			Input:    schema.ColumnMPLS3rdLabel,
			Expected: `toString(MPLS3rdLabel)`,
		}, {
			Input: schema.ColumnTCPFlags,
			// Can be tested with "WITH 16 AS TCPFlags SELECT ..."
			Expected: `arrayStringConcat([if(bitTest(TCPFlags, 0) = 1, 'F', ''), if(bitTest(TCPFlags, 1) = 1, 'S', ''), if(bitTest(TCPFlags, 2) = 1, 'R', ''), if(bitTest(TCPFlags, 3) = 1, 'P', ''), if(bitTest(TCPFlags, 4) = 1, '.', ''), if(bitTest(TCPFlags, 5) = 1, 'U', ''), if(bitTest(TCPFlags, 6) = 1, 'E', ''), if(bitTest(TCPFlags, 7) = 1, 'C', ''), if(bitTest(TCPFlags, 8) = 1, 'N', '')], '')`,
		}, {
			Input:    schema.ColumnDstPort,
			Expected: "replaceRegexpOne(multiIf(Proto==6, concat(toString(DstPort), '/', dictGetOrDefault('tcp', 'name', DstPort,'')), Proto==17, concat(toString(DstPort), '/', dictGetOrDefault('udp', 'name', DstPort,'')), toString(DstPort)), '/$', '')",
		}, {
			Input:    schema.ColumnSrcPort,
			Expected: "replaceRegexpOne(multiIf(Proto==6, concat(toString(SrcPort), '/', dictGetOrDefault('tcp', 'name', SrcPort,'')), Proto==17, concat(toString(SrcPort), '/', dictGetOrDefault('udp', 'name', SrcPort,'')), toString(SrcPort)), '/$', '')",
		},
	}
	for _, tc := range cases {
		t.Run(tc.Input.String(), func(t *testing.T) {
			column := query.NewColumn(tc.Input.String())
			if err := column.Validate(schema.NewMock(t).EnableAllColumns()); err != nil {
				t.Fatalf("Validate() error:\n%+v", err)
			}
			got := column.ToSQLSelect(sch)
			if diff := helpers.Diff(got, tc.Expected); diff != "" {
				t.Errorf("toSQLWhere (-got, +want):\n%s", diff)
			}
		})
	}
}

func TestReverseDirection(t *testing.T) {
	columns := query.Columns{
		query.NewColumn("SrcAS"),
		query.NewColumn("DstAS"),
		query.NewColumn("ExporterName"),
		query.NewColumn("InIfProvider"),
	}
	sch := schema.NewMock(t)
	if err := columns.Validate(sch); err != nil {
		t.Fatalf("Validate() error:\n%+v", err)
	}
	columns.Reverse(sch)
	expected := query.Columns{
		query.NewColumn("DstAS"),
		query.NewColumn("SrcAS"),
		query.NewColumn("ExporterName"),
		query.NewColumn("OutIfProvider"),
	}
	if diff := helpers.Diff(columns, expected); diff != "" {
		t.Fatalf("Reverse() (-got, +want):\n%s", diff)
	}
}
