package main

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	"github.com/hashicorp/go-hclog"
	plugin "github.com/hashicorp/go-plugin"
	"gopkg.in/yaml.v3"

	"github.com/crowdsecurity/crowdsec/pkg/csplugin"
	"github.com/crowdsecurity/crowdsec/pkg/protobufs"
)

var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{
	Name:       "splunk-plugin",
	Level:      hclog.LevelFromString("INFO"),
	Output:     os.Stderr,
	JSONFormat: true,
})

type PluginConfig struct {
	Name     string `yaml:"name"`
	URL      string `yaml:"url"`
	Token    string `yaml:"token"`
	LogLevel string `yaml:"log_level"`
}

type Splunk struct {
	protobufs.UnimplementedNotifierServer
	PluginConfigByName map[string]PluginConfig
	Client             http.Client
}

type Payload struct {
	Event string `json:"event"`
}

func (s *Splunk) Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) {
	name := notification.GetName()
	cfg, ok := s.PluginConfigByName[name]

	if !ok {
		return &protobufs.Empty{}, fmt.Errorf("splunk invalid config name %s", name)
	}

	if cfg.LogLevel != "" {
		logger.SetLevel(hclog.LevelFromString(cfg.LogLevel))
	}

	logger.Info(fmt.Sprintf("received notify signal for %s config", name))

	p := Payload{Event: notification.GetText()}

	data, err := json.Marshal(p)
	if err != nil {
		return &protobufs.Empty{}, err
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, cfg.URL, strings.NewReader(string(data)))
	if err != nil {
		return &protobufs.Empty{}, err
	}

	req.Header.Add("Authorization", fmt.Sprintf("Splunk %s", cfg.Token))
	logger.Debug(fmt.Sprintf("posting event %s to %s", string(data), req.URL))

	resp, err := s.Client.Do(req)
	if err != nil {
		return &protobufs.Empty{}, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		content, err := io.ReadAll(resp.Body)
		if err != nil {
			return &protobufs.Empty{}, fmt.Errorf("got non 200 response and failed to read error %w", err)
		}

		return &protobufs.Empty{}, fmt.Errorf("got non 200 response %s", string(content))
	}

	respData, err := io.ReadAll(resp.Body)
	if err != nil {
		return &protobufs.Empty{}, fmt.Errorf("failed to read response body got error %w", err)
	}

	logger.Debug(fmt.Sprintf("got response %s", string(respData)))

	return &protobufs.Empty{}, nil
}

func (s *Splunk) Configure(_ context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
	d := PluginConfig{}
	err := yaml.Unmarshal(config.GetConfig(), &d)
	s.PluginConfigByName[d.Name] = d
	logger.Debug(fmt.Sprintf("Splunk plugin '%s' use URL '%s'", d.Name, d.URL))

	return &protobufs.Empty{}, err
}

func main() {
	handshake := plugin.HandshakeConfig{
		ProtocolVersion:  1,
		MagicCookieKey:   "CROWDSEC_PLUGIN_KEY",
		MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),
	}

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	sp := &Splunk{PluginConfigByName: make(map[string]PluginConfig), Client: *client}
	plugin.Serve(&plugin.ServeConfig{
		HandshakeConfig: handshake,
		Plugins: map[string]plugin.Plugin{
			"splunk": &csplugin.NotifierPlugin{
				Impl: sp,
			},
		},
		GRPCServer: plugin.DefaultGRPCServer,
		Logger:     logger,
	})
}
