// Package grpcserver implements the gRPC server for Metacrypt.
package grpcserver

import (
	"context"
	"fmt"
	"log/slog"
	"strings"
	"sync"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	pb "git.wntrmute.dev/mc/metacrypt/gen/metacrypt/v2"
	"git.wntrmute.dev/mc/mcdsl/grpcserver"

	internacme "git.wntrmute.dev/mc/metacrypt/internal/acme"
	"git.wntrmute.dev/mc/metacrypt/internal/audit"
	"git.wntrmute.dev/mc/metacrypt/internal/auth"
	"git.wntrmute.dev/mc/metacrypt/internal/config"
	"git.wntrmute.dev/mc/metacrypt/internal/engine"
	"git.wntrmute.dev/mc/metacrypt/internal/policy"
	"git.wntrmute.dev/mc/metacrypt/internal/seal"
)

// GRPCServer wraps the mcdsl gRPC server and all service implementations.
type GRPCServer struct {
	cfg          *config.Config
	sealMgr      *seal.Manager
	auth         *auth.Authenticator
	policy       *policy.Engine
	engines      *engine.Registry
	audit        *audit.Logger
	logger       *slog.Logger
	srv          *grpcserver.Server
	acmeHandlers map[string]*internacme.Handler
	mu           sync.Mutex
}

// New creates a new GRPCServer.
func New(cfg *config.Config, sealMgr *seal.Manager, authenticator *auth.Authenticator,
	policyEngine *policy.Engine, engineRegistry *engine.Registry, auditLog *audit.Logger, logger *slog.Logger) *GRPCServer {
	return &GRPCServer{
		cfg:          cfg,
		sealMgr:      sealMgr,
		auth:         authenticator,
		policy:       policyEngine,
		engines:      engineRegistry,
		audit:        auditLog,
		logger:       logger,
		acmeHandlers: make(map[string]*internacme.Handler),
	}
}

// Start starts the gRPC server on cfg.Server.GRPCAddr.
func (s *GRPCServer) Start() error {
	if s.cfg.Server.GRPCAddr == "" {
		s.logger.Info("grpc_addr not configured, gRPC server disabled")
		return nil
	}

	opts := &grpcserver.Options{
		PreInterceptors:  []grpc.UnaryServerInterceptor{sealInterceptor(s.sealMgr, s.logger, sealRequiredMethods())},
		PostInterceptors: []grpc.UnaryServerInterceptor{auditInterceptor(s.audit)},
	}

	srv, err := grpcserver.New(
		s.cfg.Server.TLSCert,
		s.cfg.Server.TLSKey,
		s.auth,
		methodMap(),
		s.logger,
		opts,
	)
	if err != nil {
		return fmt.Errorf("grpc: create server: %w", err)
	}
	s.srv = srv

	pb.RegisterSystemServiceServer(srv.GRPCServer, &systemServer{s: s})
	pb.RegisterAuthServiceServer(srv.GRPCServer, &authServer{s: s})
	pb.RegisterEngineServiceServer(srv.GRPCServer, &engineServer{s: s})
	pb.RegisterPKIServiceServer(srv.GRPCServer, &pkiServer{s: s})
	pb.RegisterCAServiceServer(srv.GRPCServer, &caServer{s: s})
	pb.RegisterPolicyServiceServer(srv.GRPCServer, &policyServer{s: s})
	pb.RegisterBarrierServiceServer(srv.GRPCServer, &barrierServer{s: s})
	pb.RegisterUserServiceServer(srv.GRPCServer, &userServer{s: s})
	pb.RegisterACMEServiceServer(srv.GRPCServer, &acmeServer{s: s})
	pb.RegisterSSHCAServiceServer(srv.GRPCServer, &sshcaServer{s: s})
	pb.RegisterTransitServiceServer(srv.GRPCServer, &transitServer{s: s})

	return srv.Serve(s.cfg.Server.GRPCAddr)
}

// Shutdown gracefully stops the gRPC server.
func (s *GRPCServer) Shutdown() {
	if s.srv != nil {
		s.srv.Stop()
	}
}

