package apiserver

import (
	"net/http"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/crowdsecurity/crowdsec/pkg/database"
	"github.com/crowdsecurity/crowdsec/pkg/database/ent/metric"
)

func TestLPMetrics(t *testing.T) {
	ctx := t.Context()

	tests := []struct {
		name                 string
		body                 string
		expectedStatusCode   int
		expectedResponse     string
		expectedMetricsCount int
		expectedOSName       string
		expectedOSVersion    string
		expectedFeatureFlags string
		authType             string
	}{
		{
			name: "empty metrics for LP",
			body: `{
			}`,
			expectedStatusCode: http.StatusBadRequest,
			expectedResponse:   "Missing log processor data",
			authType:           PASSWORD,
		},
		{
			name: "basic metrics with empty dynamic metrics for LP",
			body: `
{
	"log_processors": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"],
		"datasources": {"file": 42},
		"hub_items": {}
	}
	]
}`,
			expectedStatusCode:   http.StatusCreated,
			expectedMetricsCount: 1,
			expectedResponse:     "",
			expectedOSName:       "foo",
			expectedOSVersion:    "42",
			expectedFeatureFlags: "a,b,c",
			authType:             PASSWORD,
		},
		{
			name: "basic metrics with dynamic metrics for LP",
			body: `
{
	"log_processors": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [{"meta":{"utc_now_timestamp":42, "window_size_seconds": 42}, "items": [{"name": "foo", "value": 42, "unit": "bla"}] }, {"meta":{"utc_now_timestamp":43, "window_size_seconds": 42}, "items": [{"name": "foo", "value": 42, "unit": "bla"}] }],
		"feature_flags": ["a", "b", "c"],
		"datasources": {"file": 42},
		"hub_items": {}
	}
	]
}`,
			expectedStatusCode:   http.StatusCreated,
			expectedMetricsCount: 1,
			expectedResponse:     "",
			expectedOSName:       "foo",
			expectedOSVersion:    "42",
			expectedFeatureFlags: "a,b,c",
			authType:             PASSWORD,
		},
		{
			name: "wrong auth type for LP",
			body: `
{
	"log_processors": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"],
		"datasources": {"file": 42},
		"hub_items": {}
	}
	]
}`,
			expectedStatusCode: http.StatusBadRequest,
			expectedResponse:   "Missing remediation component data",
			authType:           APIKEY,
		},
		{
			name: "missing OS field for LP",
			body: `
{
	"log_processors": [
	{
		"version": "1.42",
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"],
		"datasources": {"file": 42},
		"hub_items": {}
	}
	]
}`,
			expectedStatusCode:   http.StatusCreated,
			expectedResponse:     "",
			expectedMetricsCount: 1,
			expectedFeatureFlags: "a,b,c",
			authType:             PASSWORD,
		},
		{
			name: "missing datasources for LP",
			body: `
{
	"log_processors": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"],
		"hub_items": {}
	}
	]
}`,
			expectedStatusCode: http.StatusUnprocessableEntity,
			expectedResponse:   "log_processors.0.datasources in body is required",
			authType:           PASSWORD,
		},
		{
			name: "missing feature flags for LP",
			body: `
{
	"log_processors": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [],
		"datasources": {"file": 42},
		"hub_items": {}
	}
	]
}`,
			expectedStatusCode:   http.StatusCreated,
			expectedMetricsCount: 1,
			expectedOSName:       "foo",
			expectedOSVersion:    "42",
			authType:             PASSWORD,
		},
		{
			name: "missing OS name",
			body: `
{
	"log_processors": [
	{
		"version": "1.42",
		"os": {"version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"],
		"datasources": {"file": 42},
		"hub_items": {}
	}
	]
}`,
			expectedStatusCode: http.StatusUnprocessableEntity,
			expectedResponse:   "log_processors.0.os.name in body is required",
			authType:           PASSWORD,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lapi := SetupLAPITest(t, ctx)

			dbClient, err := database.NewClient(ctx, lapi.DBConfig)
			if err != nil {
				t.Fatalf("unable to create database client: %s", err)
			}

			w := lapi.RecordResponse(t, ctx, http.MethodPost, "/v1/usage-metrics", strings.NewReader(tt.body), tt.authType)

			assert.Equal(t, tt.expectedStatusCode, w.Code)
			assert.Contains(t, w.Body.String(), tt.expectedResponse)

			machine, _ := dbClient.QueryMachineByID(ctx, "test")
			metrics, _ := dbClient.GetLPUsageMetricsByMachineID(ctx, "test", true)

			assert.Len(t, metrics, tt.expectedMetricsCount)
			assert.Equal(t, tt.expectedOSName, machine.Osname)
			assert.Equal(t, tt.expectedOSVersion, machine.Osversion)
			assert.Equal(t, tt.expectedFeatureFlags, machine.Featureflags)

			if len(metrics) > 0 {
				assert.Equal(t, "test", metrics[0].GeneratedBy)
				assert.Equal(t, metric.GeneratedType("LP"), metrics[0].GeneratedType)
			}
		})
	}
}

