package telegram

import (
	
	
	

	

	
	
	
)

// Single key because help.getCdnConfig has no request params.
const helpGetCDNConfigSingleflightKey = "help.getCdnConfig"

type cdnKeyEntry struct {
	dcID int
	key  *rsa.PublicKey
}

type fetchedCDNKeys struct {
	all  []exchange.PublicKey
	byDC map[int][]exchange.PublicKey
}

func ( []exchange.PublicKey) []exchange.PublicKey {
	return append([]exchange.PublicKey(nil), ...)
}

func (,  []exchange.PublicKey) []exchange.PublicKey {
	if len() == 0 && len() == 0 {
		return nil
	}

	 := make([]exchange.PublicKey, 0, len()+len())
	 := make(map[int64]struct{}, len()+len())
	 := func( []exchange.PublicKey) {
		for ,  := range  {
			 := .Fingerprint()
			if ,  := [];  {
				continue
			}
			[] = struct{}{}
			 = append(, )
		}
	}

	// Prefer primary keyset order and use fallback only for missing fingerprints.
	()
	()
	return 
}

func ( ...tg.CDNPublicKey) ([]cdnKeyEntry, error) {
	 := make([]cdnKeyEntry, 0, len())

	for ,  := range  {
		,  := pem.Decode([]byte(.PublicKey))
		if  == nil {
			continue
		}

		,  := crypto.ParseRSA(.Bytes)
		if  != nil {
			return nil, errors.Wrap(, "parse RSA from PEM")
		}

		 = append(, cdnKeyEntry{
			dcID: .DCID,
			key:  ,
		})
	}

	return , nil
}

func ( []cdnKeyEntry) fetchedCDNKeys {
	 := fetchedCDNKeys{
		all:  make([]exchange.PublicKey, 0, len()),
		byDC: make(map[int][]exchange.PublicKey),
	}

	 := make(map[int64]struct{}, len())
	 := make(map[int]map[int64]struct{})

	for ,  := range  {
		 := exchange.PublicKey{RSA: .key}
		 := .Fingerprint()

		if ,  := []; ! {
			[] = struct{}{}
			.all = append(.all, )
		}

		,  := [.dcID]
		if ! {
			 = map[int64]struct{}{}
			[.dcID] = 
		}
		if ,  := [];  {
			continue
		}
		[] = struct{}{}
		.byDC[.dcID] = append(.byDC[.dcID], )
	}

	return 
}

func ( map[int][]exchange.PublicKey) map[int][]exchange.PublicKey {
	if len() == 0 {
		return nil
	}

	 := make(map[int][]exchange.PublicKey, len())
	for ,  := range  {
		[] = append([]exchange.PublicKey(nil), ...)
	}
	return 
}

func ( fetchedCDNKeys) fetchedCDNKeys {
	return fetchedCDNKeys{
		all:  clonePublicKeys(.all),
		byDC: copyCDNKeysByDC(.byDC),
	}
}

func ( *Client) () ([]exchange.PublicKey, bool, uint64) {
	.cdnKeysMux.Lock()
	defer .cdnKeysMux.Unlock()

	return clonePublicKeys(.cdnKeys), .cdnKeysSet, .cdnKeysGen
}

func ( *Client) ( int) ([]exchange.PublicKey, bool) {
	.cdnKeysMux.Lock()
	defer .cdnKeysMux.Unlock()

	return clonePublicKeys(.cdnKeysByDC[]), .cdnKeysSet
}

func ( *Client) ( context.Context) context.Context {
	if .ctx != nil {
		// Bind network request lifetime to client lifecycle, not to the first
		// singleflight caller.
		return .ctx
	}

	// Caller cancellation is handled outside singleflight wait loop; request
	// itself should not inherit first caller deadline/cancellation.
	return context.WithoutCancel()
}

