package authorize

import (
	"context"
	"fmt"
	"net/url"

	googlegrpc "google.golang.org/grpc"

	"github.com/pomerium/pomerium/authorize/evaluator"
	"github.com/pomerium/pomerium/authorize/internal/store"
	"github.com/pomerium/pomerium/config"
	"github.com/pomerium/pomerium/internal/httputil"
	"github.com/pomerium/pomerium/pkg/grpc"
	"github.com/pomerium/pomerium/pkg/grpc/databroker"
	"github.com/pomerium/pomerium/pkg/hpke"
	"github.com/pomerium/pomerium/pkg/protoutil"
)

var outboundGRPCConnection = new(grpc.CachedOutboundGRPClientConn)

type authorizeState struct {
	sharedKey                  []byte
	evaluator                  *evaluator.Evaluator
	dataBrokerClientConnection *googlegrpc.ClientConn
	dataBrokerClient           databroker.DataBrokerServiceClient
	auditEncryptor             *protoutil.Encryptor
	sessionStore               *config.SessionStore
	hpkePrivateKey             *hpke.PrivateKey
	authenticateKeyFetcher     hpke.KeyFetcher
}

func newAuthorizeStateFromConfig(cfg *config.Config, store *store.Store) (*authorizeState, error) {
	if err := validateOptions(cfg.Options); err != nil {
		return nil, fmt.Errorf("authorize: bad options: %w", err)
	}

	state := new(authorizeState)

	var err error

	state.evaluator, err = newPolicyEvaluator(cfg.Options, store)
	if err != nil {
		return nil, fmt.Errorf("authorize: failed to update policy with options: %w", err)
	}

	state.sharedKey, err = cfg.Options.GetSharedKey()
	if err != nil {
		return nil, err
	}

	sharedKey, err := cfg.Options.GetSharedKey()
	if err != nil {
		return nil, err
	}

	cc, err := outboundGRPCConnection.Get(context.Background(), &grpc.OutboundOptions{
		OutboundPort:   cfg.OutboundPort,
		InstallationID: cfg.Options.InstallationID,
		ServiceName:    cfg.Options.Services,
		SignedJWTKey:   sharedKey,
	})
	if err != nil {
		return nil, fmt.Errorf("authorize: error creating databroker connection: %w", err)
	}
	state.dataBrokerClientConnection = cc
	state.dataBrokerClient = databroker.NewDataBrokerServiceClient(cc)

	auditKey, err := cfg.Options.GetAuditKey()
	if err != nil {
		return nil, fmt.Errorf("authorize: invalid audit key: %w", err)
	}
	if auditKey != nil {
		state.auditEncryptor = protoutil.NewEncryptor(auditKey)
	}

	state.sessionStore, err = config.NewSessionStore(cfg.Options)
	if err != nil {
		return nil, fmt.Errorf("authorize: invalid session store: %w", err)
	}

	authenticateURL, err := cfg.Options.GetAuthenticateURL()
	if err != nil {
		return nil, fmt.Errorf("authorize: invalid authenticate service url: %w", err)
	}

	state.hpkePrivateKey = hpke.DerivePrivateKey(sharedKey)

	jwksURL := authenticateURL.ResolveReference(&url.URL{
		Path: "/.well-known/pomerium/jwks.json",
	}).String()
	transport := httputil.GetInsecureTransport()
	ok, err := cfg.WillHaveCertificateForServerName(authenticateURL.Hostname())
	if err != nil {
		return nil, fmt.Errorf("authorize: error determining if authenticate service will have a certificate name: %w", err)
	}
	if ok {
		transport, err = config.GetTLSClientTransport(cfg)
		if err != nil {
			return nil, fmt.Errorf("authorize: get tls client config: %w", err)
		}
	}
	state.authenticateKeyFetcher = hpke.NewKeyFetcher(jwksURL, transport)

	return state, nil
}