package processor

import (
	"context"
	"fmt"

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

	"github.com/google/uuid"
)

type CategoryProcessor interface {
	CreateCategory(ctx context.Context, req *models.CreateCategoryRequest) (*models.CategoryResponse, error)
	UpdateCategory(ctx context.Context, id uuid.UUID, req *models.UpdateCategoryRequest) (*models.CategoryResponse, error)
	DeleteCategory(ctx context.Context, id uuid.UUID) error
	GetCategoryByID(ctx context.Context, id uuid.UUID) (*models.CategoryResponse, error)
	ListCategories(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.CategoryResponse, int, error)
}

type CategoryRepository interface {
	Create(ctx context.Context, category *entities.Category) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.Category, error)
	GetWithProducts(ctx context.Context, id uuid.UUID) (*entities.Category, error)
	GetByOrganization(ctx context.Context, organizationID uuid.UUID) ([]*entities.Category, error)
	GetByBusinessType(ctx context.Context, businessType string) ([]*entities.Category, error)
	Update(ctx context.Context, category *entities.Category) error
	Delete(ctx context.Context, id uuid.UUID) error
	List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Category, int64, error)
	Count(ctx context.Context, filters map[string]interface{}) (int64, error)
	GetByName(ctx context.Context, organizationID uuid.UUID, name string) (*entities.Category, error)
	ExistsByName(ctx context.Context, organizationID uuid.UUID, name string, excludeID *uuid.UUID) (bool, error)
}

type CategoryProcessorImpl struct {
	categoryRepo CategoryRepository
}

func NewCategoryProcessorImpl(categoryRepo CategoryRepository) *CategoryProcessorImpl {
	return &CategoryProcessorImpl{
		categoryRepo: categoryRepo,
	}
}

func (p *CategoryProcessorImpl) CreateCategory(ctx context.Context, req *models.CreateCategoryRequest) (*models.CategoryResponse, error) {
	// Check if category with same name exists for this organization
	exists, err := p.categoryRepo.ExistsByName(ctx, req.OrganizationID, req.Name, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to check category name uniqueness: %w", err)
	}
	if exists {
		return nil, fmt.Errorf("category with name '%s' already exists for this organization", req.Name)
	}

	// Map request to entity
	categoryEntity := mappers.CreateCategoryRequestToEntity(req)

	// Create category
	if err := p.categoryRepo.Create(ctx, categoryEntity); err != nil {
		return nil, fmt.Errorf("failed to create category: %w", err)
	}

	// Map entity to response model
	response := mappers.CategoryEntityToResponse(categoryEntity)
	return response, nil
}

func (p *CategoryProcessorImpl) UpdateCategory(ctx context.Context, id uuid.UUID, req *models.UpdateCategoryRequest) (*models.CategoryResponse, error) {
	// Get existing category
	existingCategory, err := p.categoryRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("category not found: %w", err)
	}

	// Check name uniqueness if name is being updated
	if req.Name != nil && *req.Name != existingCategory.Name {
		exists, err := p.categoryRepo.ExistsByName(ctx, existingCategory.OrganizationID, *req.Name, &id)
		if err != nil {
			return nil, fmt.Errorf("failed to check category name uniqueness: %w", err)
		}
		if exists {
			return nil, fmt.Errorf("category with name '%s' already exists for this organization", *req.Name)
		}
	}

	// Apply updates to entity
	mappers.UpdateCategoryEntityFromRequest(existingCategory, req)

	// Update category
	if err := p.categoryRepo.Update(ctx, existingCategory); err != nil {
		return nil, fmt.Errorf("failed to update category: %w", err)
	}

	// Map entity to response model
	response := mappers.CategoryEntityToResponse(existingCategory)
	return response, nil
}

func (p *CategoryProcessorImpl) DeleteCategory(ctx context.Context, id uuid.UUID) error {
	// Check if category exists
	_, err := p.categoryRepo.GetByID(ctx, id)
	if err != nil {
		return fmt.Errorf("category not found: %w", err)
	}

	// Check if category has products
	categoryWithProducts, err := p.categoryRepo.GetWithProducts(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to check category products: %w", err)
	}

	if len(categoryWithProducts.Products) > 0 {
		return fmt.Errorf("cannot delete category: it has %d products associated with it", len(categoryWithProducts.Products))
	}

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

	return nil
}

func (p *CategoryProcessorImpl) GetCategoryByID(ctx context.Context, id uuid.UUID) (*models.CategoryResponse, error) {
	categoryEntity, err := p.categoryRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("category not found: %w", err)
	}

	response := mappers.CategoryEntityToResponse(categoryEntity)
	return response, nil
}

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

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

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

	return responses, int(total), nil
}