// extractToken extracts the bearer token from gRPC metadata. Used by the
// auth service Logout handler which needs the raw token.
func extractToken(ctx context.Context) string {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return ""
	}
	vals := md.Get("authorization")
	if len(vals) == 0 {
		return ""
	}
	v := vals[0]
	if strings.HasPrefix(v, "Bearer ") {
		return strings.TrimPrefix(v, "Bearer ")
	}
	return v
}

// callerUsername returns the username from the token info in the context,
// or empty string if no token info is present.
func callerUsername(ctx context.Context) string {
	ti := auth.TokenInfoFromContext(ctx)
	if ti == nil {
		return ""
	}
	return ti.Username
}

// methodMap builds the mcdsl grpcserver.MethodMap for Metacrypt.
//
// Public methods require no authentication. Auth-required and admin-required
// methods are enforced by the mcdsl auth interceptor.
func methodMap() grpcserver.MethodMap {
	return grpcserver.MethodMap{
		Public: publicMethods(),

		AuthRequired: authRequiredMethods(),

		AdminRequired: adminRequiredMethods(),
	}
}

// publicMethods returns methods that require no authentication.
// These include system lifecycle RPCs (status, init, unseal), the login
// endpoint, and read-only PKI/CA/SSH CA endpoints that serve public
// certificates and keys.
func publicMethods() map[string]bool {
	return map[string]bool{
		// System lifecycle — always available.
		"/metacrypt.v2.SystemService/Status":  true,
		"/metacrypt.v2.SystemService/Init":    true,
		"/metacrypt.v2.SystemService/Unseal":  true,
		// Auth — login requires no existing token.
		"/metacrypt.v2.AuthService/Login":     true,
		// PKI read-only — public certificates.
		"/metacrypt.v2.PKIService/GetRootCert":   true,
		"/metacrypt.v2.PKIService/GetChain":       true,
		"/metacrypt.v2.PKIService/GetIssuerCert":  true,
		// CA read-only — public certificates and chains.
		"/metacrypt.v2.CAService/GetRoot":    true,
		"/metacrypt.v2.CAService/GetIssuer":  true,
		"/metacrypt.v2.CAService/GetChain":   true,
		// SSH CA — public key and key revocation list.
		"/metacrypt.v2.SSHCAService/GetCAPublicKey": true,
		"/metacrypt.v2.SSHCAService/GetKRL":         true,
	}
}

// authRequiredMethods returns methods that require a valid MCIAS token but
// not necessarily the admin role.
func authRequiredMethods() map[string]bool {
	return map[string]bool{
		"/metacrypt.v2.AuthService/Logout":    true,
		"/metacrypt.v2.AuthService/TokenInfo": true,
		"/metacrypt.v2.EngineService/ListMounts": true,
		"/metacrypt.v2.CAService/ListIssuers": true,
		"/metacrypt.v2.CAService/IssueCert":   true,
		"/metacrypt.v2.CAService/GetCert":     true,
		"/metacrypt.v2.CAService/ListCerts":   true,
		"/metacrypt.v2.CAService/RenewCert":   true,
		"/metacrypt.v2.CAService/SignCSR":      true,
		"/metacrypt.v2.UserService/Register":   true,
		"/metacrypt.v2.UserService/GetPublicKey": true,
		"/metacrypt.v2.UserService/ListUsers":  true,
		"/metacrypt.v2.UserService/Encrypt":    true,
		"/metacrypt.v2.UserService/Decrypt":    true,
		"/metacrypt.v2.UserService/ReEncrypt":  true,
		"/metacrypt.v2.UserService/RotateKey":  true,
		"/metacrypt.v2.ACMEService/CreateEAB":  true,
		// SSH CA — sign and read operations.
		"/metacrypt.v2.SSHCAService/SignHost":    true,
		"/metacrypt.v2.SSHCAService/SignUser":    true,
		"/metacrypt.v2.SSHCAService/GetProfile":  true,
		"/metacrypt.v2.SSHCAService/ListProfiles": true,
		"/metacrypt.v2.SSHCAService/GetCert":     true,
		"/metacrypt.v2.SSHCAService/ListCerts":   true,
		// Transit — data-plane operations.
		"/metacrypt.v2.TransitService/GetKey":       true,
		"/metacrypt.v2.TransitService/ListKeys":     true,
		"/metacrypt.v2.TransitService/Encrypt":      true,
		"/metacrypt.v2.TransitService/Decrypt":      true,
		"/metacrypt.v2.TransitService/Rewrap":       true,
		"/metacrypt.v2.TransitService/BatchEncrypt": true,
		"/metacrypt.v2.TransitService/BatchDecrypt": true,
		"/metacrypt.v2.TransitService/BatchRewrap":  true,
		"/metacrypt.v2.TransitService/Sign":         true,
		"/metacrypt.v2.TransitService/Verify":       true,
		"/metacrypt.v2.TransitService/Hmac":         true,
		"/metacrypt.v2.TransitService/GetPublicKey": true,
	}
}

