package controller

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/olivere/elastic/v7"
	"github.com/rs/zerolog/log"
	"gitlab.com/tensorsecurity-rd/waf-console/internal/service"
	"gitlab.com/tensorsecurity-rd/waf-console/internal/utils"
	"gorm.io/gorm"
)

type WafController struct {
	service service.Service
}

func NewWafController(clusterClientManager *utils.ClusterClientManager, db *gorm.DB, gatewayUrl string, elasticClient *elastic.Client) *WafController {
	return &WafController{
		service: service.NewWafService(clusterClientManager, db, gatewayUrl, elasticClient),
	}
}

func (c *WafController) Waf(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	gatewayName := ctx.Query("gateway_name")
	regionCode := ctx.Query("region_code")
	namespace := ctx.Query("namespace")

	waf, err := c.service.GetWaf(ctx1, regionCode, namespace, gatewayName)
	if err != nil {
		// logging.Get().Err(err).Msgf("get waf")
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	resp := &utils.SingleRespData{
		Item: waf,
	}
	utils.AssembleResponse(ctx, resp, nil)
}

func (c *WafController) ListWafs(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	wafs, err := c.service.ListWafs(ctx1)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, wafs, nil)
}

func (c *WafController) GetWafGatewayInfo(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	cookie := ctx.Request.Header.Get("Cookie")
	if cookie == "" {
		utils.AssembleResponse(ctx, nil, errors.New("cookie is required"))
		return
	}

	var req service.GetWafGatewayInfoReq
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	info, err := c.service.GetWafGatewayInfo(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, info, nil)
}

func (c *WafController) CreateWaf(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req []service.CreateWafReq
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	for _, r := range req {
		_, err := c.service.CreateWaf(ctx1, &r)
		if err != nil {
			utils.AssembleResponse(ctx, nil, err)
			return
		}
	}
	utils.AssembleResponse(ctx, nil, nil)
}

// `
// curl -X POST http://127.0.0.1:8080/v1/api/waf \
//      -H "Content-Type: application/json" \
//      -d '[
//            {
//              "region_code": "cn-east-1",
//              "gatewayname": "istio-ingressgateway",
// 			 "namespace": "istio-system",
// 			 "port": 80,
// 			 "host": ["*"]
//            }
//          ]'
// `

