package processor

import (
	"context"
	"fmt"

	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/mappers"
	"apskel-pos-be/internal/models"
	"apskel-pos-be/internal/repository"

	"github.com/google/uuid"
)

type ProductProcessor interface {
	CreateProduct(ctx context.Context, req *models.CreateProductRequest) (*models.ProductResponse, error)
	UpdateProduct(ctx context.Context, id uuid.UUID, req *models.UpdateProductRequest) (*models.ProductResponse, error)
	DeleteProduct(ctx context.Context, id uuid.UUID) error
	GetProductByID(ctx context.Context, id uuid.UUID) (*models.ProductResponse, error)
	ListProducts(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.ProductResponse, int, error)
}

type ProductRepository interface {
	Create(ctx context.Context, product *entities.Product) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.Product, error)
	GetWithCategory(ctx context.Context, id uuid.UUID) (*entities.Product, error)
	GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Product, error)
	GetByOrganization(ctx context.Context, organizationID uuid.UUID) ([]*entities.Product, error)
	GetByCategory(ctx context.Context, categoryID uuid.UUID) ([]*entities.Product, error)
	GetByBusinessType(ctx context.Context, businessType string) ([]*entities.Product, error)
	GetActiveByCategoryID(ctx context.Context, categoryID uuid.UUID) ([]*entities.Product, error)
	Update(ctx context.Context, product *entities.Product) error
	Delete(ctx context.Context, id uuid.UUID) error
	List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Product, int64, error)
	Count(ctx context.Context, filters map[string]interface{}) (int64, error)
	GetBySKU(ctx context.Context, organizationID uuid.UUID, sku string) (*entities.Product, error)
	ExistsBySKU(ctx context.Context, organizationID uuid.UUID, sku string, excludeID *uuid.UUID) (bool, error)
	GetByName(ctx context.Context, organizationID uuid.UUID, name string) (*entities.Product, error)
	ExistsByName(ctx context.Context, organizationID uuid.UUID, name string, excludeID *uuid.UUID) (bool, error)
	UpdateActiveStatus(ctx context.Context, id uuid.UUID, isActive bool) error
	GetLowCostProducts(ctx context.Context, organizationID uuid.UUID, maxCost float64) ([]*entities.Product, error)
}

type ProductProcessorImpl struct {
	productRepo        ProductRepository
	categoryRepo       CategoryRepository
	productVariantRepo repository.ProductVariantRepository
	inventoryRepo      repository.InventoryRepository
	outletRepo         OutletRepository
}

func NewProductProcessorImpl(productRepo ProductRepository, categoryRepo CategoryRepository, productVariantRepo repository.ProductVariantRepository, inventoryRepo repository.InventoryRepository, outletRepo OutletRepository) *ProductProcessorImpl {
	return &ProductProcessorImpl{
		productRepo:        productRepo,
		categoryRepo:       categoryRepo,
		productVariantRepo: productVariantRepo,
		inventoryRepo:      inventoryRepo,
		outletRepo:         outletRepo,
	}
}

func (p *ProductProcessorImpl) CreateProduct(ctx context.Context, req *models.CreateProductRequest) (*models.ProductResponse, error) {
	_, err := p.categoryRepo.GetByID(ctx, req.CategoryID)
	if err != nil {
		return nil, fmt.Errorf("invalid category: %w", err)
	}

	if req.SKU != nil && *req.SKU != "" {
		exists, err := p.productRepo.ExistsBySKU(ctx, req.OrganizationID, *req.SKU, nil)
		if err != nil {
			return nil, fmt.Errorf("failed to check SKU uniqueness: %w", err)
		}
		if exists {
			return nil, fmt.Errorf("product with SKU '%s' already exists for this organization", *req.SKU)
		}
	}

	exists, err := p.productRepo.ExistsByName(ctx, req.OrganizationID, req.Name, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to check product name uniqueness: %w", err)
	}
	if exists {
		return nil, fmt.Errorf("product with name '%s' already exists for this organization", req.Name)
	}

	productEntity := mappers.CreateProductRequestToEntity(req)

	if err := p.productRepo.Create(ctx, productEntity); err != nil {
		return nil, fmt.Errorf("failed to create product: %w", err)
	}

	// Create variants if provided
	if req.Variants != nil && len(req.Variants) > 0 {
		for _, variantReq := range req.Variants {
			// Set the product ID for the variant
			variantReq.ProductID = productEntity.ID

			// Check variant name uniqueness within the same product
			exists, err := p.productVariantRepo.ExistsByName(ctx, productEntity.ID, variantReq.Name, nil)
			if err != nil {
				return nil, fmt.Errorf("failed to check variant name uniqueness: %w", err)
			}
			if exists {
				return nil, fmt.Errorf("variant with name '%s' already exists for this product", variantReq.Name)
			}

			variantEntity := mappers.CreateProductVariantRequestToEntity(&variantReq)
			if err := p.productVariantRepo.Create(ctx, variantEntity); err != nil {
				return nil, fmt.Errorf("failed to create product variant: %w", err)
			}
		}
	}

	// Create inventory records for all outlets if requested
	if req.CreateInventory {
		if err := p.createInventoryForAllOutlets(ctx, productEntity.ID, req.OrganizationID, req.InitialStock, req.ReorderLevel); err != nil {
			return nil, fmt.Errorf("failed to create inventory records: %w", err)
		}
	}

	productWithCategory, err := p.productRepo.GetWithCategory(ctx, productEntity.ID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve created product: %w", err)
	}

	response := mappers.ProductEntityToResponse(productWithCategory)
	return response, nil
}