// adminRequiredMethods returns methods that require a valid MCIAS token
// with the admin role.
func adminRequiredMethods() map[string]bool {
	return map[string]bool{
		"/metacrypt.v2.SystemService/Seal":         true,
		"/metacrypt.v2.EngineService/Mount":        true,
		"/metacrypt.v2.EngineService/Unmount":      true,
		"/metacrypt.v2.CAService/ImportRoot":       true,
		"/metacrypt.v2.CAService/CreateIssuer":     true,
		"/metacrypt.v2.CAService/DeleteIssuer":     true,
		"/metacrypt.v2.CAService/RevokeCert":       true,
		"/metacrypt.v2.CAService/DeleteCert":       true,
		"/metacrypt.v2.PolicyService/CreatePolicy": true,
		"/metacrypt.v2.PolicyService/ListPolicies": true,
		"/metacrypt.v2.PolicyService/GetPolicy":    true,
		"/metacrypt.v2.PolicyService/DeletePolicy": true,
		// User.
		"/metacrypt.v2.UserService/Provision":  true,
		"/metacrypt.v2.UserService/DeleteUser": true,
		"/metacrypt.v2.ACMEService/SetConfig":    true,
		"/metacrypt.v2.ACMEService/ListAccounts": true,
		"/metacrypt.v2.ACMEService/ListOrders":   true,
		"/metacrypt.v2.BarrierService/ListKeys":  true,
		"/metacrypt.v2.BarrierService/RotateMEK": true,
		"/metacrypt.v2.BarrierService/RotateKey":  true,
		"/metacrypt.v2.BarrierService/Migrate":    true,
		// SSH CA.
		"/metacrypt.v2.SSHCAService/CreateProfile": true,
		"/metacrypt.v2.SSHCAService/UpdateProfile": true,
		"/metacrypt.v2.SSHCAService/DeleteProfile": true,
		"/metacrypt.v2.SSHCAService/RevokeCert":    true,
		"/metacrypt.v2.SSHCAService/DeleteCert":    true,
		// Transit.
		"/metacrypt.v2.TransitService/CreateKey":       true,
		"/metacrypt.v2.TransitService/DeleteKey":       true,
		"/metacrypt.v2.TransitService/RotateKey":       true,
		"/metacrypt.v2.TransitService/UpdateKeyConfig": true,
		"/metacrypt.v2.TransitService/TrimKey":         true,
	}
}