func ( *Client) ( context.Context) (fetchedCDNKeys, error) {
	 := .cdnKeysLoad.DoChan(helpGetCDNConfigSingleflightKey, func() (interface{}, error) {
		// singleflight ensures only one goroutine issues help.getCdnConfig;
		// others wait and reuse same result.
		,  := .tg.HelpGetCDNConfig(.cdnConfigFetchContext())
		if  != nil {
			return nil, errors.Wrap(, "help.getCdnConfig")
		}

		,  := parseCDNKeyEntries(.PublicKeys...)
		if  != nil {
			return nil, errors.Wrap(, "parse CDN public keys")
		}
		return buildCDNKeysCache(), nil
	})

	select {
	case <-.Done():
		return fetchedCDNKeys{}, .Err()
	case  := <-:
		if .Err != nil {
			return fetchedCDNKeys{}, .Err
		}

		,  := .Val.(fetchedCDNKeys)
		if ! {
			return fetchedCDNKeys{}, errors.Errorf("unexpected CDN keys type %T", .Val)
		}
		return cloneFetchedCDNKeys(), nil
	}
}

func ( *Client) ( context.Context) ([]exchange.PublicKey, error) {
	const  = 3
	for  := 0;  < ; ++ {
		// Fast path: fully cached, no network requests.
		, ,  := .cachedCDNKeys()
		if  {
			return , nil
		}
		// Snapshot generation to detect invalidation races after in-flight load.

		,  := .loadCDNKeys()
		if  != nil {
			return nil, 
		}

		.cdnKeysMux.Lock()
		switch {
		case .cdnKeysSet:
			// Another goroutine already populated cache while we were waiting.
			 := clonePublicKeys(.cdnKeys)
			.cdnKeysMux.Unlock()
			return , nil
		case .cdnKeysGen != :
			// Cache was invalidated (fingerprint miss) during in-flight request.
			// Discard stale result and retry from fresh generation.
			.cdnKeysMux.Unlock()
			continue
		default:
			// Safe to commit fetched keys into cache.
			.cdnKeys = clonePublicKeys(.all)
			.cdnKeysByDC = copyCDNKeysByDC(.byDC)
			.cdnKeysSet = true
			 := clonePublicKeys(.cdnKeys)
			.cdnKeysMux.Unlock()
			return , nil
		}
	}

	return nil, errors.New("cdn keys cache changed concurrently")
}

func ( *Client) ( context.Context) ([]exchange.PublicKey, error) {
	const  = 3
	for  := 0;  < ; ++ {
		.cdnKeysMux.Lock()
		 := .cdnKeysGen
		.cdnKeysMux.Unlock()

		,  := .loadCDNKeys()
		if  != nil {
			return nil, 
		}

		.cdnKeysMux.Lock()
		if .cdnKeysGen !=  {
			// Fingerprint invalidation happened while refresh was in-flight.
			// Discard stale result and refetch for fresh generation.
			.cdnKeysMux.Unlock()
			continue
		}
		.cdnKeys = clonePublicKeys(.all)
		.cdnKeysByDC = copyCDNKeysByDC(.byDC)
		.cdnKeysSet = true
		 := clonePublicKeys(.cdnKeys)
		.cdnKeysMux.Unlock()

		return , nil
	}

	return nil, errors.New("cdn keys cache changed concurrently")
}

func ( *Client) ( context.Context,  int) ([]exchange.PublicKey, error) {
	,  := .cachedCDNKeysForDC()
	if ! {
		if ,  := .fetchCDNKeys();  != nil {
			return nil, 
		}
	}

	const  = 3
	for  := 0;  < ; ++ {
		if  := .Err();  != nil {
			return nil, 
		}

		, _ = .cachedCDNKeysForDC()
		if len() > 0 {
			return , nil
		}
		if  == -1 {
			break
		}

		// Requested CDN DC is missing in current snapshot; retry bounded
		// help.getCdnConfig refreshes to handle eventual config propagation.
		if ,  := .refreshCDNKeys();  != nil {
			return nil, 
		}
	}

	return nil, errors.Errorf("no CDN public keys for CDN DC %d after %d refresh attempts", , )
}