/*
** 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 ceph

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"

	"golang.zabbix.com/sdk/log"
	"golang.zabbix.com/sdk/zbxerr"
)

type cephResponse struct {
	Failed []struct {
		Outs string `json:"outs"`
	} `json:"failed"`
	Finished []struct {
		Outb string `json:"outb"`
	} `json:"finished"`
	HasFailed bool   `json:"has_failed"`
	Message   string `json:"message"`
}

// request makes an http request to Ceph RESTful API Module with a given command and extra parameters.
func request(ctx context.Context, client *http.Client, uri, cmd string, args map[string]string) ([]byte, error) {
	var resp cephResponse

	params := map[string]string{
		"prefix": cmd,
		"format": "json",
	}

	for k, v := range args {
		params[k] = v
	}

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

	req, err := http.NewRequestWithContext(ctx, "POST", uri+"/request?wait=1", bytes.NewBuffer(requestBody))
	if err != nil {
		return nil, zbxerr.New(err.Error())
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Add("User-Agent", "zbx_monitor")

	res, err := client.Do(req)
	if err != nil {
		return nil, zbxerr.ErrorCannotFetchData.Wrap(err)
	}
	defer res.Body.Close()

	err = json.NewDecoder(res.Body).Decode(&resp)
	if err != nil {
		return nil, zbxerr.ErrorCannotUnmarshalJSON.Wrap(err)
	}

	if resp.HasFailed {
		if len(resp.Failed) == 0 {
			return nil, zbxerr.ErrorCannotParseResult
		}

		return nil, zbxerr.New(resp.Failed[0].Outs)
	}

	if len(resp.Message) > 0 {
		return nil, zbxerr.New(resp.Message)
	}

	log.Debugf("Response = %+v", resp)

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

	return []byte(resp.Finished[0].Outb), nil
}

type response struct {
	cmd  string
	data []byte
	err  error
}

// asyncRequest makes asynchronous https requests to Ceph RESTful API Module for each metric's command and sends
// results to the channel.
func asyncRequest(ctx context.Context, cancel context.CancelFunc, client *http.Client,
	uri string, meta metricMeta) <-chan *response {
	ch := make(chan *response, len(meta.commands))

	for _, cmd := range meta.commands {
		go func(cmd string) {
			data, err := request(ctx, client, uri, cmd, meta.args)
			if err != nil {
				cancel()
				ch <- &response{cmd, nil, err}
			}
			ch <- &response{cmd, data, err}
		}(string(cmd))
	}

	return ch
}