package services

import (
	"fmt"
	"nearle/models"
	"nearle/repositories"
	"time"
)

type ProductService interface {
	GetProductSubCategory(categoryID, tenantID int) ([]models.ProductSubCategory, error)
	GetProductCount(tenantID, categoryID, subcategoryID int, approve string) ([]models.Productcount, error)
	GetProductCategory() ([]models.ProductCategory, error)
	GetProductVariants(tenantID, subcategoryID int) ([]models.Productvariant, error)
	GetCatalougeProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Products, error)
	GetProductStocks(tenantID, locationID string) ([]models.Productstocks, error)
	UpdateProductStatus(productIDs []int, status string) error
	CreateProductStock(stocks []models.Productstock) error
	CreateProduct(product models.Products) error
	UpdateProduct(product models.Products) error
	DeleteProduct(productID int) error
	GetStockStatement(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Productstockstatement, error)
	GetLocationProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Locationproducts, error)
	GetLocationProductSummary(tenantID, locationID int) ([]models.ProductSummary, error)
	GetSaleTemplate(tenantID, locationID int) (*models.SaleTemplate, error)
	FetchFilteredProducts(categoryID, subcategoryID, productID, applocationID, tenantID, locationID int, keyword, productStatus, approve string, pageno, pagesize int) ([]models.Tenantproducts, error)
	GetProductByVariant(tenantid, variantid, locationid int) ([]models.Products, error)
	GetProductsBySubcategory(params models.ProductFilter) (map[string]interface{}, error)
	UpdateProductLocation(input models.Productlocations) error
	CreateProductLocation(input []models.Productlocations) error
	CreateProductVariant(input models.Productvariant) error
	DeleteProductLocation(tenantid, locationid, productid int) error
	ImportCatalogueProduct(reqs []models.ImportCatalogueProductRequest) error
	GetImportedCatalogueRefs(tenantid int, brand string) ([]models.ImportedCatalogueRef, error)
	GetTenantCategories(tenantid int) ([]models.TenantCategory, error)
}
type productService struct {
	repo             repositories.ProductRepository
	catalogueService CatalogueService
}

func NewProductService(repo repositories.ProductRepository, catalogueService CatalogueService) ProductService {
	return &productService{repo: repo, catalogueService: catalogueService}
}

func (s *productService) GetProductSubCategory(categoryID, tenantID int) ([]models.ProductSubCategory, error) {
	return s.repo.GetProductSubCategory(categoryID, tenantID)
}

func (s *productService) GetProductCount(tenantID, categoryID, subcategoryID int, approve string) ([]models.Productcount, error) {
	return s.repo.GetProductCount(tenantID, categoryID, subcategoryID, approve)
}

func (s *productService) GetProductCategory() ([]models.ProductCategory, error) {
	return s.repo.GetProductCategory()
}

func (s *productService) GetProductVariants(tenantID, subcategoryID int) ([]models.Productvariant, error) {
	return s.repo.GetProductVariants(tenantID, subcategoryID)
}

