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

package console

import (
	"fmt"
	"net/http"
	"reflect"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"akvorado/common/helpers"
	"akvorado/common/schema"
)

func (c *Component) widgetFlowLastHandlerFunc(gc *gin.Context) {
	ctx := c.t.Context(gc.Request.Context())
	replace := []struct {
		key         schema.ColumnKey
		replaceWith string
	}{
		{schema.ColumnDstCommunities, `arrayMap(c -> concat(toString(bitShiftRight(c, 16)), ':',
                      toString(bitAnd(c, 0xffff))), DstCommunities)`},
		{schema.ColumnDstLargeCommunities, `arrayMap(c -> concat(toString(bitAnd(bitShiftRight(c, 64), 0xffffffff)), ':',
                      toString(bitAnd(bitShiftRight(c, 32), 0xffffffff)), ':',
                      toString(bitAnd(c, 0xffffffff))), DstLargeCommunities)`},
		{schema.ColumnSrcMAC, `MACNumToString(SrcMAC)`},
		{schema.ColumnDstMAC, `MACNumToString(DstMAC)`},
	}
	selectClause := []string{"SELECT *"}
	except := []string{}
	for _, r := range replace {
		if column, ok := c.d.Schema.LookupColumnByKey(r.key); ok && !column.Disabled {
			except = append(except, r.key.String())
			selectClause = append(selectClause, fmt.Sprintf("%s AS %s", r.replaceWith, r.key))
		}
	}
	if len(except) > 0 {
		selectClause[0] = fmt.Sprintf("SELECT * EXCEPT (%s)", strings.Join(except, ", "))
	}
	query := fmt.Sprintf(`
%s
FROM flows
WHERE TimeReceived=(SELECT MAX(TimeReceived) FROM flows)
LIMIT 1`, strings.Join(selectClause, ",\n "))
	gc.Header("X-SQL-Query", query)
	// Do not increase counter for this one.
	rows, err := c.d.ClickHouseDB.Conn.Query(ctx, query)
	if err != nil {
		c.r.Err(err).Msg("unable to query database")
		gc.JSON(http.StatusInternalServerError, gin.H{"message": "Unable to query database."})
		return
	}

	if !rows.Next() {
		gc.JSON(http.StatusNotFound, gin.H{"message": "No flow currently in database."})
		return
	}
	defer rows.Close()

	var (
		response    = gin.H{}
		columnTypes = rows.ColumnTypes()
		vars        = make([]any, len(columnTypes))
	)
	for i := range columnTypes {
		vars[i] = reflect.New(columnTypes[i].ScanType()).Interface()
	}
	if err := rows.Scan(vars...); err != nil {
		c.r.Err(err).Msg("unable to parse flow")
		gc.JSON(http.StatusInternalServerError, gin.H{"message": "Unable to parse flow."})
		return
	}
	for index, column := range rows.Columns() {
		response[column] = vars[index]
	}
	gc.IndentedJSON(http.StatusOK, response)
}

func (c *Component) widgetFlowRateHandlerFunc(gc *gin.Context) {
	ctx := c.t.Context(gc.Request.Context())
	query := `SELECT COUNT(*)/300 AS rate FROM flows WHERE TimeReceived > date_sub(minute, 5, now())`
	gc.Header("X-SQL-Query", query)
	// Do not increase counter for this one.
	var result float64
	row := c.d.ClickHouseDB.Conn.QueryRow(ctx, query)
	if err := row.Scan(&result); err != nil {
		c.r.Err(err).Msg("unable to parse result")
		gc.JSON(http.StatusInternalServerError, gin.H{"message": "Unable to parse result."})
		return
	}
	gc.IndentedJSON(http.StatusOK, gin.H{
		"rate":   result,
		"period": "second",
	})
}

func (c *Component) widgetExportersHandlerFunc(gc *gin.Context) {
	ctx := c.t.Context(gc.Request.Context())
	query := `SELECT ExporterName FROM exporters GROUP BY ExporterName ORDER BY ExporterName`
	gc.Header("X-SQL-Query", query)
	// Do not increase counter for this one.

	exporters := []struct {
		ExporterName string
	}{}
	err := c.d.ClickHouseDB.Conn.Select(ctx, &exporters, query)
	if err != nil {
		c.r.Err(err).Msg("unable to query database")
		gc.JSON(http.StatusInternalServerError, gin.H{"message": "Unable to query database."})
		return
	}
	exporterList := make([]string, len(exporters))
	for idx, exporter := range exporters {
		exporterList[idx] = exporter.ExporterName
	}

	gc.IndentedJSON(http.StatusOK, gin.H{"exporters": exporterList})
}

// UnmarshalParam is similar to UnmarshalText but for Gin.
func (i *HomepageTopWidget) UnmarshalParam(param string) error {
	var err error
	*i, err = HomepageTopWidgetString(param)
	return err
}

type topResult struct {
	Name    string  `json:"name"`
	Percent float64 `json:"percent"`
}

