package secretscan

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/google/uuid"
)

var errWebHookURL = errors.New("webhook url must be https")

// webHookClient is a client for sending leak notifications.
type webHookClient struct {
	httpClient *http.Client
	version    string
	url        string
}

var ErrInvalidWebHookStatusCode = errors.New("invalid webhook status code")

func newWebHookClient(url, version string, dev bool) (*webHookClient, error) {
	if !strings.HasPrefix(url, "https://") && !dev {
		return nil, errWebHookURL
	}

	return &webHookClient{
		version: version,
		url:     url,
		httpClient: &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					Renegotiation: tls.RenegotiateFreelyAsClient,
				},
				Proxy: http.ProxyFromEnvironment,
				DialContext: (&net.Dialer{
					Timeout:   timeout,
					KeepAlive: 15 * time.Second,
				}).DialContext,
				TLSHandshakeTimeout:   10 * time.Second,
				ExpectContinueTimeout: 1 * time.Second,
				MaxIdleConns:          100,
				IdleConnTimeout:       30 * time.Second,
			},
			Timeout: time.Second * 30,
		},
	}, nil
}

func (wClient *webHookClient) Notify(ctx context.Context,
	token *Token, tokenName string, revoked bool,
) error {
	revokedMsg := ""
	if revoked {
		revokedMsg = " Grafana has revoked this token"
	}

	// create request body
	values := map[string]any{
		"alert_uid":                uuid.NewString(),
		"title":                    "SecretScan Alert: Grafana Token leaked",
		"state":                    "alerting",
		"link_to_upstream_details": token.URL,
		"message": "Token of type " +
			token.Type + " with name " +
			tokenName + " has been publicly exposed in " +
			token.URL + "." + revokedMsg,
	}

	jsonValue, err := json.Marshal(values)
	if err != nil {
		return fmt.Errorf("%s: %w", "failed to marshal webhook request", err)
	}

	// Build URL
	// Create request for secretscan server
	req, err := http.NewRequestWithContext(ctx, http.MethodPost,
		wClient.url, bytes.NewReader(jsonValue))
	if err != nil {
		return fmt.Errorf("%s: %w", "failed to make http request", err)
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", "grafana-secretscan-webhook-client/"+wClient.version)

	// make http POST request to check for leaked tokens.
	resp, err := wClient.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("%s: %w", "failed to webhook request", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("%w. status code %s", ErrInvalidWebHookStatusCode, resp.Status)
	}

	return nil
}