func (p *ProductProcessorImpl) UpdateProduct(ctx context.Context, id uuid.UUID, req *models.UpdateProductRequest) (*models.ProductResponse, error) {
	existingProduct, err := p.productRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("product not found: %w", err)
	}

	if req.CategoryID != nil {
		_, err := p.categoryRepo.GetByID(ctx, *req.CategoryID)
		if err != nil {
			return nil, fmt.Errorf("invalid category: %w", err)
		}
	}

	if req.SKU != nil && *req.SKU != "" {
		currentSKU := ""
		if existingProduct.SKU != nil {
			currentSKU = *existingProduct.SKU
		}
		if *req.SKU != currentSKU {
			exists, err := p.productRepo.ExistsBySKU(ctx, existingProduct.OrganizationID, *req.SKU, &id)
			if err != nil {
				return nil, fmt.Errorf("failed to check SKU uniqueness: %w", err)
			}
			if exists {
				return nil, fmt.Errorf("product with SKU '%s' already exists for this organization", *req.SKU)
			}
		}
	}

	if req.Name != nil && *req.Name != existingProduct.Name {
		exists, err := p.productRepo.ExistsByName(ctx, existingProduct.OrganizationID, *req.Name, &id)
		if err != nil {
			return nil, fmt.Errorf("failed to check product name uniqueness: %w", err)
		}
		if exists {
			return nil, fmt.Errorf("product with name '%s' already exists for this organization", *req.Name)
		}
	}

	mappers.UpdateProductEntityFromRequest(existingProduct, req)

	if err := p.productRepo.Update(ctx, existingProduct); err != nil {
		return nil, fmt.Errorf("failed to update product: %w", err)
	}

	// Update reorder level for all existing inventory records if provided
	if req.ReorderLevel != nil {
		if err := p.updateReorderLevelForAllOutlets(ctx, id, *req.ReorderLevel); err != nil {
			return nil, fmt.Errorf("failed to update reorder levels: %w", err)
		}
	}

	productWithCategory, err := p.productRepo.GetWithCategory(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve updated product: %w", err)
	}

	response := mappers.ProductEntityToResponse(productWithCategory)
	return response, nil
}

func (p *ProductProcessorImpl) DeleteProduct(ctx context.Context, id uuid.UUID) error {
	_, err := p.productRepo.GetByID(ctx, id)
	if err != nil {
		return fmt.Errorf("product not found: %w", err)
	}

	productWithRelations, err := p.productRepo.GetWithRelations(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to check product relations: %w", err)
	}

	if len(productWithRelations.Inventory) > 0 {
		return fmt.Errorf("cannot delete product: it has inventory records associated with it")
	}

	if len(productWithRelations.OrderItems) > 0 {
		return fmt.Errorf("cannot delete product: it has order items associated with it")
	}

	if err := p.productRepo.Delete(ctx, id); err != nil {
		return fmt.Errorf("failed to delete product: %w", err)
	}

	return nil
}

func (p *ProductProcessorImpl) GetProductByID(ctx context.Context, id uuid.UUID) (*models.ProductResponse, error) {
	productEntity, err := p.productRepo.GetWithCategory(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("product not found: %w", err)
	}

	response := mappers.ProductEntityToResponse(productEntity)
	return response, nil
}

func (p *ProductProcessorImpl) ListProducts(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.ProductResponse, int, error) {
	offset := (page - 1) * limit

	productEntities, total, err := p.productRepo.List(ctx, filters, limit, offset)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to list products: %w", err)
	}

	responses := make([]models.ProductResponse, len(productEntities))
	for i, entity := range productEntities {
		response := mappers.ProductEntityToResponse(entity)
		if response != nil {
			responses[i] = *response
		}
	}

	return responses, int(total), nil
}

// Helper methods for inventory management

// createInventoryForAllOutlets creates inventory records for all outlets of an organization
func (p *ProductProcessorImpl) createInventoryForAllOutlets(ctx context.Context, productID, organizationID uuid.UUID, initialStock, reorderLevel *int) error {
	// Get all outlets for the organization
	outlets, err := p.outletRepo.GetByOrganizationID(ctx, organizationID)
	if err != nil {
		return fmt.Errorf("failed to get outlets for organization: %w", err)
	}

	if len(outlets) == 0 {
		return fmt.Errorf("no outlets found for organization")
	}

	// Prepare inventory items for bulk creation
	var inventoryItems []*entities.Inventory
	for _, outlet := range outlets {
		quantity := 0
		if initialStock != nil {
			quantity = *initialStock
		}

		reorderLevelValue := 0
		if reorderLevel != nil {
			reorderLevelValue = *reorderLevel
		}

		inventoryItem := &entities.Inventory{
			OutletID:     outlet.ID,
			ProductID:    productID,
			Quantity:     quantity,
			ReorderLevel: reorderLevelValue,
		}
		inventoryItems = append(inventoryItems, inventoryItem)
	}

	// Bulk create inventory records
	if err := p.inventoryRepo.BulkCreate(ctx, inventoryItems); err != nil {
		return fmt.Errorf("failed to bulk create inventory records: %w", err)
	}

	return nil
}

// updateReorderLevelForAllOutlets updates the reorder level for all inventory records of a product
func (p *ProductProcessorImpl) updateReorderLevelForAllOutlets(ctx context.Context, productID uuid.UUID, reorderLevel int) error {
	// Get all inventory records for the product
	inventoryRecords, err := p.inventoryRepo.GetByProduct(ctx, productID)
	if err != nil {
		return fmt.Errorf("failed to get inventory records for product: %w", err)
	}

	// Update reorder level for each inventory record
	for _, inventory := range inventoryRecords {
		inventory.ReorderLevel = reorderLevel
		if err := p.inventoryRepo.Update(ctx, inventory); err != nil {
			return fmt.Errorf("failed to update inventory reorder level: %w", err)
		}
	}

	return nil
}
