package products

import (
	"context"
	"fmt"
	pb "sipro-mps/api/generated/v1/wb/products"
	"sipro-mps/internal/marketplace"
	"sipro-mps/internal/redis"
	"sipro-mps/internal/tasks/client"
	"sipro-mps/internal/tasks/types"
	"sipro-mps/internal/wb"
	"sipro-mps/internal/wb/products/mapping/generated"
	wbapi "sipro-mps/pkg/api/wb/client"
	"sipro-mps/pkg/utils"

	"github.com/samber/lo"
)

const (
	maxRetries            = 5
	maxProductsPerRequest = 100
)

type apiRepository struct {
	marketplaceRepository marketplace.Repository
}

func GetProductsKey(identifier string) string {
	return fmt.Sprintf("wb:products:%s:compressed", identifier)
}

func (a apiRepository) ParseMarketplace(ctx context.Context, marketplaceId int) (*marketplace.Marketplace, string, error) {
	marketplaceByID, err := a.marketplaceRepository.GetMarketplaceByID(ctx, marketplaceId)
	if err != nil {
		return nil, "", err
	}
	_, claims, err := utils.DecodeWildberriesJwt(marketplaceByID.AuthDataJson)
	if err != nil {
		return nil, "", err
	}
	sellerId := claims["sid"].(string)
	return marketplaceByID, sellerId, nil
}

func fetchProducts(
	ctx context.Context,
	client *wbapi.Client,
	sellerId string,
	resultChan chan<- []WbProduct,
	errChan chan<- error,
) {
	defer close(resultChan)
	defer close(errChan)
	request := wbapi.ContentV2GetCardsListPostReq{}
	request.Settings.SetTo(wbapi.ContentV2GetCardsListPostReqSettings{})

	request.Settings.Value.Cursor.SetTo(wbapi.ContentV2GetCardsListPostReqSettingsCursor{})
	request.Settings.Value.Cursor.Value.Limit.SetTo(maxProductsPerRequest)

	request.Settings.Value.Filter.SetTo(wbapi.ContentV2GetCardsListPostReqSettingsFilter{})
	request.Settings.Value.Filter.Value.WithPhoto.SetTo(-1)
	currentRetry := 0
	for {
		response, err := client.ContentV2GetCardsListPost(ctx, &request, wbapi.ContentV2GetCardsListPostParams{Locale: wbapi.NewOptString("ru")})
		if err != nil {
			currentRetry++
			if currentRetry >= maxRetries {
				errChan <- fmt.Errorf("fetching product IDs: %w", err)
				return
			}
			continue
		}
		currentRetry = 0

		switch r := response.(type) {
		case *wbapi.ContentV2GetCardsListPostOKHeaders:
			err = wb.SyncRateLimitRemaining(ctx, sellerId, r.XRatelimitRemaining.Value)
			if err != nil {
				errChan <- fmt.Errorf("syncing rate limit: %w", err)
				return
			}
			resultChan <- r.Response.Cards
			if r.Response.Cursor.Value.Total.Value < maxProductsPerRequest {
				return
			}
			request.Settings.Value.Cursor.Value.UpdatedAt.SetTo(r.Response.Cursor.Value.UpdatedAt.Value)
			request.Settings.Value.Cursor.Value.NmID.SetTo(r.Response.Cursor.Value.NmID.Value)
		case *wbapi.R429Headers:
			err = wb.SetRateLimitRetry(ctx, sellerId, r.XRatelimitRetry.Value, r.XRatelimitLimit.Value, r.XRatelimitReset.Value)
			if err != nil {
				errChan <- fmt.Errorf("setting rate limit retry: %w", err)
				return
			}
		default:
			errChan <- fmt.Errorf("unexpected response type: %T", r)
			return
		}
	}
}

func (a apiRepository) StreamAllProductsCache(ctx context.Context, marketplaceId int, resultChan chan<- []pb.Product, errChan chan<- error) {
	defer close(resultChan)
	defer close(errChan)
	mp, err := a.marketplaceRepository.GetMarketplaceByID(ctx, marketplaceId)
	if err != nil {
		errChan <- fmt.Errorf("getting marketplace by ID: %w", err)
		return
	}
	identifier, err := mp.GetIdentifier()
	if err != nil {
		errChan <- fmt.Errorf("getting marketplace identifier: %w", err)
		return
	}

	converter := generated.ConverterImpl{}

	key := GetProductsKey(identifier)
	var cachedMessage pb.GetProductsResponse
	err = redis.ReadProtoMessage(ctx, key, &cachedMessage)
	if err == nil && len(cachedMessage.Products) > 0 {
		resultChan <- utils.DerefSlice(cachedMessage.Products)
		_ = client.EnqueueFetchProductsTask(types.TypeWbFetchProducts, marketplaceId)
		return
	}

	innerResultChan := make(chan []WbProduct)
	innerErrChan := make(chan error)
	go a.StreamAllProducts(ctx, marketplaceId, innerResultChan, innerErrChan)
	var allProducts []pb.Product
	defer func() {
		if len(allProducts) == 0 {
			return
		}
		message := pb.GetProductsResponse{
			Products: utils.ToPtrs(allProducts),
		}
		_ = redis.WriteProtoMessage(ctx, key, &message)

	}()
	for {
		select {
		case err, ok := <-innerErrChan:
			if !ok {
				return
			}
			errChan <- fmt.Errorf("streaming products: %w", err)
			return
		case products, ok := <-innerResultChan:
			if !ok {
				return
			}
			pbProducts := lo.Map(products, func(p WbProduct, _ int) pb.Product {
				return *converter.ToProto(&p)
			})
			allProducts = append(allProducts, pbProducts...)
			resultChan <- pbProducts
		}
	}

}
func (a apiRepository) GetAllProducts(ctx context.Context, marketplaceId int) ([]WbProduct, error) {
	marketplaceByID, sellerId, err := a.ParseMarketplace(ctx, marketplaceId)
	fromMarketplace, err := wb.GetClientFromMarketplace(marketplaceByID)
	if err != nil {
		return nil, err
	}
	resultChan := make(chan []WbProduct)
	errChan := make(chan error)
	go fetchProducts(ctx, fromMarketplace, sellerId, resultChan, errChan)

	var products []WbProduct
	isWaiting := true
	for {
		if !isWaiting {
			break
		}
		select {
		case err, ok := <-errChan:
			if !ok {
				isWaiting = false
				continue
			}
			return nil, err
		case newProducts, ok := <-resultChan:
			if !ok {
				isWaiting = false
			}
			products = append(products, newProducts...)
		}
	}
	return products, nil
}

func (a apiRepository) StreamAllProducts(ctx context.Context, marketplaceId int, resultChan chan<- []WbProduct, errChan chan<- error) {
	marketplaceByID, sellerId, err := a.ParseMarketplace(ctx, marketplaceId)
	if err != nil {
		errChan <- err
		return
	}
	fromMarketplace, err := wb.GetClientFromMarketplace(marketplaceByID)
	if err != nil {
		errChan <- err
		return
	}
	go fetchProducts(ctx, fromMarketplace, sellerId, resultChan, errChan)
}

func NewAPIRepository(marketplaceRepository marketplace.Repository) Repository {
	return &apiRepository{
		marketplaceRepository: marketplaceRepository,
	}
}
