package contextlinks

import (
	"encoding/json"
	"fmt"
	"net/url"
	"time"

	tracesV3 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v3"
	v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
	"github.com/SigNoz/signoz/pkg/query-service/utils"
)

func PrepareLinksToTraces(start, end time.Time, filterItems []v3.FilterItem) string {

	// Traces list view expects time in nanoseconds
	tr := URLShareableTimeRange{
		Start:    start.UnixNano(),
		End:      end.UnixNano(),
		PageSize: 100,
	}

	options := URLShareableOptions{
		MaxLines:      2,
		Format:        "list",
		SelectColumns: tracesV3.TracesListViewDefaultSelectedColumns,
	}

	period, _ := json.Marshal(tr)
	urlEncodedTimeRange := url.QueryEscape(string(period))

	builderQuery := v3.BuilderQuery{
		DataSource:         v3.DataSourceTraces,
		QueryName:          "A",
		AggregateOperator:  v3.AggregateOperatorNoOp,
		AggregateAttribute: v3.AttributeKey{},
		Filters: &v3.FilterSet{
			Items:    filterItems,
			Operator: "AND",
		},
		Expression:   "A",
		Disabled:     false,
		Having:       []v3.Having{},
		StepInterval: 60,
		OrderBy: []v3.OrderBy{
			{
				ColumnName: "timestamp",
				Order:      "desc",
			},
		},
	}

	urlData := URLShareableCompositeQuery{
		QueryType: string(v3.QueryTypeBuilder),
		Builder: URLShareableBuilderQuery{
			QueryData: []LinkQuery{
				{BuilderQuery: builderQuery},
			},
			QueryFormulas: make([]string, 0),
		},
	}

	data, _ := json.Marshal(urlData)
	compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))

	optionsData, _ := json.Marshal(options)
	urlEncodedOptions := url.QueryEscape(string(optionsData))

	return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
}

func PrepareLinksToLogs(start, end time.Time, filterItems []v3.FilterItem) string {

	// Logs list view expects time in milliseconds
	tr := URLShareableTimeRange{
		Start:    start.UnixMilli(),
		End:      end.UnixMilli(),
		PageSize: 100,
	}

	options := URLShareableOptions{
		MaxLines:      2,
		Format:        "list",
		SelectColumns: []v3.AttributeKey{},
	}

	period, _ := json.Marshal(tr)
	urlEncodedTimeRange := url.QueryEscape(string(period))

	builderQuery := v3.BuilderQuery{
		DataSource:         v3.DataSourceLogs,
		QueryName:          "A",
		AggregateOperator:  v3.AggregateOperatorNoOp,
		AggregateAttribute: v3.AttributeKey{},
		Filters: &v3.FilterSet{
			Items:    filterItems,
			Operator: "AND",
		},
		Expression:   "A",
		Disabled:     false,
		Having:       []v3.Having{},
		StepInterval: 60,
		OrderBy: []v3.OrderBy{
			{
				ColumnName: "timestamp",
				Order:      "desc",
			},
		},
	}

	urlData := URLShareableCompositeQuery{
		QueryType: string(v3.QueryTypeBuilder),
		Builder: URLShareableBuilderQuery{
			QueryData: []LinkQuery{
				{BuilderQuery: builderQuery},
			},
			QueryFormulas: make([]string, 0),
		},
	}

	data, _ := json.Marshal(urlData)
	compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))

	optionsData, _ := json.Marshal(options)
	urlEncodedOptions := url.QueryEscape(string(optionsData))

	return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
}

