package controllers

import (
	"encoding/json"
	"mizuserver/pkg/models"
	"mizuserver/pkg/utils"
	"mizuserver/pkg/validation"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"

	basenine "github.com/up9inc/basenine/client/go"
	"github.com/up9inc/mizu/shared"
	"github.com/up9inc/mizu/shared/logger"
	tapApi "github.com/up9inc/mizu/tap/api"
)

var extensionsMap map[string]*tapApi.Extension // global

func InitExtensionsMap(ref map[string]*tapApi.Extension) {
	extensionsMap = ref
}

func Error(c *gin.Context, err error) bool {
	if err != nil {
		logger.Log.Errorf("Error getting entry: %v", err)
		c.Error(err)
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
			"error":     true,
			"type":      "error",
			"autoClose": "5000",
			"msg":       err.Error(),
		})
		return true // signal that there was an error and the caller should return
	}
	return false // no error, can continue
}

func GetEntries(c *gin.Context) {
	entriesRequest := &models.EntriesRequest{}

	if err := c.BindQuery(entriesRequest); err != nil {
		c.JSON(http.StatusBadRequest, err)
	}
	validationError := validation.Validate(entriesRequest)
	if validationError != nil {
		c.JSON(http.StatusBadRequest, validationError)
	}

	if entriesRequest.TimeoutMs == 0 {
		entriesRequest.TimeoutMs = 3000
	}

	data, meta, err := basenine.Fetch(shared.BasenineHost, shared.BaseninePort,
		entriesRequest.LeftOff, entriesRequest.Direction, entriesRequest.Query,
		entriesRequest.Limit, time.Duration(entriesRequest.TimeoutMs)*time.Millisecond)
	if err != nil {
		c.JSON(http.StatusInternalServerError, validationError)
	}

	response := &models.EntriesResponse{}
	var dataSlice []interface{}

	for _, row := range data {
		var entry *tapApi.Entry
		err = json.Unmarshal(row, &entry)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error":     true,
				"type":      "error",
				"autoClose": "5000",
				"msg":       string(row),
			})
			return // exit
		}

		base := tapApi.Summarize(entry)

		dataSlice = append(dataSlice, base)
	}

	var metadata *basenine.Metadata
	err = json.Unmarshal(meta, &metadata)
	if err != nil {
		logger.Log.Debugf("Error recieving metadata: %v", err.Error())
	}

	response.Data = dataSlice
	response.Meta = metadata

	c.JSON(http.StatusOK, response)
}

func GetEntry(c *gin.Context) {
	singleEntryRequest := &models.SingleEntryRequest{}

	if err := c.BindQuery(singleEntryRequest); err != nil {
		c.JSON(http.StatusBadRequest, err)
	}
	validationError := validation.Validate(singleEntryRequest)
	if validationError != nil {
		c.JSON(http.StatusBadRequest, validationError)
	}

	id, _ := strconv.Atoi(c.Param("id"))
	var entry *tapApi.Entry
	bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, id, singleEntryRequest.Query)
	if Error(c, err) {
		return // exit
	}
	err = json.Unmarshal(bytes, &entry)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"error":     true,
			"type":      "error",
			"autoClose": "5000",
			"msg":       string(bytes),
		})
		return // exit
	}

	extension := extensionsMap[entry.Protocol.Name]
	representation, bodySize, _ := extension.Dissector.Represent(entry.Request, entry.Response)

	var rules []map[string]interface{}
	var isRulesEnabled bool
	if entry.Protocol.Name == "http" {
		harEntry, _ := utils.NewEntry(entry.Request, entry.Response, entry.StartTime, entry.ElapsedTime)
		_, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entry.Destination.Name)
		isRulesEnabled = _isRulesEnabled
		inrec, _ := json.Marshal(rulesMatched)
		json.Unmarshal(inrec, &rules)
	}

	c.JSON(http.StatusOK, tapApi.EntryWrapper{
		Protocol:       entry.Protocol,
		Representation: string(representation),
		BodySize:       bodySize,
		Data:           entry,
		Rules:          rules,
		IsRulesEnabled: isRulesEnabled,
	})
}
