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

package lighttpd

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

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

const (
	busyWorkers = "BusyWorkers"
	idleWorkers = "IdleWorkers"

	busyServers   = "BusyServers"
	idleServers   = "IdleServers"
	totalAccesses = "Total Accesses"
	totalkBytes   = "Total kBytes"
	uptime        = "Uptime"
	scoreBoard    = "Scoreboard"
)

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) getServerStatus() (*serverStatus, 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 := parseResponse(resp.Body)

	if err != nil {
		return nil, fmt.Errorf("error on parsing response from %s : %v", req.URL, err)
	}

	return status, nil
}

func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
	resp, err := a.httpClient.Do(req)
	if err != nil {
		return nil, 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, nil
}

func parseResponse(r io.Reader) (*serverStatus, error) {
	s := bufio.NewScanner(r)
	var status serverStatus

	for s.Scan() {
		parts := strings.Split(s.Text(), ":")
		if len(parts) != 2 {
			continue
		}
		key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])

		switch key {
		default:
		case busyWorkers, idleWorkers:
			return nil, fmt.Errorf("found '%s', apache data", key)
		case busyServers:
			status.Servers.Busy = mustParseInt(value)
		case idleServers:
			status.Servers.Idle = mustParseInt(value)
		case totalAccesses:
			status.Total.Accesses = mustParseInt(value)
		case totalkBytes:
			status.Total.KBytes = mustParseInt(value)
		case uptime:
			status.Uptime = mustParseInt(value)
		case scoreBoard:
			status.Scoreboard = parseScoreboard(value)
		}
	}

	return &status, nil
}

func parseScoreboard(value string) *scoreboard {
	// Descriptions from https://blog.serverdensity.com/monitor-lighttpd/
	//
	// “.” = Opening the TCP connection (connect)
	// “C” = Closing the TCP connection if no other HTTP request will use it (close)
	// “E” = hard error
	// “k” = Keeping the TCP connection open for more HTTP requests from the same client to avoid the TCP handling overhead (keep-alive)
	// “r” = ReadAsMap the content of the HTTP request (read)
	// “R” = ReadAsMap the content of the HTTP request (read-POST)
	// “W” = Write the HTTP response to the socket (write)
	// “h” = Decide action to take with the request (handle-request)
	// “q” = Start of HTTP request (request-start)
	// “Q” = End of HTTP request (request-end)
	// “s” = Start of the HTTP request response (response-start)
	// “S” = End of the HTTP request response (response-end)
	// “_” Waiting for Connection (NOTE: not sure, copied the description from apache score board)

	var sb scoreboard
	for _, s := range strings.Split(value, "") {
		switch s {
		case "_":
			sb.Waiting++
		case ".":
			sb.Open++
		case "C":
			sb.Close++
		case "E":
			sb.HardError++
		case "k":
			sb.KeepAlive++
		case "r":
			sb.Read++
		case "R":
			sb.ReadPost++
		case "W":
			sb.Write++
		case "h":
			sb.HandleRequest++
		case "q":
			sb.RequestStart++
		case "Q":
			sb.RequestEnd++
		case "s":
			sb.ResponseStart++
		case "S":
			sb.ResponseEnd++
		}
	}

	return &sb
}

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

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