package runtimegrpc

import (
	"context"
	"crypto/sha256"
	"encoding/binary"
	"sync"
	"time"

	identitysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/identityv1"
)

const (
	identityBurstCacheCapacity = 128
	identityExpirySafetyMargin = 5 * time.Second
)

// identityBurstCache reuses the exact opaque delegation that Runtime already
// issued for one bearer, AppInstallation and capability set. Reuse never
// exceeds the delegation's signed lifetime (minus a safety margin), the raw
// bearer is never retained, and every data RPC still performs Runtime's normal
// delegation, AuthEpoch, SecurityEpoch, session and PermissionFloor admission.
type identityBurstCache struct {
	mu        sync.Mutex
	entries   map[[sha256.Size]byte]identityBurstEntry
	inFlight  map[[sha256.Size]byte]*identityBurstCall
	now       func() time.Time
	capacity  int
	authority [sha256.Size]byte
}

// identityExchangeCoordinator owns the bounded, exact-capability delegation
// closure shared by every Runtime client in one L1 process. Runtime clients use
// separate gRPC connections, but share delegations only inside the exact
// Runtime target and workload-credential authority captured at construction.
type identityExchangeCoordinator struct {
	bursts *identityBurstCache
}

func newIdentityExchangeCoordinator(config RuntimeClientConfig) *identityExchangeCoordinator {
	return &identityExchangeCoordinator{bursts: newIdentityBurstCache(runtimeIdentityAuthority(config))}
}

func (coordinator *identityExchangeCoordinator) exchange(
	ctx context.Context,
	bearer string,
	request identitysemantic.Request,
	exchange func(context.Context, string, identitysemantic.Request) (identitysemantic.Response, error),
) (identitysemantic.Response, error) {
	if coordinator == nil {
		return exchange(ctx, bearer, request)
	}
	return coordinator.bursts.exchange(ctx, bearer, request, exchange)
}

type identityBurstEntry struct {
	response identitysemantic.Response
	reuseTo  time.Time
}

type identityBurstCall struct {
	done     chan struct{}
	response identitysemantic.Response
	err      error
}

func newIdentityBurstCache(authorities ...[sha256.Size]byte) *identityBurstCache {
	var authority [sha256.Size]byte
	if len(authorities) > 0 {
		authority = authorities[0]
	}
	return &identityBurstCache{
		entries:  make(map[[sha256.Size]byte]identityBurstEntry),
		inFlight: make(map[[sha256.Size]byte]*identityBurstCall),
		now:      time.Now, capacity: identityBurstCacheCapacity, authority: authority,
	}
}

func (cache *identityBurstCache) exchange(
	ctx context.Context,
	bearer string,
	request identitysemantic.Request,
	exchange func(context.Context, string, identitysemantic.Request) (identitysemantic.Response, error),
) (identitysemantic.Response, error) {
	if exchange == nil {
		return identitysemantic.Response{}, &identitysemantic.Failure{Category: identitysemantic.FailureUnavailable}
	}
	if cache == nil {
		return exchange(ctx, bearer, request)
	}
	key := identityBurstKey(cache.authority, bearer, request)
	now := cache.now().UTC()

	cache.mu.Lock()
	if entry, ok := cache.entries[key]; ok {
		if now.Before(entry.reuseTo) {
			response := entry.response
			cache.mu.Unlock()
			return response, nil
		}
		delete(cache.entries, key)
	}
	if call, ok := cache.inFlight[key]; ok {
		cache.mu.Unlock()
		select {
		case <-ctx.Done():
			return identitysemantic.Response{}, ctx.Err()
		case <-call.done:
			return call.response, call.err
		}
	}
	call := &identityBurstCall{done: make(chan struct{})}
	cache.inFlight[key] = call
	cache.mu.Unlock()

	response, err := exchange(ctx, bearer, request)
	now = cache.now().UTC()

	cache.mu.Lock()
	delete(cache.inFlight, key)
	call.response, call.err = response, err
	if err == nil {
		authorityExpiry := time.Unix(response.ExpiresAtUnixSecond, 0).UTC().Add(-identityExpirySafetyMargin)
		reuseTo := authorityExpiry
		if now.Before(reuseTo) {
			cache.makeRoom(now)
			cache.entries[key] = identityBurstEntry{response: response, reuseTo: reuseTo}
		}
	}
	close(call.done)
	cache.mu.Unlock()
	return response, err
}

func (cache *identityBurstCache) makeRoom(now time.Time) {
	for key, entry := range cache.entries {
		if !now.Before(entry.reuseTo) {
			delete(cache.entries, key)
		}
	}
	if len(cache.entries) < cache.capacity {
		return
	}
	var oldestKey [sha256.Size]byte
	var oldest time.Time
	for key, entry := range cache.entries {
		if oldest.IsZero() || entry.reuseTo.Before(oldest) {
			oldestKey, oldest = key, entry.reuseTo
		}
	}
	delete(cache.entries, oldestKey)
}

func runtimeIdentityAuthority(config RuntimeClientConfig) [sha256.Size]byte {
	hash := sha256.New()
	writeIdentityBurstKeyPart(hash, config.Target)
	writeIdentityBurstKeyPart(hash, config.WorkloadToken)
	writeIdentityBurstKeyPart(hash, config.ServerName)
	writeIdentityBurstKeyPart(hash, config.ClientCertificateFile)
	var authority [sha256.Size]byte
	copy(authority[:], hash.Sum(nil))
	return authority
}

func identityBurstKey(authority [sha256.Size]byte, bearer string, request identitysemantic.Request) [sha256.Size]byte {
	hash := sha256.New()
	_, _ = hash.Write(authority[:])
	writeIdentityBurstKeyPart(hash, bearer)
	writeIdentityBurstKeyPart(hash, request.AppInstallationID)
	for _, capability := range request.RequestedCapabilities {
		writeIdentityBurstKeyPart(hash, capability)
	}
	var key [sha256.Size]byte
	copy(key[:], hash.Sum(nil))
	return key
}

type identityBurstKeyWriter interface {
	Write([]byte) (int, error)
}

func writeIdentityBurstKeyPart(writer identityBurstKeyWriter, value string) {
	var length [8]byte
	binary.BigEndian.PutUint64(length[:], uint64(len(value)))
	_, _ = writer.Write(length[:])
	_, _ = writer.Write([]byte(value))
}
