package lokigrpc

import (
	"context"
	"crypto/tls"
	"errors"

	grpcretry "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/grafana/grafana/pkg/components/loki/logproto"
)

// Client is a gRPC-based Loki client implementation.
type Client struct {
	client logproto.PusherClient
	conn   *grpc.ClientConn
	opts   []grpc.DialOption
	cfg    Config
}

// NewClient instantiates a new Client.
func NewClient(cfg Config, opts ...grpc.DialOption) (*Client, error) {
	w := &Client{
		opts: opts,
		cfg:  cfg,
	}

	return w, w.init()
}

// Write pushes a new request with the given streams through
// the attached gRPC connection.
func (c *Client) Write(streams []logproto.Stream) (err error) {
	pushRequest := &logproto.PushRequest{
		Streams: streams,
	}

	ctx, cancel := c.timeoutCtx()
	defer cancel()

	if len(c.cfg.TenantID) > 0 {
		ctx = injectOrgID(ctx, c.cfg.TenantID)
	}

	_, err = c.client.Push(ctx, pushRequest)
	return err
}

// Close closes the attached gRPC connection.
func (c *Client) Close() error {
	return c.conn.Close()
}

func (c *Client) init() error {
	if len(c.cfg.URL) == 0 {
		return errors.New("cfg must have Loki url")
	}

	opts := append(c.opts, c.grpcTLSOption(), c.grpcRetryOption())
	conn, err := grpc.NewClient(c.cfg.URL, opts...)
	if err != nil {
		return err
	}

	c.conn = conn
	c.client = logproto.NewPusherClient(conn)
	return nil
}

func (c *Client) grpcTLSOption() grpc.DialOption {
	if c.cfg.TLSDisabled {
		return grpc.WithTransportCredentials(insecure.NewCredentials())
	}

	config := &tls.Config{InsecureSkipVerify: false}
	return grpc.WithTransportCredentials(credentials.NewTLS(config))
}

func (c *Client) grpcRetryOption() grpc.DialOption {
	return grpc.WithUnaryInterceptor(
		grpcretry.UnaryClientInterceptor(
			grpcretry.WithMax(c.cfg.Retries),
		),
	)
}

func (c *Client) timeoutCtx() (context.Context, context.CancelFunc) {
	if c.cfg.Timeout > 0 {
		return context.WithTimeout(context.Background(), c.cfg.Timeout)
	}

	return context.WithCancel(context.Background())
}
