// Package grpcserver implements the MCR gRPC admin API server.
//
// It delegates TLS, logging, and auth/admin interceptors to the mcdsl
// grpcserver package. MCR-specific business logic lives in the service
// handler files (registry.go, policy.go, audit.go, admin.go).
package grpcserver

import (
	"log"
	"log/slog"
	"net"
	"sync"

	mcdslauth "git.wntrmute.dev/mc/mcdsl/auth"
	mcdslgrpc "git.wntrmute.dev/mc/mcdsl/grpcserver"

	pb "git.wntrmute.dev/mc/mcr/gen/mcr/v1"
	"git.wntrmute.dev/mc/mcr/internal/db"
	"git.wntrmute.dev/mc/mcr/internal/gc"
	"git.wntrmute.dev/mc/mcr/internal/policy"
)

// AuditFunc is a callback for recording audit events. It follows the same
// signature as db.WriteAuditEvent but without an error return -- audit
// failures should not block request processing.
type AuditFunc func(eventType, actorID, repository, digest, ip string, details map[string]string)

// Deps holds the dependencies injected into the gRPC server.
type Deps struct {
	DB            *db.DB
	Authenticator *mcdslauth.Authenticator
	Engine        PolicyReloader
	AuditFn       AuditFunc
	Collector     *gc.Collector
}

// PolicyReloader can reload policy rules from a store.
type PolicyReloader interface {
	Reload(store policy.RuleStore) error
}

// GCStatus tracks the current state of garbage collection for the gRPC server.
type GCStatus struct {
	mu      sync.Mutex
	running bool
	lastRun *gcLastRun
}

type gcLastRun struct {
	StartedAt    string
	CompletedAt  string
	BlobsRemoved int
	BytesFreed   int64
}

// Server wraps a mcdsl grpcserver.Server with MCR-specific configuration.
type Server struct {
	srv      *mcdslgrpc.Server
	deps     Deps
	gcStatus *GCStatus
}

// New creates a configured gRPC server that delegates TLS setup, logging,
// and auth/admin interceptors to the mcdsl grpcserver package.
//
// If certFile or keyFile is empty, TLS is skipped (for testing only).
func New(certFile, keyFile string, deps Deps, logger *slog.Logger) (*Server, error) {
	srv, err := mcdslgrpc.New(certFile, keyFile, deps.Authenticator, methodMap(), logger, nil)
	if err != nil {
		return nil, err
	}

	// The JSON codec is registered globally via init() in gen/mcr/v1/codec.go.
	// The client must use grpc.ForceCodecV2(mcrv1.JSONCodec{}) to match.
	_ = pb.JSONCodec{} // ensure the gen/mcr/v1 init() runs (codec registration)

	gcStatus := &GCStatus{}

	s := &Server{srv: srv, deps: deps, gcStatus: gcStatus}

	// Register all services.
	pb.RegisterRegistryServiceServer(srv.GRPCServer, &registryService{
		db:        deps.DB,
		collector: deps.Collector,
		gcStatus:  gcStatus,
		auditFn:   deps.AuditFn,
	})
	pb.RegisterPolicyServiceServer(srv.GRPCServer, &policyService{
		db:      deps.DB,
		engine:  deps.Engine,
		auditFn: deps.AuditFn,
	})
	pb.RegisterAuditServiceServer(srv.GRPCServer, &auditService{
		db: deps.DB,
	})
	pb.RegisterAdminServiceServer(srv.GRPCServer, &adminService{})

	return s, nil
}

// Serve starts the gRPC server on the given listener.
func (s *Server) Serve(lis net.Listener) error {
	log.Printf("grpc server listening on %s", lis.Addr())
	return s.srv.GRPCServer.Serve(lis)
}

// GracefulStop gracefully stops the gRPC server.
func (s *Server) GracefulStop() {
	s.srv.Stop()
}

// GRPCServer returns the underlying grpc.Server for testing.
func (s *Server) GRPCServer() *mcdslgrpc.Server {
	return s.srv
}
