package grpcserver

import (
	"context"
	"crypto/subtle"
	"fmt"
	"strings"

	"git3.techno-world.net/lrosales/broad-announce/internal/pipeline"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// authenticate validates the API key from gRPC metadata and returns the
// corresponding SourceConfig. It is the gRPC equivalent of the HTTP
// X-BA-Signature HMAC check.
//
// Metadata key: "authorization" → "Bearer <company_id>:<source_id>:<secret>"
//
// Returns codes.Unauthenticated on any failure.
func authenticate(ctx context.Context, sources map[string]pipeline.SourceConfig) (*pipeline.SourceConfig, error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, status.Error(codes.Unauthenticated, "missing metadata")
	}
	// Support both canonical "authorization" and " Authorization".
	authVals := md.Get("authorization")
	if len(authVals) == 0 {
		authVals = md.Get("Authorization")
	}
	if len(authVals) == 0 || authVals[0] == "" {
		return nil, status.Error(codes.Unauthenticated, "missing authorization header")
	}

	raw := strings.TrimPrefix(authVals[0], "Bearer ")
	if raw == authVals[0] {
		return nil, status.Error(codes.Unauthenticated, "authorization must use Bearer scheme")
	}

	key := parseAPIKey(raw)
	if key == nil {
		return nil, status.Error(codes.Unauthenticated, "malformed API key")
	}

	src, ok := sources[key.SourceKey()]
	if !ok {
		return nil, status.Error(codes.Unauthenticated,
			fmt.Sprintf("no such source %s/%s", key.CompanyID, key.SourceID))
	}

	// Verify the secret. The sources map stores the full raw API key as the
	// secret for env-based auth. Constant-time compare to avoid timing leaks.
	want := key.CompanyID + ":" + key.SourceID + ":" + string(src.HMACSecret)
	if subtle.ConstantTimeCompare([]byte(raw), []byte(want)) != 1 {
		return nil, status.Error(codes.Unauthenticated, "invalid API key")
	}

	return &src, nil
}

// apiKey represents a parsed API key in "<company_id>:<source_id>:<secret>" form.
type apiKey struct {
	CompanyID string
	SourceID  string
	Secret    string
}

func parseAPIKey(raw string) *apiKey {
	parts := strings.SplitN(raw, ":", 3)
	if len(parts) != 3 {
		return nil
	}
	return &apiKey{CompanyID: parts[0], SourceID: parts[1], Secret: parts[2]}
}

func (k *apiKey) SourceKey() string { return k.CompanyID + ":" + k.SourceID }