// The following function is used to prepare the where clause for the query
// `lbls` contains the key value pairs of the labels from the result of the query
// We iterate over the where clause and replace the labels with the actual values
// There are two cases:
// 1. The label is present in the where clause
// 2. The label is not present in the where clause
//
// Example for case 2:
// Latency by serviceName without any filter
// In this case, for each service with latency > threshold we send a notification
// The expectation will be that clicking on the related traces for service A, will
// take us to the traces page with the filter serviceName=A
// So for all the missing labels in the where clause, we add them as key = value
//
// Example for case 1:
// Severity text IN (WARN, ERROR)
// In this case, the Severity text will appear in the `lbls` if it were part of the group
// by clause, in which case we replace it with the actual value for the notification
// i.e Severity text = WARN
// If the Severity text is not part of the group by clause, then we add it as it is.
func PrepareFilters(labels map[string]string, whereClauseItems []v3.FilterItem, groupByItems []v3.AttributeKey, keys map[string]v3.AttributeKey) []v3.FilterItem {
	filterItems := make([]v3.FilterItem, 0)

	//delete predefined alert labels
	for _, label := range PredefinedAlertLabels {
		delete(labels, label)
	}

	added := make(map[string]struct{})

	for _, item := range whereClauseItems {
		exists := false
		for key, value := range labels {
			if item.Key.Key == key {
				// if the label is present in the where clause, replace it with key = value
				filterItems = append(filterItems, v3.FilterItem{
					Key:      item.Key,
					Operator: v3.FilterOperatorEqual,
					Value:    value,
				})
				exists = true
				added[key] = struct{}{}
				break
			}
		}

		if !exists {
			// if there is no label for the filter item, add it as it is
			filterItems = append(filterItems, item)
		}
	}

	// if there are labels which are not part of the where clause, but
	// exist in the result, then they could be part of the group by clause
	for key, value := range labels {
		if _, ok := added[key]; !ok {
			// start by taking the attribute key from the keys map, if not present, create a new one
			var attributeKey v3.AttributeKey
			var attrFound bool

			// as of now this logic will only apply for logs
			for _, tKey := range utils.GenerateEnrichmentKeys(v3.AttributeKey{Key: key}) {
				if val, ok := keys[tKey]; ok {
					attributeKey = val
					attrFound = true
					break
				}
			}

			// check if the attribute key is directly present, as of now this will always be false for logs
			// as for logs it will be satisfied in the condition above
			if !attrFound {
				attributeKey, attrFound = keys[key]
			}

			// if the attribute key is not present, create a new one
			if !attrFound {
				attributeKey = v3.AttributeKey{Key: key}
			}

			// if there is a group by item with the same key, use that instead
			for _, groupByItem := range groupByItems {
				if groupByItem.Key == key {
					attributeKey = groupByItem
					break
				}
			}

			filterItems = append(filterItems, v3.FilterItem{
				Key:      attributeKey,
				Operator: v3.FilterOperatorEqual,
				Value:    value,
			})
		}
	}

	return filterItems
}

func PrepareLinksToTracesV5(start, end time.Time, whereClause string) string {

	// Traces list view expects time in nanoseconds
	tr := URLShareableTimeRange{
		Start:    start.UnixNano(),
		End:      end.UnixNano(),
		PageSize: 100,
	}

	options := URLShareableOptions{}

	period, _ := json.Marshal(tr)
	urlEncodedTimeRange := url.QueryEscape(string(period))

	linkQuery := LinkQuery{
		BuilderQuery: v3.BuilderQuery{
			DataSource:         v3.DataSourceTraces,
			QueryName:          "A",
			AggregateOperator:  v3.AggregateOperatorNoOp,
			AggregateAttribute: v3.AttributeKey{},
			Expression:         "A",
			Disabled:           false,
			Having:             []v3.Having{},
			StepInterval:       60,
		},
		Filter: &FilterExpression{Expression: whereClause},
	}

	urlData := URLShareableCompositeQuery{
		QueryType: string(v3.QueryTypeBuilder),
		Builder: URLShareableBuilderQuery{
			QueryData: []LinkQuery{
				linkQuery,
			},
			QueryFormulas: make([]string, 0),
		},
	}

	data, _ := json.Marshal(urlData)
	compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))

	optionsData, _ := json.Marshal(options)
	urlEncodedOptions := url.QueryEscape(string(optionsData))

	return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
}

func PrepareLinksToLogsV5(start, end time.Time, whereClause string) string {

	// Logs list view expects time in milliseconds
	tr := URLShareableTimeRange{
		Start:    start.UnixMilli(),
		End:      end.UnixMilli(),
		PageSize: 100,
	}

	options := URLShareableOptions{}

	period, _ := json.Marshal(tr)
	urlEncodedTimeRange := url.QueryEscape(string(period))

	linkQuery := LinkQuery{
		BuilderQuery: v3.BuilderQuery{
			DataSource:         v3.DataSourceLogs,
			QueryName:          "A",
			AggregateOperator:  v3.AggregateOperatorNoOp,
			AggregateAttribute: v3.AttributeKey{},
			Expression:         "A",
			Disabled:           false,
			Having:             []v3.Having{},
			StepInterval:       60,
		},
		Filter: &FilterExpression{Expression: whereClause},
	}

	urlData := URLShareableCompositeQuery{
		QueryType: string(v3.QueryTypeBuilder),
		Builder: URLShareableBuilderQuery{
			QueryData: []LinkQuery{
				linkQuery,
			},
			QueryFormulas: make([]string, 0),
		},
	}

	data, _ := json.Marshal(urlData)
	compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))

	optionsData, _ := json.Marshal(options)
	urlEncodedOptions := url.QueryEscape(string(optionsData))

	return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
}
