/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

package redis

import (
	"bufio"
	"encoding/json"
	"regexp"
	"strings"

	"github.com/mediocregopher/radix/v3"
	"golang.zabbix.com/sdk/zbxerr"
)

type infoSection string
type infoKey string
type infoKeySpace map[infoKey]interface{}
type infoExtKey string
type infoExtKeySpace map[infoExtKey]string
type redisInfo map[infoSection]infoKeySpace

var redisSlaveMetricRE = regexp.MustCompile(`^slave\d+`)

// parseRedisInfo parses an output of 'INFO' command.
// https://redis.io/commands/info
func parseRedisInfo(info string) (res redisInfo, err error) {
	var (
		section infoSection
	)

	scanner := bufio.NewScanner(strings.NewReader(info))
	res = make(redisInfo)

	for scanner.Scan() {
		line := scanner.Text()

		if len(line) == 0 {
			continue
		}

		// Names of sections are preceded by '#'.
		if line[0] == '#' {
			section = infoSection(line[2:])
			if _, ok := res[section]; !ok {
				res[section] = make(infoKeySpace)
			}

			continue
		}

		// Each parameter represented in the 'key:value' format.
		kv := strings.SplitN(line, ":", 2)
		if len(kv) != 2 {
			continue
		}

		key := infoKey(kv[0])
		value := strings.TrimSpace(kv[1])

		// Followed sections has a bit more complicated format.
		// E.g: dbXXX: keys=XXX,expires=XXX
		if section == "Keyspace" || section == "Commandstats" ||
			(section == "Replication" && redisSlaveMetricRE.MatchString(string(key))) {
			extKeySpace := make(infoExtKeySpace)

			for _, ksParams := range strings.Split(value, ",") {
				ksParts := strings.Split(ksParams, "=")
				extKeySpace[infoExtKey(ksParts[0])] = ksParts[1]
			}

			res[section][key] = extKeySpace

			continue
		}

		if len(section) == 0 {
			return nil, zbxerr.ErrorCannotParseResult
		}

		res[section][key] = value
	}

	if err = scanner.Err(); err != nil {
		return nil, err
	}

	if len(res) == 0 {
		return nil, zbxerr.ErrorEmptyResult
	}

	return res, nil
}

// infoHandler gets an output of 'INFO' command, parses it and returns it in JSON format.
func infoHandler(conn redisClient, params map[string]string) (interface{}, error) {
	var res string

	section := infoSection(strings.ToLower(params["Section"]))

	if err := conn.Query(radix.Cmd(&res, "INFO", string(section))); err != nil {
		return nil, zbxerr.ErrorCannotFetchData.Wrap(err)
	}

	redisInfo, err := parseRedisInfo(res)
	if err != nil {
		return nil, err
	}

	jsonRes, err := json.Marshal(redisInfo)
	if err != nil {
		return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
	}

	return string(jsonRes), nil
}
