package sqlc

import (
	"context"
	"errors"
	"fmt"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"

	"route256/loms/internal/domain/entity"
	"route256/loms/internal/domain/model"
	"route256/loms/internal/domain/service"
	"route256/loms/internal/infra/postgres"
	"route256/loms/internal/infra/tools"
)

type stockRepo struct {
	read  *pgxpool.Pool
	write *pgxpool.Pool
}

func NewStockRepository(write, read *pgxpool.Pool) service.StockRepository {
	return &stockRepo{
		read:  read,
		write: write,
	}
}

func (s *stockRepo) GetQuerier(ctx context.Context) *Queries {
	tx, ok := postgres.TxFromCtx(ctx)
	if ok {
		return New(tx)
	}

	return New(s.write)
}

func (s *stockRepo) StockReserve(ctx context.Context, stock *entity.Stock) error {
	querier := s.GetQuerier(ctx)

	rows, err := querier.StockReserve(ctx, &StockReserveParams{
		Sku:      int64(stock.Item.ID),
		Reserved: int64(stock.Reserved),
	})
	if err != nil {
		return fmt.Errorf("querier.StockReserve: %w", err)
	}

	if rows == 0 {
		return model.ErrNotEnoughStocks
	}

	return nil
}

func (s *stockRepo) StockReserveRemove(ctx context.Context, stock *entity.Stock) error {
	querier := s.GetQuerier(ctx)

	rows, err := querier.StockReserveRemove(ctx, &StockReserveRemoveParams{
		Sku:      int64(stock.Item.ID),
		Reserved: int64(stock.Reserved),
	})
	if err != nil {
		return fmt.Errorf("querier.StockReserveRemove: %w", err)
	}

	if rows > 0 {
		return nil
	}

	_, err = querier.StockGetByID(ctx, int64(stock.Item.ID))
	switch {
	case errors.Is(err, pgx.ErrNoRows):
		return model.ErrUnknownStock
	case err != nil:
		return fmt.Errorf("querier.StockGetByID: %w", err)
	default:
		return model.ErrNotEnoughStocks
	}
}

func (s *stockRepo) StockCancel(ctx context.Context, stock *entity.Stock) error {
	querier := s.GetQuerier(ctx)

	rows, err := querier.StockCancel(ctx, &StockCancelParams{
		Sku:      int64(stock.Item.ID),
		Reserved: int64(stock.Reserved),
	})
	if err != nil {
		return fmt.Errorf("querier.StockCancel: %w", err)
	}

	if rows > 0 {
		return nil
	}

	_, err = querier.StockGetByID(ctx, int64(stock.Item.ID))
	switch {
	case errors.Is(err, pgx.ErrNoRows):
		return model.ErrUnknownStock
	case err != nil:
		return fmt.Errorf("querier.StockGetByID: %w", err)
	default:
		return model.ErrNotEnoughStocks
	}
}

func (s *stockRepo) StockGetByID(ctx context.Context, sku entity.Sku) (*entity.Stock, error) {
	querier := s.GetQuerier(ctx)

	stock, err := querier.StockGetByID(ctx, int64(sku))
	switch {
	case errors.Is(err, pgx.ErrNoRows):
		return nil, model.ErrUnknownStock
	case err != nil:
		return nil, fmt.Errorf("querier.StockGetByID: %w", err)
	default:
		count, castErr := tools.SafeCastInt64ToUInt32(stock.TotalCount)
		if castErr != nil {
			return nil, castErr
		}

		reserved, castErr := tools.SafeCastInt64ToUInt32(stock.Reserved)
		if castErr != nil {
			return nil, castErr
		}

		return &entity.Stock{
			Item: entity.OrderItem{
				ID:    entity.Sku(stock.Sku),
				Count: count,
			},
			Reserved: reserved,
		}, nil
	}
}
