package downloader

import (
	

	

	
)

const cdnRefreshProbeLimit = 4 * 1024

func ( *cdn) ( context.Context,  int) (CDN, error) {
	.clientMux.Lock()
	defer .clientMux.Unlock()

	.stateMux.RLock()
	 := .cdn
	 := .clientDC
	.stateMux.RUnlock()
	if  != nil &&  ==  {
		return , nil
	}

	// Redirect may switch DC; recreate client lazily on demand.
	.closeClient()

	, ,  := .provider.CDN(, , .max)
	if  != nil {
		return nil, 
	}
	if  == nil {
		if  != nil {
			_ = .Close()
		}
		return nil, errors.New("cdn provider returned nil client")
	}

	.stateMux.Lock()
	.cdn = 
	.closer = 
	.clientDC = 
	.stateMux.Unlock()

	return , nil
}

func ( *cdn) ( context.Context,  *tg.UploadFileCDNRedirect) error {
	if  == nil {
		.setMaster()
		return nil
	}
	if ,  := .ensureClient(, .DCID);  != nil {
		return 
	}
	.setRedirect()
	return nil
}

func ( *cdn) (
	 context.Context,
	 error,
	 int64,
	 int,
	 uint64,
	 int,
) ( *chunk,  bool,  bool,  error) {
	if isCDNFingerprintErr() {
		.closeClient()
		return nil, true, true, nil
	}
	if !isCDNMasterFallbackErr() {
		return nil, false, false, nil
	}

	,  := .refreshRedirect(, , , , )
	if  != nil {
		return nil, false, true, 
	}
	if  != nil {
		return , false, true, nil
	}

	return nil, true, true, nil
}

func ( *cdn) (
	 context.Context,
	 int64,
	 int,
	 uint64,
	 int,
) (*chunk, error) {
	if  <= 0 {
		 = cdnRefreshProbeLimit
	}

	.refreshMux.Lock()
	defer .refreshMux.Unlock()

	, ,  := .snapshot()
	if  !=  {
		// Another goroutine already refreshed state; just retry outer loop.
		return nil, nil
	}

	,  := retryRequest(
		,
		"refresh CDN redirect",
		func( int,  error) {
			.reportRetry(RetryOperationRefreshRedirect, , )
		},
		func() (chunk, error) {
			return .master.Chunk(, , )
		},
	)
	if  == nil {
		// Server stopped redirecting this file/token; return to master mode.
		.setMaster()
		return &, nil
	}

	var  *RedirectError
	if errors.As(, &) {
		if  := .activateRedirect(, .Redirect);  != nil {
			if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
				return nil, errors.Wrapf(, "create CDN client for DC %d", .Redirect.DCID)
			}
			if isCDNFingerprintErr() {
				.reportRetry(RetryOperationCreateClient, , )
				.closeClient()
				return nil, nil
			}
			return nil, errors.Wrapf(, "create CDN client for DC %d", .Redirect.DCID)
		}
		return nil, nil
	}

	return nil, errors.Wrap(, "refresh CDN redirect")
}

