package main

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"net/http"
	"os"
	"strings"
	"time"

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

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

type PluginConfig struct {
	Name       string  `yaml:"name"`
	CustomerID string  `yaml:"customer_id"`
	SharedKey  string  `yaml:"shared_key"`
	LogType    string  `yaml:"log_type"`
	LogLevel   *string `yaml:"log_level"`
}

type SentinelPlugin struct {
	protobufs.UnimplementedNotifierServer
	PluginConfigByName map[string]PluginConfig
}

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

func (s *SentinelPlugin) getAuthorizationHeader(now string, length int, pluginName string) (string, error) {
	xHeaders := "X-Ms-Date:" + now

	stringToHash := fmt.Sprintf("POST\n%d\napplication/json\n%s\n/api/logs", length, xHeaders)
	decodedKey, _ := base64.StdEncoding.DecodeString(s.PluginConfigByName[pluginName].SharedKey)

	h := hmac.New(sha256.New, decodedKey)
	h.Write([]byte(stringToHash))

	encodedHash := base64.StdEncoding.EncodeToString(h.Sum(nil))
	authorization := "SharedKey " + s.PluginConfigByName[pluginName].CustomerID + ":" + encodedHash

	logger.Trace("authorization header", "header", authorization)

	return authorization, nil
}

func (s *SentinelPlugin) Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) {
	if _, ok := s.PluginConfigByName[notification.Name]; !ok {
		return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
	}
	cfg := s.PluginConfigByName[notification.Name]

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

	logger.Info("received notification for sentinel config", "name", notification.Name)

	url := fmt.Sprintf("https://%s.ods.opinsights.azure.com/api/logs?api-version=2016-04-01", s.PluginConfigByName[notification.Name].CustomerID)
	body := strings.NewReader(notification.Text)

	//Cannot use time.RFC1123 as azure wants GMT, not UTC
	now := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")

	authorization, err := s.getAuthorizationHeader(now, len(notification.Text), notification.Name)
	if err != nil {
		return &protobufs.Empty{}, err
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
	if err != nil {
		logger.Error("failed to create request", "error", err)
		return &protobufs.Empty{}, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Log-Type", s.PluginConfigByName[notification.Name].LogType)
	req.Header.Set("Authorization", authorization)
	req.Header.Set("X-Ms-Date", now)

	client := &http.Client{}
	resp, err := client.Do(req.WithContext(ctx))
	if err != nil {
		logger.Error("failed to send request", "error", err)
		return &protobufs.Empty{}, err
	}
	defer resp.Body.Close()
	logger.Debug("sent notification to sentinel", "status", resp.Status)

	if resp.StatusCode != http.StatusOK {
		return &protobufs.Empty{}, fmt.Errorf("failed to send notification to sentinel: %s", resp.Status)
	}

	return &protobufs.Empty{}, nil
}

func (s *SentinelPlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
	d := PluginConfig{}
	err := yaml.Unmarshal(config.Config, &d)
	s.PluginConfigByName[d.Name] = d
	return &protobufs.Empty{}, err
}

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

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