package cloudwatch

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"

	cloudwatchLogsTypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/grafana/grafana-plugin-sdk-go/data"
	"github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery"
)

var executeLogAnomaliesQuery = func(ctx context.Context, ds *DataSource, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
	resp := backend.NewQueryDataResponse()

	for _, q := range req.Queries {
		var anomaliesQuery dataquery.CloudWatchLogsAnomaliesQuery
		err := json.Unmarshal(q.JSON, &anomaliesQuery)
		if err != nil {
			continue
		}

		region := anomaliesQuery.Region
		if region == "" || region == defaultRegion {
			anomaliesQuery.Region = ds.Settings.Region
		}

		logsClient, err := ds.getCWLogsClient(ctx, region)
		if err != nil {
			return nil, err
		}

		listAnomaliesInput := &cloudwatchlogs.ListAnomaliesInput{}
		if anomaliesQuery.SuppressionState != nil {
			listAnomaliesInput.SuppressionState = getSuppressionState(*anomaliesQuery.SuppressionState)
		}

		if anomaliesQuery.AnomalyDetectionARN == nil || *anomaliesQuery.AnomalyDetectionARN != "" {
			listAnomaliesInput.AnomalyDetectorArn = anomaliesQuery.AnomalyDetectionARN
		}

		response, err := logsClient.ListAnomalies(ctx, listAnomaliesInput)

		if err != nil {
			result := backend.NewQueryDataResponse()
			result.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(backend.DownstreamError(fmt.Errorf("%v: %w", "failed to call cloudwatch:ListAnomalies", err)))
			return result, nil
		}

		dataframe, err := logsAnomaliesResultsToDataframes(response)
		if err != nil {
			return nil, err
		}

		respD := resp.Responses[q.RefID]
		respD.Frames = data.Frames{dataframe}
		resp.Responses[q.RefID] = respD
	}

	return resp, nil

}

func logsAnomaliesResultsToDataframes(response *cloudwatchlogs.ListAnomaliesOutput) (*data.Frame, error) {
	frame := data.NewFrame("Log anomalies")

	if len(response.Anomalies) == 0 {
		return frame, nil
	}

	n := len(response.Anomalies)
	anomalyArns := make([]string, n)
	descriptions := make([]string, n)
	suppressedStatus := make([]bool, n)

	priorities := make([]string, n)
	patterns := make([]string, n)
	statuses := make([]string, n)
	logGroupArnLists := make([]string, n)
	firstSeens := make([]time.Time, n)
	lastSeens := make([]time.Time, n)
	logTrends := make([]*json.RawMessage, n)

	for i, anomaly := range response.Anomalies {
		anomalyArns[i] = *anomaly.AnomalyDetectorArn
		descriptions[i] = *anomaly.Description
		suppressedStatus[i] = *anomaly.Suppressed
		priorities[i] = *anomaly.Priority
		if anomaly.PatternString != nil {
			patterns[i] = *anomaly.PatternString
		}
		statuses[i] = string(anomaly.State)
		logGroupArnLists[i] = strings.Join(anomaly.LogGroupArnList, ",")

		firstSeens[i] = time.UnixMilli(anomaly.FirstSeen)

		lastSeens[i] = time.UnixMilli(anomaly.LastSeen)

		// data.Frame returned from the backend cannot contain fields of type data.Frames
		// so histogram is kept as json.RawMessageto be built as sparkline table cell on the FE
		histogramField := anomaly.Histogram
		histogramJSON, err := json.Marshal(histogramField)
		if err != nil {
			logTrends[i] = nil
		} else {
			rawMsg := json.RawMessage(histogramJSON)
			logTrends[i] = &rawMsg
		}
	}

	newFields := make([]*data.Field, 0, len(response.Anomalies))

	newFields = append(newFields, data.NewField("state", nil, statuses).SetConfig(&data.FieldConfig{DisplayName: "State"}))
	newFields = append(newFields, data.NewField("description", nil, descriptions).SetConfig(&data.FieldConfig{DisplayName: "Anomaly"}))
	newFields = append(newFields, data.NewField("priority", nil, priorities).SetConfig(&data.FieldConfig{DisplayName: "Priority"}))
	newFields = append(newFields, data.NewField("patternString", nil, patterns).SetConfig(&data.FieldConfig{DisplayName: "Log Pattern"}))
	// FE expects the field name to be logTrend in order to identify histogram field for sparkline rendering
	newFields = append(newFields, data.NewField("logTrend", nil, logTrends).SetConfig(&data.FieldConfig{DisplayName: "Log Trend"}))
	newFields = append(newFields, data.NewField("firstSeen", nil, firstSeens).SetConfig(&data.FieldConfig{DisplayName: "First seen"}))
	newFields = append(newFields, data.NewField("lastSeen", nil, lastSeens).SetConfig(&data.FieldConfig{DisplayName: "Last seen"}))
	newFields = append(newFields, data.NewField("suppressed", nil, suppressedStatus).SetConfig(&data.FieldConfig{DisplayName: "Suppressed?"}))
	newFields = append(newFields, data.NewField("logGroupArnList", nil, logGroupArnLists).SetConfig(&data.FieldConfig{DisplayName: "Log Groups"}))
	newFields = append(newFields, data.NewField("anomalyArn", nil, anomalyArns).SetConfig(&data.FieldConfig{DisplayName: "Anomaly Arn"}))

	frame.Fields = newFields
	setPreferredVisType(frame, data.VisTypeTable)
	return frame, nil
}

func getSuppressionState(suppressionState string) cloudwatchLogsTypes.SuppressionState {
	switch suppressionState {
	case "suppressed":
		return cloudwatchLogsTypes.SuppressionStateSuppressed
	case "unsuppressed":
		return cloudwatchLogsTypes.SuppressionStateUnsuppressed
	case "all":
		return ""
	default:
		return ""
	}
}
