package service

import (
	"errors"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-stocks/validations"
	productRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"

	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	"github.com/sirupsen/logrus"
	"gorm.io/gorm"
)

type ProductStockService interface {
	GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Product, int64, error)
	GetOne(ctx *fiber.Ctx, id uint) (*entity.Product, error)
}

type productStockService struct {
	Log               *logrus.Logger
	Validate          *validator.Validate
	ProductRepository productRepository.ProductRepository
}

func NewProductStockService(
	productRepo productRepository.ProductRepository,
	validate *validator.Validate,
) ProductStockService {
	return &productStockService{
		Log:               utils.Log,
		Validate:          validate,
		ProductRepository: productRepo,
	}
}

func (s productStockService) withRelations(db *gorm.DB, locationScope, areaScope m.ScopeFilter) *gorm.DB {
	warehouseScope := func(db *gorm.DB) *gorm.DB {
		if locationScope.Restrict {
			db = db.Where("warehouses.location_id IN ?", locationScope.IDs)
		}
		if areaScope.Restrict {
			db = db.Where("warehouses.area_id IN ?", areaScope.IDs)
		}
		return db
	}
	productWarehouseScope := func(db *gorm.DB) *gorm.DB {
		db = db.Joins("JOIN warehouses w ON w.id = product_warehouses.warehouse_id")
		if locationScope.Restrict {
			db = db.Where("w.location_id IN ?", locationScope.IDs)
		}
		if areaScope.Restrict {
			db = db.Where("w.area_id IN ?", areaScope.IDs)
		}
		return db
	}
	stockLogScope := func(db *gorm.DB) *gorm.DB {
		db = db.
			Joins("JOIN product_warehouses pw ON pw.id = stock_logs.product_warehouse_id").
			Joins("JOIN warehouses w ON w.id = pw.warehouse_id")
		if locationScope.Restrict {
			db = db.Where("w.location_id IN ?", locationScope.IDs)
		}
		if areaScope.Restrict {
			db = db.Where("w.area_id IN ?", areaScope.IDs)
		}
		return db.Order("stock_logs.created_at ASC")
	}

	return db.
		Preload("CreatedUser").
		Preload("Uom").
		Preload("ProductCategory").
		Preload("Flags").
		Preload("ProductWarehouses", productWarehouseScope).
		Preload("ProductWarehouses.Warehouse", warehouseScope).
		Preload("ProductWarehouses.Warehouse.Location").
		Preload("ProductWarehouses.Warehouse.Location.Area").
		Preload("ProductWarehouses.StockLogs", stockLogScope).
		Preload("ProductWarehouses.StockLogs.CreatedUser").
		Preload("ProductSuppliers").
		Preload("ProductSuppliers.Supplier", func(db *gorm.DB) *gorm.DB {
			return db.Order("suppliers.name ASC")
		})
}

func (s productStockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Product, int64, error) {
	if err := s.Validate.Struct(params); err != nil {
		return nil, 0, err
	}

	locationScope, areaScope, err := m.ResolveLocationAreaScopes(c, s.ProductRepository.DB())
	if err != nil {
		return nil, 0, err
	}

	offset := (params.Page - 1) * params.Limit

	productStocks, total, err := s.ProductRepository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
		if locationScope.Restrict || areaScope.Restrict {
			if (locationScope.Restrict && len(locationScope.IDs) == 0) || (areaScope.Restrict && len(areaScope.IDs) == 0) {
				return db.Where("1 = 0")
			}
			db = db.Where(`EXISTS (
				SELECT 1
				FROM product_warehouses pw
				JOIN warehouses w ON w.id = pw.warehouse_id
				WHERE pw.product_id = products.id
					AND pw.qty > 0
					AND (? OR w.location_id IN ?)
					AND (? OR w.area_id IN ?)
			)`,
				!locationScope.Restrict, locationScope.IDs,
				!areaScope.Restrict, areaScope.IDs,
			)
		} else {
			db = db.Where(`EXISTS (
				SELECT 1
				FROM product_warehouses pw
				WHERE pw.product_id = products.id
					AND pw.qty > 0
			)`)
		}

		db = s.withRelations(db, locationScope, areaScope)
		if params.Search != "" {
			db = db.Where("products.name ILIKE ?", "%"+params.Search+"%")
		}
		return db.Order("products.created_at DESC").Order("products.updated_at DESC")
	})

	if err != nil {
		s.Log.Errorf("Failed to get productStocks: %+v", err)
		return nil, 0, err
	}
	return productStocks, total, nil
}

func (s productStockService) GetOne(c *fiber.Ctx, id uint) (*entity.Product, error) {
	locationScope, areaScope, err := m.ResolveLocationAreaScopes(c, s.ProductRepository.DB())
	if err != nil {
		return nil, err
	}

	if locationScope.Restrict || areaScope.Restrict {
		if (locationScope.Restrict && len(locationScope.IDs) == 0) || (areaScope.Restrict && len(areaScope.IDs) == 0) {
			return nil, fiber.NewError(fiber.StatusNotFound, "Product not found")
		}
		var count int64
		if err := s.ProductRepository.DB().WithContext(c.Context()).
			Table("product_warehouses pw").
			Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
			Where("pw.product_id = ?", id).
			Where("pw.qty > 0").
			Where("(? OR w.location_id IN ?)", !locationScope.Restrict, locationScope.IDs).
			Where("(? OR w.area_id IN ?)", !areaScope.Restrict, areaScope.IDs).
			Count(&count).Error; err != nil {
			return nil, err
		}
		if count == 0 {
			return nil, fiber.NewError(fiber.StatusNotFound, "Product not found")
		}
	}

	product, err := s.ProductRepository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
		return s.withRelations(db, locationScope, areaScope)
	})
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fiber.NewError(fiber.StatusNotFound, "Product not found")
	}
	if err != nil {
		s.Log.Errorf("Failed get product by id: %+v", err)
		return nil, err
	}

	if len(product.ProductWarehouses) > 0 {
		ids := make([]uint, 0, len(product.ProductWarehouses))
		for _, pw := range product.ProductWarehouses {
			if pw.Id != 0 {
				ids = append(ids, pw.Id)
			}
		}

		if len(ids) > 0 {
			type pendingUsageRow struct {
				ProductWarehouseId uint
				PendingQty         float64
			}

			var rows []pendingUsageRow
			if err := s.ProductRepository.DB().WithContext(c.Context()).
				Table("recording_stocks").
				Select("product_warehouse_id, COALESCE(SUM(pending_qty), 0) AS pending_qty").
				Where("pending_qty > 0").
				Where("product_warehouse_id IN ?", ids).
				Group("product_warehouse_id").
				Scan(&rows).Error; err != nil {
				s.Log.Errorf("Failed to load pending usage for product warehouses: %+v", err)
				return nil, err
			}

			if len(rows) > 0 {
				pendingMap := make(map[uint]float64, len(rows))
				for _, row := range rows {
					pendingMap[row.ProductWarehouseId] = row.PendingQty
				}

				for i := range product.ProductWarehouses {
					pw := &product.ProductWarehouses[i]
					if pending, ok := pendingMap[pw.Id]; ok && pending != 0 {
						pw.Quantity -= pending
					}
				}
			}
		}
	}
	return product, nil
}