func (c *WafController) UpdateMode(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req service.UpdateModeReq
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	_, err := c.service.UpdateMode(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) UpdateRule(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req service.RuleRequest
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	err := c.service.UpdateRule(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) ListRules(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	regionCode := ctx.Query("region_code")
	namespace := ctx.Query("namespace")
	gatewayName := ctx.Query("gateway_name")
	language := ctx.Request.Header.Get("Accept-Language")
	name := ctx.Query("name")
	if language == "" {
		language = "zh"
	}

	rules, err := c.service.ListRules(ctx1, regionCode, namespace, gatewayName, language, name)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	type resp struct {
		Items []service.RuleGroupResp `json:"items"`
	}
	items := []service.RuleGroupResp{}
	for _, rule := range rules {
		items = append(items, service.RuleGroupResp{
			CategoryID:  rule.CategoryID,
			Status:      rule.Status,
			Category:    rule.Category,
			Description: rule.Description,
		})
	}

	utils.AssembleResponse(ctx, resp{Items: items}, nil)
}

func (c *WafController) SaveRuleCategoryToDB(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	err := c.service.SaveRuleCategoryToDB(ctx1)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) EnableListenerWaf(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req service.EnableListenerWafReq
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	err := c.service.EnableListenerWaf(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) EnableGatewayWaf(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req service.EnableGatewayWafReq
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	req.Cookie = ctx.Request.Header.Get("Cookie")

	err := c.service.EnableGatewayWaf(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) EnableListenerWafs(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req service.EnableListenerWafsReq
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	err := c.service.EnableListenerWafs(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) DeleteListenerWaf(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	regionCode := ctx.Param("region_code")
	namespace := ctx.Param("namespace")
	gatewayName := ctx.Param("gateway_name")
	port, err := strconv.Atoi(ctx.Param("port"))
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	err = c.service.DeleteListenerWaf(ctx1, &service.DeleteListenerReq{
		GatewateInfo: service.GatewateInfo{
			RegionCode:  regionCode,
			Namespace:   namespace,
			GatewayName: gatewayName,
		},
		Port: port,
	})
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) DeleteGatewayWaf(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	regionCode := ctx.Param("region_code")
	namespace := ctx.Param("namespace")
	gatewayName := ctx.Param("gateway_name")

	err := c.service.DeleteGatewayWaf(ctx1, &service.GatewateInfo{
		RegionCode:  regionCode,
		Namespace:   namespace,
		GatewayName: gatewayName,
	})
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) ListAttackLogs(ctx *gin.Context) {
	var filter service.AttackLogFilter

	id := ctx.Query("serviceId")
	filter.ServiceId, _ = strconv.ParseInt(id, 10, 64)
	filter.Limit, filter.Offset, _ = getLimitAndOffset(ctx)
	filter.Cluster = ctx.Query("cluster")
	filter.AttackUrl = ctx.Query("attackAddr")
	filter.AttackIp = ctx.Query("attackIp")
	filter.AttackType = ctx.Query("attackType")
	filter.AttackApp = ctx.Query("attackApp")
	filter.AttackListener = ctx.Query("attackListener")
	filter.Token = ctx.Query("token")
	filter.Action = ctx.Query("action")
	stime := ctx.Query("startTime")
	filter.StartTime, _ = strconv.ParseInt(stime, 10, 64)
	etime := ctx.Query("endTime")
	filter.EndTime, _ = strconv.ParseInt(etime, 10, 64)

	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	logs, token, err := c.service.ListAttackLogs(ctx1, &filter)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	log.Info().Interface("logs", logs).Msg("logs")

	type resp struct {
		Items []service.AttackLog `json:"items"`
		Token string              `json:"token"`
	}

	utils.AssembleResponse(ctx, resp{
		Items: logs,
		Token: token,
	}, nil)
}

// getLimitAndOffset extracts pagination parameters from the context
// Returns limit, offset, and error
func getLimitAndOffset(ctx *gin.Context) (int, int, error) {
	limit := 10 // default limit
	offset := 0 // default offset

	// Get limit from query parameters
	if limitStr := ctx.Query("limit"); limitStr != "" {
		parsedLimit, err := strconv.Atoi(limitStr)
		if err != nil {
			return 0, 0, fmt.Errorf("invalid limit parameter: %v", err)
		}
		if parsedLimit > 0 {
			limit = parsedLimit
		}
	}

	// Get offset from query parameters
	if offsetStr := ctx.Query("offset"); offsetStr != "" {
		parsedOffset, err := strconv.Atoi(offsetStr)
		if err != nil {
			return 0, 0, fmt.Errorf("invalid offset parameter: %v", err)
		}
		if parsedOffset >= 0 {
			offset = parsedOffset
		}
	}

	return limit, offset, nil
}

func (c *WafController) CreateBlackWhiteList(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req service.MatcherExpr
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	err := c.service.CreateBlackWhiteList(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) UpdateBlackWhiteList(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req service.MatcherExpr
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	err := c.service.UpdateBlackWhiteList(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}
func (c *WafController) EnableBlackWhiteList(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var req service.MatcherExpr
	if err := ctx.BindJSON(&req); err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	err := c.service.EnableBlackWhiteList(ctx1, &req)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) DeleteBlackWhiteList(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	id := ctx.Param("id")
	idUint, err := strconv.ParseUint(id, 10, 32)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	log.Info().Interface("delete black white list", idUint).Msg("delete black white list")
	err = c.service.DeleteBlackWhiteList(ctx1, uint32(idUint))
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	utils.AssembleResponse(ctx, nil, nil)
}

func (c *WafController) GetBlackWhiteLists(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	limit, offset, err := getLimitAndOffset(ctx)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	name := ctx.Query("name")
	status := ctx.Query("status")
	mode := ctx.Query("mode")
	expr := ctx.Query("expr")

	query := service.MatchExprQuery()

	if status != "" {
		statusList := strings.Split(status, ",")
		if len(statusList) == 1 && statusList[0] != "" {
			switch status {
			case "0":
				query.WithStatus(0)
			case "1":
				query.WithStatus(1)
			}
		}
	}

	if name != "" {
		query.WithFuzzyName(name)
	}

	if mode != "" {
		modes := strings.Split(mode, ",")
		query.WithInConditionCustom("mode", modes)
	}

	if expr != "" {
		query.WithFuzzyExpr(expr)
	}
	lists, total, err := c.service.GetBlackWhiteLists(ctx1, query, limit, offset)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	respData := utils.ListRespData{
		Items:        lists,
		TotalItems:   total,
		ItemsPerPage: limit,
	}
	utils.AssembleResponse(ctx, respData, nil)
}

func (c *WafController) ListListenerHistory(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	limit, offset, err := getLimitAndOffset(ctx)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}
	limit = 1000000
	name := ctx.Query("name")
	gatewayName := ctx.Query("gateway_name")
	listenerName := ctx.Query("listener_name")
	regionCode := ctx.Query("region_code")
	operation := ctx.Query("operation")
	status := ctx.Query("status")

	query := service.WafListenerHistoryQuery()
	if name != "" {
		query.WithFuzzyName(name)
	}
	if gatewayName != "" {
		query.WithFuzzyGatewayName(gatewayName)
	}
	if listenerName != "" {
		query.WithFuzzyListenerName(listenerName)
	}

	if regionCode != "" {
		query.WithFuzzyRegionCode(regionCode)
	}
	if operation != "" {
		query.WithOperation(operation)
	}
	if status != "" {
		statusInt, err := strconv.Atoi(status)
		if err != nil {
			utils.AssembleResponse(ctx, nil, err)
			return
		}
		query.WithStatus(int32(statusInt))
	}

	lists, total, err := c.service.ListListenerHistory(ctx1, query, limit, offset)
	if err != nil {
		utils.AssembleResponse(ctx, nil, err)
		return
	}

	respData := utils.ListRespData{
		Items:        lists,
		TotalItems:   total,
		ItemsPerPage: limit,
	}
	utils.AssembleResponse(ctx, respData, nil)
}

func (c *WafController) AttackClassesList(ctx *gin.Context) {
	ctx1, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	language := ctx.Request.Header.Get("Accept-Language")
	if language == "" {
		language = "zh"
	}

	classes := c.service.ListAttackClasses(ctx1, language)
	respData := utils.ListRespData{
		Items:        classes,
		TotalItems:   len(classes),
		ItemsPerPage: 10,
	}

	utils.AssembleResponse(ctx, respData, nil)
}