func (s *productService) GetCatalougeProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Products, error) {
	return s.repo.GetCatalougeProducts(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
}

func (s *productService) GetProductStocks(tenantID, locationID string) ([]models.Productstocks, error) {
	return s.repo.GetProductStocks(tenantID, locationID)
}

func (s *productService) CreateProductStock(stocks []models.Productstock) error {

	for i := range stocks {
		stocks[i].Stockdate = time.Now()
	}

	if err := s.repo.CreateProductStock(stocks); err != nil {
		return err
	}

	locMap := make(map[models.ProductLocationRef]struct{})
	var locRefs []models.ProductLocationRef
	for _, stk := range stocks {
		// Every entry gets synced, "in" and "out" alike: the status is now
		// derived from the resulting balance rather than assumed from the
		// direction of the movement, so an "out" that empties a location
		// flags it outofstock and a partial "in" that leaves the balance at
		// or below zero correctly does not mark it sellable.
		if stk.Productid > 0 && stk.Locationid > 0 && stk.Tenantid > 0 {
			ref := models.ProductLocationRef{Tenantid: stk.Tenantid, Locationid: stk.Locationid, Productid: stk.Productid}
			if _, exists := locMap[ref]; !exists {
				locMap[ref] = struct{}{}
				locRefs = append(locRefs, ref)
			}
		}
	}

	// products.productstatus is deliberately NOT touched here. It is a
	// per-product lifecycle field holding "Active"/"Inactive", and receiving
	// stock used to overwrite it with "available" — an availability value in a
	// lifecycle column, which is how 136 products ended up reading "available"
	// and 12 "outofstock" with their real lifecycle state destroyed.
	//
	// Availability is a per-outlet fact and belongs to productlocations.status,
	// which SyncProductLocationStatus derives from the ledger below. A single
	// column on products cannot express it anyway: the same product can be
	// stocked at one outlet and empty at another.
	if len(locRefs) > 0 {
		if err := s.repo.SyncProductLocationStatus(locRefs); err != nil {
			return err
		}
	}

	return nil
}

func (s *productService) UpdateProductStatus(productIDs []int, status string) error {
	return s.repo.UpdateProductStatus(productIDs, status)
}

func (s *productService) CreateProduct(product models.Products) error {
	return s.repo.CreateProduct(product)
}

func (s *productService) UpdateProduct(product models.Products) error {
	return s.repo.UpdateProduct(product)
}

func (s *productService) DeleteProduct(productID int) error {
	return s.repo.DeleteProduct(productID)
}

func (s *productService) GetStockStatement(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Productstockstatement, error) {
	return s.repo.GetStockStatement(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
}

func (s *productService) GetLocationProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Locationproducts, error) {
	return s.repo.GetLocationProducts(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
}

func (s *productService) GetLocationProductSummary(tenantID, locationID int) ([]models.ProductSummary, error) {
	return s.repo.GetLocationProductSummary(tenantID, locationID)
}

func (s *productService) GetSaleTemplate(tenantID, locationID int) (*models.SaleTemplate, error) {
	return s.repo.GetSaleTemplate(tenantID, locationID)
}

func (s *productService) FetchFilteredProducts(categoryID, subcategoryID, productID, applocationID, tenantID, locationID int, keyword, productStatus,
	approve string, pageno, pagesize int) ([]models.Tenantproducts, error) {
	return s.repo.FetchFilteredProducts(categoryID, subcategoryID, productID, applocationID, tenantID, locationID, keyword, productStatus, approve, pageno, pagesize)
}

func (s *productService) GetProductByVariant(tenantid, variantid, locationid int) ([]models.Products, error) {

	var data []models.Products

	result, err := s.repo.GetProductByVariant(tenantid, variantid, locationid)

	if err != nil {
		return nil, err
	}

	data = result

	return data, nil

}

func (s *productService) GetProductsBySubcategory(params models.ProductFilter) (map[string]interface{}, error) {

	subcategories, err := s.repo.GetSubcategories(params.CategoryID)
	if err != nil {
		return nil, err
	}

	products, err := s.repo.GetProducts(params)
	if err != nil {
		return nil, err
	}

	var details []models.SubcategoryProductResponse
	var uncategorized []models.Products

	for _, sub := range subcategories {
		var subProducts []models.Products
		for _, p := range products {
			if p.Subcategoryid == sub.Subcategoryid {
				subProducts = append(subProducts, p)
			}
		}
		if len(subProducts) > 0 {
			details = append(details, models.SubcategoryProductResponse{
				SubcategoryID:   sub.Subcategoryid,
				SubcategoryName: sub.Subcategoryname,
				Image:           sub.Image,
				Products:        subProducts,
			})
		}
	}

	for _, p := range products {
		if p.Subcategoryid == 0 {
			uncategorized = append(uncategorized, p)
		}
	}
	if len(uncategorized) > 0 {
		details = append(details, models.SubcategoryProductResponse{
			SubcategoryID:   0,
			SubcategoryName: "Uncategorized",
			Products:        uncategorized,
		})
	}

	if params.TenantID > 0 {
		tenantInfo, err := s.repo.GetTenantInfo(params.TenantID, params.AppLocationID)
		if err == nil && tenantInfo != nil {
			tenantInfo["details"] = details
			return tenantInfo, nil
		}
	}

	return map[string]interface{}{"details": details}, nil
}

func (s *productService) UpdateProductLocation(input models.Productlocations) error {
	return s.repo.UpdateProductLocation(input)
}

func (s *productService) CreateProductLocation(input []models.Productlocations) error {
	return s.repo.CreateProductLocation(input)
}

func (s *productService) CreateProductVariant(input models.Productvariant) error {
	return s.repo.CreateProductVariant(input)
}

func (s *productService) DeleteProductLocation(tenantid, locationid, productid int) error {
	return s.repo.DeleteProductLocation(tenantid, locationid, productid)
}

// ImportCatalogueProduct bridges a global catalogue product (CatalogueDB) into
// a tenant's own store catalogue: it snapshots the catalogue product into the
// tenant's `products` table on first import (keyed on brand+catalogueid so
// re-imports are recognized), then links it to the location via the existing
// CreateProductLocation upsert, which already handles conflicting
// (tenantid, locationid, productid) rows and stock-ledger entries.
func (s *productService) ImportCatalogueProduct(reqs []models.ImportCatalogueProductRequest) error {
	locations := make([]models.Productlocations, 0, len(reqs))

	for _, req := range reqs {
		catalogueProduct, err := s.catalogueService.GetProductByID(req.Brand, req.Catalogueid)
		if err != nil {
			return err
		}
		if catalogueProduct == nil {
			return fmt.Errorf("catalogue product not found: brand=%s id=%d", req.Brand, req.Catalogueid)
		}

		existing, err := s.repo.FindTenantProductByCatalogueRef(req.Tenantid, req.Brand, req.Catalogueid)
		if err != nil {
			return err
		}

		productID := 0
		if existing != nil {
			productID = existing.Productid
			if err := s.repo.UpdateProductPricing(productID, req.Retailprice, req.Productcost, req.Taxpercent); err != nil {
				return err
			}
		} else {
			snapshot := models.Products{
				Tenantid:      req.Tenantid,
				Categoryid:    req.Categoryid,
				Subcategoryid: req.Subcategoryid,
				Productname:   catalogueProduct.ProductName,
				Productdesc:   catalogueProduct.Description,
				Productsku:    catalogueProduct.ProductSKU,
				Productbrand:  catalogueProduct.Brand,
				Catalogueid:   int(catalogueProduct.ID),
				Productunit:   catalogueProduct.Size,
				Productcost:   req.Productcost,
				Retailprice:   req.Retailprice,
				Taxpercent:    req.Taxpercent,
				Approve:       1,
			}
			if len(catalogueProduct.Images) > 0 {
				snapshot.Productimage = catalogueProduct.Images[0]
			}

			productID, err = s.repo.CreateProductReturningID(snapshot)
			if err != nil {
				return err
			}
		}

		locations = append(locations, models.Productlocations{
			Tenantid:   req.Tenantid,
			Locationid: req.Locationid,
			Productid:  productID,
			Quantity:   req.Quantity,
			Stocktype:  req.Stocktype,
			Status:     req.Status,
		})
	}

	return s.repo.CreateProductLocation(locations)
}

func (s *productService) GetImportedCatalogueRefs(tenantid int, brand string) ([]models.ImportedCatalogueRef, error) {
	return s.repo.GetImportedCatalogueRefs(tenantid, brand)
}

func (s *productService) GetTenantCategories(tenantid int) ([]models.TenantCategory, error) {
	return s.repo.GetTenantCategories(tenantid)
}

