// SPDX-License-Identifier: GPL-3.0-or-later

package nginx

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
)

const (
	connActive  = "connActive"
	connAccepts = "connAccepts"
	connHandled = "connHandled"
	requests    = "requests"
	requestTime = "requestTime"
	connReading = "connReading"
	connWriting = "connWriting"
	connWaiting = "connWaiting"
)

var (
	nginxSeq = []string{
		connActive,
		connAccepts,
		connHandled,
		requests,
		connReading,
		connWriting,
		connWaiting,
	}
	tengineSeq = []string{
		connActive,
		connAccepts,
		connHandled,
		requests,
		requestTime,
		connReading,
		connWriting,
		connWaiting,
	}

	reStatus = regexp.MustCompile(`^Active connections: ([0-9]+)\n[^\d]+([0-9]+) ([0-9]+) ([0-9]+) ?([0-9]+)?\nReading: ([0-9]+) Writing: ([0-9]+) Waiting: ([0-9]+)`)
)

func newAPIClient(client *http.Client, request web.Request) *apiClient {
	return &apiClient{httpClient: client, request: request}
}

type apiClient struct {
	httpClient *http.Client
	request    web.Request
}

func (a apiClient) getStubStatus() (*stubStatus, error) {
	req, err := web.NewHTTPRequest(a.request)
	if err != nil {
		return nil, fmt.Errorf("error on creating request : %v", err)
	}

	resp, err := a.doRequestOK(req)
	defer closeBody(resp)
	if err != nil {
		return nil, err
	}

	status, err := parseStubStatus(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error on parsing response : %v", err)
	}

	return status, nil
}

func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
	resp, err := a.httpClient.Do(req)
	if err != nil {
		return resp, fmt.Errorf("error on request : %v", err)
	}

	if resp.StatusCode != http.StatusOK {
		return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
	}

	return resp, err
}

func closeBody(resp *http.Response) {
	if resp != nil && resp.Body != nil {
		_, _ = io.Copy(io.Discard, resp.Body)
		_ = resp.Body.Close()
	}
}

func parseStubStatus(r io.Reader) (*stubStatus, error) {
	sc := bufio.NewScanner(r)
	var lines []string

	for sc.Scan() {
		lines = append(lines, strings.Trim(sc.Text(), "\r\n "))
	}

	parsed := reStatus.FindStringSubmatch(strings.Join(lines, "\n"))

	if len(parsed) == 0 {
		return nil, fmt.Errorf("can't parse '%v'", lines)
	}

	parsed = parsed[1:]

	var (
		seq    []string
		status stubStatus
	)

	switch len(parsed) {
	default:
		return nil, fmt.Errorf("invalid number of fields, got %d, expect %d or %d", len(parsed), len(nginxSeq), len(tengineSeq))
	case len(nginxSeq):
		seq = nginxSeq
	case len(tengineSeq):
		seq = tengineSeq
	}

	for i, key := range seq {
		strValue := parsed[i]
		if strValue == "" {
			continue
		}
		value := mustParseInt(strValue)
		switch key {
		default:
			return nil, fmt.Errorf("unknown key in seq : %s", key)
		case connActive:
			status.Connections.Active = value
		case connAccepts:
			status.Connections.Accepts = value
		case connHandled:
			status.Connections.Handled = value
		case requests:
			status.Requests.Total = value
		case connReading:
			status.Connections.Reading = value
		case connWriting:
			status.Connections.Writing = value
		case connWaiting:
			status.Connections.Waiting = value
		case requestTime:
			status.Requests.Time = &value
		}
	}

	return &status, nil
}

func mustParseInt(value string) int64 {
	v, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		panic(err)
	}
	return v
}
