/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
:                                                                               :
:   █▀ █ █▀▀   ·   Blazing-fast pentesting suite                                :
:   ▄█ █ █▀    ·   BSD 3-Clause License                                         :
:                                                                               :
:   (c) 2022-2026 vmfunc, xyzeva,                                               :
:                 lunchcat alumni & contributors                                :
:                                                                               :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/

package notify

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

	"github.com/dropalldatabases/sif/internal/finding"
	"github.com/dropalldatabases/sif/internal/httpx"
)

// contentTypeJSON is the body type every provider POSTs; all four speak json.
const contentTypeJSON = "application/json"

// messageHeader prefixes the rendered finding block. kept terse - chat sinks
// truncate, so the count and lead-in carry the signal.
const messageHeader = "sif found %d finding(s):"

// renderFindings turns a batch into a single plain-text block, one finding per
// line in the same "[severity] target module title" shape as the -silent sink so
// a reader sees identical lines across stdout and chat. a strings.Builder keeps
// the per-line concat to one allocation path.
func renderFindings(findings []finding.Finding) string {
	var b strings.Builder
	fmt.Fprintf(&b, messageHeader, len(findings))
	b.WriteByte('\n')
	for i := 0; i < len(findings); i++ {
		b.WriteString(findings[i].Line())
		b.WriteByte('\n')
	}
	return b.String()
}

// postJSON marshals payload and POSTs it to url through the shared client. it
// drains+closes the response so the conn returns to httpx's pool, and treats any
// non-2xx as a delivery failure so a 4xx from a bad webhook surfaces loudly.
func postJSON(ctx context.Context, client *http.Client, url string, payload any) error {
	body, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("marshal payload: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("build request: %w", err)
	}
	req.Header.Set("Content-Type", contentTypeJSON)

	resp, err := client.Do(req) //nolint:bodyclose // drained and closed via httpx.DrainClose
	if err != nil {
		return fmt.Errorf("post: %w", err)
	}
	defer httpx.DrainClose(resp)

	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		return fmt.Errorf("unexpected status %d", resp.StatusCode)
	}
	return nil
}