func (c *Component) widgetTopHandlerFunc(gc *gin.Context) {
	ctx := c.t.Context(gc.Request.Context())
	var (
		selector          string
		groupby           string
		filter            string
		mainTableRequired bool
	)

	type URIParams struct {
		WidgetName HomepageTopWidget `uri:"name" binding:"required"`
	}
	var uriParams URIParams
	if err := gc.ShouldBindUri(&uriParams); err != nil {
		gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
		return
	}

	switch uriParams.WidgetName {
	default:
		gc.JSON(http.StatusNotFound, gin.H{"message": "Unknown top request."})
		return
	case HomepageTopWidgetSrcAS:
		selector = fmt.Sprintf(`concat(toString(SrcAS), ': ', dictGetOrDefault('%s', 'name', SrcAS, '???'))`, schema.DictionaryASNs)
		groupby = `SrcAS`
	case HomepageTopWidgetDstAS:
		selector = fmt.Sprintf(`concat(toString(DstAS), ': ', dictGetOrDefault('%s', 'name', DstAS, '???'))`, schema.DictionaryASNs)
		groupby = `DstAS`
	case HomepageTopWidgetSrcCountry:
		selector = `SrcCountry`
	case HomepageTopWidgetDstCountry:
		selector = `DstCountry`
	case HomepageTopWidgetExporter:
		selector = "ExporterName"
	case HomepageTopWidgetProtocol:
		selector = fmt.Sprintf(`dictGetOrDefault('%s', 'name', Proto, '???')`, schema.DictionaryProtocols)
		groupby = `Proto`
	case HomepageTopWidgetEtype:
		selector = `if(equals(EType, 34525), 'IPv6', if(equals(EType, 2048), 'IPv4', '???'))`
		groupby = `EType`
	case HomepageTopWidgetSrcPort:
		selector = fmt.Sprintf(`concat(dictGetOrDefault('%s', 'name', Proto, '???'), '/', toString(SrcPort))`, schema.DictionaryProtocols)
		groupby = `Proto, SrcPort`
		mainTableRequired = true
	case HomepageTopWidgetDstPort:
		selector = fmt.Sprintf(`concat(dictGetOrDefault('%s', 'name', Proto, '???'), '/', toString(DstPort))`, schema.DictionaryProtocols)
		groupby = `Proto, DstPort`
		mainTableRequired = true
	}
	if strings.HasPrefix(gc.Param("name"), "src-") {
		filter = "AND InIfBoundary = 'external'"
	} else if strings.HasPrefix(gc.Param("name"), "dst-") {
		filter = "AND OutIfBoundary = 'external'"
	}
	if groupby == "" {
		groupby = selector
	}

	now := c.d.Clock.Now()
	template := fmt.Sprintf(`
WITH
 (SELECT SUM(Bytes*SamplingRate) FROM {{ .Table }} WHERE {{ .Timefilter }} %s) AS Total
SELECT
 if(empty(%s),'Unknown',%s) AS Name,
 SUM(Bytes*SamplingRate) / Total * 100 AS Percent
FROM {{ .Table }}
WHERE {{ .Timefilter }}
%s
GROUP BY %s
ORDER BY Percent DESC
LIMIT 5`,
		filter, selector, selector, filter, groupby)

	query := c.finalizeTemplateQuery(templateQuery{
		Template: template,
		Context: inputContext{
			Start:             now.Add(-5 * time.Minute),
			End:               now,
			MainTableRequired: mainTableRequired,
			Points:            5,
		},
	})
	gc.Header("X-SQL-Query", query)

	results := []topResult{}
	err := c.d.ClickHouseDB.Conn.Select(ctx, &results, strings.TrimSpace(query))
	if err != nil {
		c.r.Err(err).Msg("unable to query database")
		gc.JSON(http.StatusInternalServerError, gin.H{"message": "Unable to query database."})
		return
	}
	gc.JSON(http.StatusOK, gin.H{"top": results})
}

func (c *Component) widgetGraphHandlerFunc(gc *gin.Context) {
	filter := c.config.HomepageGraphFilter
	if filter != "" {
		filter = fmt.Sprintf("AND %s", filter)
	}
	ctx := c.t.Context(gc.Request.Context())
	now := c.d.Clock.Now()
	template := fmt.Sprintf(`
SELECT
 {{ call .ToStartOfInterval "TimeReceived" }} AS Time,
 SUM(Bytes*SamplingRate*8/{{ .Interval }})/1000/1000/1000 AS Gbps
FROM {{ .Table }}
WHERE {{ .Timefilter }}
%s
GROUP BY Time
ORDER BY Time WITH FILL
 FROM {{ .TimefilterStart }}
 TO {{ .TimefilterEnd }} + INTERVAL 1 second
 STEP {{ .Interval }}`,
		filter)

	query := c.finalizeTemplateQuery(templateQuery{
		Template: template,
		Context: inputContext{
			Start:             now.Add(-c.config.HomepageGraphTimeRange),
			End:               now,
			MainTableRequired: false,
			Points:            200,
		},
	})
	gc.Header("X-SQL-Query", query)

	results := []struct {
		Time time.Time `json:"t"`
		Gbps float64   `json:"gbps"`
	}{}
	err := c.d.ClickHouseDB.Conn.Select(ctx, &results, strings.TrimSpace(query))
	if err != nil {
		c.r.Err(err).Msg("unable to query database")
		gc.JSON(http.StatusInternalServerError, gin.H{"message": "Unable to query database."})
		return
	}

	gc.JSON(http.StatusOK, gin.H{"data": results})
}