func TestRCMetrics(t *testing.T) {
	ctx := t.Context()

	tests := []struct {
		name                 string
		body                 string
		expectedStatusCode   int
		expectedResponse     string
		expectedMetricsCount int
		expectedOSName       string
		expectedOSVersion    string
		expectedFeatureFlags string
		authType             string
	}{
		{
			name: "empty metrics for RC",
			body: `{
			}`,
			expectedStatusCode: http.StatusBadRequest,
			expectedResponse:   "Missing remediation component data",
			authType:           APIKEY,
		},
		{
			name: "basic metrics with empty dynamic metrics for RC",
			body: `
{
	"remediation_components": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"]
	}
	]
}`,
			expectedStatusCode:   http.StatusCreated,
			expectedMetricsCount: 1,
			expectedResponse:     "",
			expectedOSName:       "foo",
			expectedOSVersion:    "42",
			expectedFeatureFlags: "a,b,c",
			authType:             APIKEY,
		},
		{
			name: "basic metrics with dynamic metrics for RC",
			body: `
{
	"remediation_components": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [{"meta":{"utc_now_timestamp":42, "window_size_seconds": 42}, "items": [{"name": "foo", "value": 42, "unit": "bla"}] }, {"meta":{"utc_now_timestamp":43, "window_size_seconds": 42}, "items": [{"name": "foo", "value": 42, "unit": "bla"}] }],
		"feature_flags": ["a", "b", "c"]
	}
	]
}`,
			expectedStatusCode:   http.StatusCreated,
			expectedMetricsCount: 1,
			expectedResponse:     "",
			expectedOSName:       "foo",
			expectedOSVersion:    "42",
			expectedFeatureFlags: "a,b,c",
			authType:             APIKEY,
		},
		{
			name: "wrong auth type for RC",
			body: `
{
	"remediation_components": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"]
	}
	]
}`,
			expectedStatusCode: http.StatusBadRequest,
			expectedResponse:   "Missing log processor data",
			authType:           PASSWORD,
		},
		{
			name: "missing OS field for RC",
			body: `
{
	"remediation_components": [
	{
		"version": "1.42",
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"]
	}
	]
}`,
			expectedStatusCode:   http.StatusCreated,
			expectedResponse:     "",
			expectedMetricsCount: 1,
			expectedFeatureFlags: "a,b,c",
			authType:             APIKEY,
		},
		{
			name: "missing feature flags for RC",
			body: `
{
	"remediation_components": [
	{
		"version": "1.42",
		"os": {"name":"foo", "version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": []
	}
	]
}`,
			expectedStatusCode:   http.StatusCreated,
			expectedMetricsCount: 1,
			expectedOSName:       "foo",
			expectedOSVersion:    "42",
			authType:             APIKEY,
		},
		{
			name: "missing OS name",
			body: `
{
	"remediation_components": [
	{
		"version": "1.42",
		"os": {"version": "42"},
		"utc_startup_timestamp": 42,
		"metrics": [],
		"feature_flags": ["a", "b", "c"]
	}
	]
}`,
			expectedStatusCode: http.StatusUnprocessableEntity,
			expectedResponse:   "remediation_components.0.os.name in body is required",
			authType:           APIKEY,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lapi := SetupLAPITest(t, ctx)

			dbClient, err := database.NewClient(ctx, lapi.DBConfig)
			if err != nil {
				t.Fatalf("unable to create database client: %s", err)
			}

			w := lapi.RecordResponse(t, ctx, http.MethodPost, "/v1/usage-metrics", strings.NewReader(tt.body), tt.authType)

			assert.Equal(t, tt.expectedStatusCode, w.Code)
			assert.Contains(t, w.Body.String(), tt.expectedResponse)

			bouncer, _ := dbClient.SelectBouncerByName(ctx, "test")
			metrics, _ := dbClient.GetBouncerUsageMetricsByName(ctx, "test")

			assert.Len(t, metrics, tt.expectedMetricsCount)
			assert.Equal(t, tt.expectedOSName, bouncer.Osname)
			assert.Equal(t, tt.expectedOSVersion, bouncer.Osversion)
			assert.Equal(t, tt.expectedFeatureFlags, bouncer.Featureflags)

			if len(metrics) > 0 {
				assert.Equal(t, "test", metrics[0].GeneratedBy)
				assert.Equal(t, metric.GeneratedType("RC"), metrics[0].GeneratedType)
			}
		})
	}
}
