package company_category

import (
	"context"
	"errors"
	"time"
	"tm/pkg/logger"
	orm "tm/pkg/mongo"
	"tm/pkg/response"

	"go.mongodb.org/mongo-driver/v2/bson"
)

// Repository defines the interface for category data operations
type Repository interface {
	Create(ctx context.Context, category *Category) error
	Update(ctx context.Context, category *Category) error
	GetByID(ctx context.Context, id string) (*Category, error)
	GetByIDs(ctx context.Context, ids []string) ([]Category, error)
	GetByName(ctx context.Context, name string) (*Category, error)
	Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Category, int64, error)
	Delete(ctx context.Context, id string) error
	TogglePublish(ctx context.Context, id string) (bool, error)
}

// categoryRepository implements the Repository interface using the MongoDB ORM
type categoryRepository struct {
	ormRepo orm.Repository[Category]
	logger  logger.Logger
}

// NewCategoryRepository creates a new category repository
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
	// Create indexes using the ORM's index management
	indexes := []orm.Index{
		*orm.NewIndex("name_idx", bson.D{{Key: "name", Value: 1}}),
		*orm.NewIndex("published_idx", bson.D{{Key: "published", Value: 1}}),
	}

	// Create indexes
	err := mongoManager.CreateIndexes("company_categories", indexes)
	if err != nil {
		logger.Warn("Failed to create category indexes", map[string]interface{}{
			"error": err.Error(),
		})
	}

	// Create ORM repository
	ormRepo := orm.NewRepository[Category](mongoManager.GetCollection("company_categories"), logger)

	return &categoryRepository{
		ormRepo: ormRepo,
		logger:  logger,
	}
}

// Create creates a new category
func (r *categoryRepository) Create(ctx context.Context, category *Category) error {
	// Set created/updated timestamps using Unix timestamps
	now := time.Now().Unix()
	category.SetCreatedAt(now)

	// Use ORM to create category
	err := r.ormRepo.Create(ctx, category)
	if err != nil {
		r.logger.Error("Failed to create category", map[string]interface{}{
			"error": err.Error(),
			"name":  category.Name,
		})
		return err
	}

	r.logger.Info("Category created successfully", map[string]interface{}{
		"category_id": category.ID,
		"name":        category.Name,
		"published":   category.Published,
	})

	return nil
}

// GetByID retrieves a category by ID
func (r *categoryRepository) GetByID(ctx context.Context, id string) (*Category, error) {
	category, err := r.ormRepo.FindByID(ctx, id)
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, errors.New("category not found")
		}
		r.logger.Error("Failed to get category by ID", map[string]interface{}{
			"error":       err.Error(),
			"category_id": id,
		})
		return nil, err
	}

	return category, nil
}

// GetByIDs retrieves categories by IDs
func (r *categoryRepository) GetByIDs(ctx context.Context, ids []string) ([]Category, error) {
	objectIDs := make([]bson.ObjectID, len(ids))
	for i, id := range ids {
		objectID, err := bson.ObjectIDFromHex(id)
		if err != nil {
			return nil, err
		}
		objectIDs[i] = objectID
	}

	filter := bson.M{"_id": bson.M{"$in": objectIDs}}

	paginationBuilder := orm.NewPaginationBuilder().
		Build()

	result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
	if err != nil {
		return nil, err
	}

	return result.Items, nil
}

// GetByName retrieves a category by name
func (r *categoryRepository) GetByName(ctx context.Context, name string) (*Category, error) {
	category, err := r.ormRepo.FindOne(ctx, bson.M{"name": name})
	if err != nil {
		return nil, err
	}
	return category, nil
}

// Update updates a category
func (r *categoryRepository) Update(ctx context.Context, category *Category) error {
	// Set updated timestamp using Unix timestamp
	category.SetUpdatedAt(time.Now().Unix())

	// Use ORM to update category
	err := r.ormRepo.Update(ctx, category)
	if err != nil {
		r.logger.Error("Failed to update category", map[string]interface{}{
			"error":       err.Error(),
			"category_id": category.ID,
		})
		return err
	}

	r.logger.Info("Category updated successfully", map[string]interface{}{
		"category_id": category.ID,
		"name":        category.Name,
	})

	return nil
}

// Delete deletes a category
func (r *categoryRepository) Delete(ctx context.Context, id string) error {
	// Get category first
	_, err := r.GetByID(ctx, id)
	if err != nil {
		return err
	}

	// Delete from database
	err = r.ormRepo.Delete(ctx, id)
	if err != nil {
		r.logger.Error("Failed to delete category", map[string]interface{}{
			"error":       err.Error(),
			"category_id": id,
		})
		return err
	}

	r.logger.Info("Category deleted successfully", map[string]interface{}{
		"category_id": id,
	})

	return nil
}

// Search retrieves categories with search and filters
func (r *categoryRepository) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Category, int64, error) {
	// Build filter
	filter := bson.M{}

	if form.Search != nil && *form.Search != "" {
		// Use regex search for case-insensitive partial matching
		searchRegex := bson.M{
			"$regex":   *form.Search,
			"$options": "i", // case-insensitive
		}
		filter["$or"] = []bson.M{
			{"name": searchRegex},
			{"description": searchRegex},
		}
	}

	if form.Published != nil {
		filter["published"] = *form.Published
	}

	// Build sort
	sort := "created_at"
	if pagination.SortBy != "" {
		sort = pagination.SortBy
	}

	sortOrder := "desc"
	if pagination.SortOrder != "" {
		sortOrder = pagination.SortOrder
	}

	// Build pagination
	paginationBuilder := orm.NewPaginationBuilder().
		Limit(pagination.Limit).
		Skip(pagination.Offset).
		SortBy(sort, sortOrder).
		Build()

	// Use ORM to find categories
	result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
	if err != nil {
		r.logger.Error("Failed to search categories", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, 0, err
	}

	return result.Items, result.TotalCount, nil
}

// TogglePublish toggles category publish status
func (r *categoryRepository) TogglePublish(ctx context.Context, id string) (bool, error) {
	// Get category first
	category, err := r.GetByID(ctx, id)
	if err != nil {
		return false, err
	}

	// Update publish status
	category.Published = !category.Published
	now := time.Now().Unix()
	category.SetUpdatedAt(now)

	// Set published_at timestamp if publishing
	category.PublishedAt = &now

	// Update in database
	err = r.ormRepo.Update(ctx, category)
	if err != nil {
		r.logger.Error("Failed to toggle category publish status", map[string]interface{}{
			"error":       err.Error(),
			"category_id": id,
			"published":   category.Published,
		})
		return false, err
	}

	r.logger.Info("Category publish status updated successfully", map[string]interface{}{
		"category_id": id,
		"published":   category.Published,
	})

	return category.Published, nil
}