// sealRequiredMethods returns the set of RPC full names that require the vault
// to be unsealed. This is used by the sealInterceptor PreInterceptor.
func sealRequiredMethods() map[string]bool {
	return map[string]bool{
		"/metacrypt.v2.AuthService/Login":          true,
		"/metacrypt.v2.AuthService/Logout":         true,
		"/metacrypt.v2.AuthService/TokenInfo":      true,
		"/metacrypt.v2.EngineService/Mount":        true,
		"/metacrypt.v2.EngineService/Unmount":      true,
		"/metacrypt.v2.EngineService/ListMounts":   true,
		"/metacrypt.v2.PKIService/GetRootCert":     true,
		"/metacrypt.v2.PKIService/GetChain":        true,
		"/metacrypt.v2.PKIService/GetIssuerCert":   true,
		"/metacrypt.v2.CAService/ImportRoot":       true,
		"/metacrypt.v2.CAService/GetRoot":          true,
		"/metacrypt.v2.CAService/CreateIssuer":     true,
		"/metacrypt.v2.CAService/DeleteIssuer":     true,
		"/metacrypt.v2.CAService/ListIssuers":      true,
		"/metacrypt.v2.CAService/GetIssuer":        true,
		"/metacrypt.v2.CAService/GetChain":         true,
		"/metacrypt.v2.CAService/IssueCert":        true,
		"/metacrypt.v2.CAService/GetCert":          true,
		"/metacrypt.v2.CAService/ListCerts":        true,
		"/metacrypt.v2.CAService/RenewCert":        true,
		"/metacrypt.v2.CAService/SignCSR":          true,
		"/metacrypt.v2.CAService/RevokeCert":       true,
		"/metacrypt.v2.CAService/DeleteCert":       true,
		"/metacrypt.v2.PolicyService/CreatePolicy": true,
		"/metacrypt.v2.PolicyService/ListPolicies": true,
		"/metacrypt.v2.PolicyService/GetPolicy":    true,
		"/metacrypt.v2.PolicyService/DeletePolicy": true,
		"/metacrypt.v2.UserService/Register":       true,
		"/metacrypt.v2.UserService/Provision":      true,
		"/metacrypt.v2.UserService/GetPublicKey":   true,
		"/metacrypt.v2.UserService/ListUsers":      true,
		"/metacrypt.v2.UserService/Encrypt":        true,
		"/metacrypt.v2.UserService/Decrypt":        true,
		"/metacrypt.v2.UserService/ReEncrypt":      true,
		"/metacrypt.v2.UserService/RotateKey":      true,
		"/metacrypt.v2.UserService/DeleteUser":     true,
		"/metacrypt.v2.ACMEService/CreateEAB":      true,
		"/metacrypt.v2.ACMEService/SetConfig":      true,
		"/metacrypt.v2.ACMEService/ListAccounts":   true,
		"/metacrypt.v2.ACMEService/ListOrders":     true,
		"/metacrypt.v2.BarrierService/ListKeys":    true,
		"/metacrypt.v2.BarrierService/RotateMEK":   true,
		"/metacrypt.v2.BarrierService/RotateKey":   true,
		"/metacrypt.v2.BarrierService/Migrate":     true,
		// SSH CA.
		"/metacrypt.v2.SSHCAService/GetCAPublicKey": true,
		"/metacrypt.v2.SSHCAService/SignHost":        true,
		"/metacrypt.v2.SSHCAService/SignUser":        true,
		"/metacrypt.v2.SSHCAService/CreateProfile":   true,
		"/metacrypt.v2.SSHCAService/UpdateProfile":   true,
		"/metacrypt.v2.SSHCAService/GetProfile":      true,
		"/metacrypt.v2.SSHCAService/ListProfiles":    true,
		"/metacrypt.v2.SSHCAService/DeleteProfile":   true,
		"/metacrypt.v2.SSHCAService/GetCert":         true,
		"/metacrypt.v2.SSHCAService/ListCerts":       true,
		"/metacrypt.v2.SSHCAService/RevokeCert":      true,
		"/metacrypt.v2.SSHCAService/DeleteCert":      true,
		"/metacrypt.v2.SSHCAService/GetKRL":          true,
		// Transit.
		"/metacrypt.v2.TransitService/CreateKey":       true,
		"/metacrypt.v2.TransitService/DeleteKey":       true,
		"/metacrypt.v2.TransitService/GetKey":          true,
		"/metacrypt.v2.TransitService/ListKeys":        true,
		"/metacrypt.v2.TransitService/RotateKey":       true,
		"/metacrypt.v2.TransitService/UpdateKeyConfig": true,
		"/metacrypt.v2.TransitService/TrimKey":         true,
		"/metacrypt.v2.TransitService/Encrypt":         true,
		"/metacrypt.v2.TransitService/Decrypt":         true,
		"/metacrypt.v2.TransitService/Rewrap":          true,
		"/metacrypt.v2.TransitService/BatchEncrypt":    true,
		"/metacrypt.v2.TransitService/BatchDecrypt":    true,
		"/metacrypt.v2.TransitService/BatchRewrap":     true,
		"/metacrypt.v2.TransitService/Sign":            true,
		"/metacrypt.v2.TransitService/Verify":          true,
		"/metacrypt.v2.TransitService/Hmac":            true,
		"/metacrypt.v2.TransitService/GetPublicKey":    true,
		// Seal itself requires unsealed state.
		"/metacrypt.v2.SystemService/Seal": true,
	}
}