func ( *cdn) ( context.Context,  int64,  int) (chunk, error) {
	// Unified state machine:
	// modeMaster -> try master and switch on redirect;
	// modeCDN    -> serve from CDN with token/keys refresh handling.
	for  := 0;  < maxRetryAttempts; ++ {
		if  := .Err();  != nil {
			return chunk{}, 
		}

		, ,  := .snapshot()
		switch  {
		case modeMaster:
			,  := .master.Chunk(, , )
			if  == nil {
				return , nil
			}

			var  *RedirectError
			if errors.As(, &) {
				// Redirect is expected protocol path when file is CDN-backed.
				if  := .activateRedirect(, .Redirect);  != nil {
					if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
						return chunk{}, errors.Wrapf(, "create CDN client for DC %d", .Redirect.DCID)
					}
					if isCDNFingerprintErr() {
						// CDN keys changed while pool still uses stale keys.
						// Close and retry so provider can reopen with fresh keys.
						.reportRetry(RetryOperationCreateClient, +1, )
						.closeClient()
						continue
					}
					return chunk{}, errors.Wrapf(, "create CDN client for DC %d", .Redirect.DCID)
				}
				continue
			}

			return chunk{}, errors.Wrapf(, "master chunk offset=%d limit=%d", , )

		case modeCDN:
			if  == nil {
				.setMaster()
				continue
			}

			,  := .ensureClient(, .DCID)
			if  != nil {
				if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
					return chunk{}, errors.Wrapf(, "create CDN client for DC %d", .DCID)
				}
				if isCDNFingerprintErr() {
					// Force recreate with fresh key set.
					.reportRetry(RetryOperationCreateClient, +1, )
					.closeClient()
					continue
				}
				return chunk{}, errors.Wrapf(, "create CDN client for DC %d", .DCID)
			}

			,  := buildCDNRequestPlan(, )
			if  != nil {
				return chunk{}, errors.Wrapf(, "cdn request plan offset=%d limit=%d", , )
			}

			 := make([]byte, 0, )
			 := false

		:
			for ,  := range  {
				,  := .UploadGetCDNFile(, &tg.UploadGetCDNFileRequest{
					Offset:    .offset,
					Limit:     .limit,
					FileToken: .FileToken,
				})
				if  != nil {
					, , ,  := .recoverCDNControlError(, , , , , +1)
					if  != nil {
						return chunk{}, 
					}
					if  {
						if  != nil {
							return *, nil
						}
						if  {
							.reportRetry(RetryOperationGetFile, +1, )
							 = true
							break 
						}
						continue
					}
					return chunk{}, errors.Wrapf(
						,
						"cdn chunk dc=%d offset=%d limit=%d",
						.DCID, .offset, .limit,
					)
				}

				switch typed := .(type) {
				case *tg.UploadCDNFile:
					,  := .decrypt(.Bytes, .offset, )
					if  != nil {
						return chunk{}, 
					}
					 = append(, ...)
					if len() < .limit {
						// Reached file tail, remaining plan segments are beyond EOF.
						break 
					}

				case *tg.UploadCDNFileReuploadNeeded:
					// Ask master DC to reissue CDN token window for this file.
					,  := .client.UploadReuploadCDNFile(, &tg.UploadReuploadCDNFileRequest{
						FileToken:    .FileToken,
						RequestToken: .RequestToken,
					})
					if  != nil {
						, , ,  := .recoverCDNControlError(, , , , , +1)
						if  != nil {
							return chunk{}, 
						}
						if  {
							if  != nil {
								return *, nil
							}
							if  {
								.reportRetry(RetryOperationReupload, +1, )
								 = true
								break 
							}
							continue
						}
						return chunk{}, errors.Wrapf(
							,
							"cdn reupload dc=%d offset=%d limit=%d",
							.DCID, .offset, .limit,
						)
					}
					// Reupload returns fresh CDN hashes for the requested token
					// window. Cache them immediately (same strategy as TDesktop) to
					// avoid an extra UploadGetCDNFileHashes call on retry.
					.cacheHashes()
					 = true
					break 

				default:
					return chunk{}, errors.Errorf("unexpected type %T", )
				}
			}
			if  {
				continue
			}

			if  := .verifyChunk(, , , );  != nil {
				return chunk{}, 
			}
			return chunk{data: }, nil
		}
	}

	return chunk{}, retryLimitErr("cdn chunk", maxRetryAttempts, errors.New("state loop"))
}

func ( *cdn) ( context.Context,  int64) ([]tg.FileHash, error) {
	// Hash retrieval follows same state machine as chunks to stay consistent
	// during concurrent token/redirect changes.
	for  := 0;  < maxRetryAttempts; ++ {
		if  := .Err();  != nil {
			return nil, 
		}

		, ,  := .snapshot()
		switch  {
		case modeMaster:
			,  := .master.Hashes(, )
			if  != nil {
				return nil, errors.Wrapf(, "master hashes offset=%d", )
			}
			return , nil

		case modeCDN:
			if  == nil {
				.setMaster()
				continue
			}

			,  := .client.UploadGetCDNFileHashes(, &tg.UploadGetCDNFileHashesRequest{
				FileToken: .FileToken,
				Offset:    ,
			})
			if  != nil {
				, , ,  := .recoverCDNControlError(, , , cdnRefreshProbeLimit, , +1)
				if  != nil {
					return nil, 
				}
				if  &&  {
					.reportRetry(RetryOperationGetFileHashes, +1, )
					continue
				}
				if  {
					continue
				}
				return nil, errors.Wrapf(, "cdn hashes dc=%d offset=%d", .DCID, )
			}
			.cacheHashes()
			return , nil
		}
	}

	return nil, retryLimitErr("cdn hashes", maxRetryAttempts, errors.New("state loop"))
}