// Package client implements a gRPC client for the authorization service.
package client

import (
	"context"
	"net/http"

	"github.com/pomerium/pomerium/internal/grpc/authorize"
	pb "github.com/pomerium/pomerium/internal/grpc/authorize"
	"github.com/pomerium/pomerium/internal/telemetry/trace"

	"google.golang.org/grpc"
)

// Authorizer provides the authorize service interface
type Authorizer interface {
	// Authorize takes a route and user session and returns whether the
	// request is valid per access policy
	Authorize(ctx context.Context, user string, r *http.Request) (*pb.IsAuthorizedReply, error)
	// Close closes the auth connection if any.
	Close() error
}

// Client is a gRPC implementation of an authenticator (authorize client)
type Client struct {
	conn   *grpc.ClientConn
	client pb.AuthorizerClient
}

// New returns a new authorize service client.
func New(conn *grpc.ClientConn) (p *Client, err error) {
	return &Client{conn: conn, client: pb.NewAuthorizerClient(conn)}, nil
}

// Authorize takes a route and user session and returns whether the
// request is valid per access policy
func (c *Client) Authorize(ctx context.Context, user string, r *http.Request) (*pb.IsAuthorizedReply, error) {
	ctx, span := trace.StartSpan(ctx, "grpc.authorize.client.Authorize")
	defer span.End()
	in := &pb.IsAuthorizedRequest{
		UserToken:         user,
		RequestHost:       r.Host,
		RequestMethod:     r.Method,
		RequestHeaders:    cloneHeaders(r.Header),
		RequestRemoteAddr: r.RemoteAddr,
		RequestRequestUri: r.RequestURI,
		RequestUrl:        r.URL.String(),
	}
	return c.client.IsAuthorized(ctx, in)
}

// Close tears down the ClientConn and all underlying connections.
func (c *Client) Close() error {
	return c.conn.Close()
}

type protoHeader map[string]*authorize.IsAuthorizedRequest_Headers

func cloneHeaders(in http.Header) protoHeader {
	out := make(protoHeader, len(in))
	for key, values := range in {
		newValues := make([]string, len(values))
		copy(newValues, values)
		out[key] = &authorize.IsAuthorizedRequest_Headers{Value: newValues}
	}
	return out
}