package loganalytics

import (
	"compress/flate"
	"compress/gzip"
	"fmt"
	"io"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/andybalholm/brotli"
	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/grafana/grafana-plugin-sdk-go/data"
	"github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery"
)

func AddCustomDataLink(frame data.Frame, dataLink data.DataLink, singleField bool) data.Frame {
	for i := range frame.Fields {
		if frame.Fields[i].Config == nil {
			frame.Fields[i].Config = &data.FieldConfig{}
		}

		frame.Fields[i].Config.Links = append(frame.Fields[i].Config.Links, dataLink)

		// Queries using the trace viz only need the link added to a single field
		if singleField {
			break
		}
	}
	return frame
}

const SingleField bool = true
const MultiField bool = false

func AddConfigLinks(frame data.Frame, dl string, title *string) data.Frame {
	linkTitle := "View query in Azure Portal"
	if title != nil {
		linkTitle = *title
	}

	deepLink := data.DataLink{
		Title:       linkTitle,
		TargetBlank: true,
		URL:         dl,
	}

	frame = AddCustomDataLink(frame, deepLink, MultiField)

	return frame
}

// Check whether a query should be handled as basic logs query
// 1. resource selected is a workspace
// 2. query is not an alerts query
// 3. number of selected resources is exactly one
// 4. the ds toggle is set to true
func meetsBasicLogsCriteria(resources []string, fromAlert bool, basicLogsEnabled bool) (bool, error) {
	if fromAlert {
		return false, backend.DownstreamError(fmt.Errorf("basic Logs queries cannot be used for alerts"))
	}
	if len(resources) != 1 {
		return false, backend.DownstreamError(fmt.Errorf("basic logs queries cannot be run against multiple resources"))
	}

	if !strings.Contains(strings.ToLower(resources[0]), "microsoft.operationalinsights/workspaces") {
		return false, backend.DownstreamError(fmt.Errorf("basic logs queries may only be run against Log Analytics workspaces"))
	}

	if !basicLogsEnabled {
		return false, backend.DownstreamError(fmt.Errorf("basic Logs queries are disabled for this data source"))
	}

	return true, nil
}

// This function should be part of migration function
func ParseResultFormat(queryResultFormat *dataquery.ResultFormat, queryType dataquery.AzureQueryType) dataquery.ResultFormat {
	if queryResultFormat != nil && *queryResultFormat != "" {
		return *queryResultFormat
	}
	if queryType == dataquery.AzureQueryTypeLogAnalytics {
		// Default to time series format for logs queries. It was time series before this change
		return dataquery.ResultFormatTimeSeries
	}
	if queryType == dataquery.AzureQueryTypeAzureTraces {
		// Default to table format for traces queries as many traces may be returned
		return dataquery.ResultFormatTable
	}
	return ""
}

func getApiURL(resourceOrWorkspace string, isAppInsightsQuery bool, basicLogsQuery bool) string {
	matchesResourceURI, _ := regexp.MatchString("^/subscriptions/", resourceOrWorkspace)

	queryOrSearch := "query"
	if basicLogsQuery {
		queryOrSearch = "search"
	}

	if matchesResourceURI {
		if isAppInsightsQuery {
			componentName := resourceOrWorkspace[strings.LastIndex(resourceOrWorkspace, "/")+1:]
			return fmt.Sprintf("v1/apps/%s/query", componentName)
		}
		return fmt.Sprintf("v1%s/%s", resourceOrWorkspace, queryOrSearch)
	} else {
		return fmt.Sprintf("v1/workspaces/%s/%s", resourceOrWorkspace, queryOrSearch)
	}
}

// Legacy queries only specify a Workspace GUID, which we need to use the old workspace-centric
// API URL for, and newer queries specifying a resource URI should use resource-centric API.
// However, legacy workspace queries using a `workspaces()` template variable will be resolved
// to a resource URI, so they should use the new resource-centric.
func retrieveResources(query dataquery.AzureLogsQuery) ([]string, string) {
	resources := []string{}
	var resourceOrWorkspace string
	if len(query.Resources) > 0 {
		resources = query.Resources
		resourceOrWorkspace = query.Resources[0]
	} else if query.Resource != nil && *query.Resource != "" {
		resources = []string{*query.Resource}
		resourceOrWorkspace = *query.Resource
	} else if query.Workspace != nil {
		resourceOrWorkspace = *query.Workspace
	}

	return resources, resourceOrWorkspace
}

func ConvertTime(timeStamp string) (time.Time, error) {
	// Convert the timestamp string to an int64
	timestampInt, err := strconv.ParseInt(timeStamp, 10, 64)
	if err != nil {
		// Handle error
		return time.Time{}, err
	}

	// Convert the Unix timestamp (in milliseconds) to a time.Time
	convTimeStamp := time.Unix(0, timestampInt*int64(time.Millisecond))

	return convTimeStamp, nil
}

func GetDataVolumeRawQuery(table string) string {
	return fmt.Sprintf("Usage \n| where DataType == \"%s\"\n| where IsBillable == true\n| summarize BillableDataGB = round(sum(Quantity) / 1000, 3)", table)
}

// This function handles various compression mechanisms that may have been used on a response body
func decode(encoding string, original io.ReadCloser) ([]byte, error) {
	var reader io.Reader
	var err error
	switch encoding {
	case "gzip":
		reader, err = gzip.NewReader(original)
		if err != nil {
			return nil, err
		}
		defer func() {
			if err := reader.(io.ReadCloser).Close(); err != nil {
				backend.Logger.Warn("Failed to close reader body", "err", err)
			}
		}()
	case "deflate":
		reader = flate.NewReader(original)
		defer func() {
			if err := reader.(io.ReadCloser).Close(); err != nil {
				backend.Logger.Warn("Failed to close reader body", "err", err)
			}
		}()
	case "br":
		reader = brotli.NewReader(original)
	case "":
		reader = original
	default:
		return nil, fmt.Errorf("unexpected encoding type %v", err)
	}

	body, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	return body, nil
}
