package customer

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

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

// Repository defines the interface for customer data operations
type Repository interface {
	Login(ctx context.Context, id, deviceToken string) error
	Register(ctx context.Context, customer *Customer) error
	Update(ctx context.Context, customer *Customer) error
	UpdatePasswordReset(ctx context.Context, id, hashedPassword, updatedBy string) error
	UpdateStatus(ctx context.Context, id string, status CustomerStatus) error
	GetByID(ctx context.Context, id string) (*Customer, error)
	GetByEmail(ctx context.Context, email string) (*Customer, error)
	GetByUsername(ctx context.Context, username string) (*Customer, error)
	GetByPhone(ctx context.Context, phone string) (*Customer, error)
	GetByIDs(ctx context.Context, ids []string) ([]Customer, error)
	GetByRole(ctx context.Context, role CustomerRole) ([]Customer, error)
	GetByCompanies(ctx context.Context, companies []string) ([]Customer, error)
	GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error)
	Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) (*orm.PaginatedResult[Customer], error)
	Delete(ctx context.Context, id string) error
}

// customerRepository implements the Repository interface using the MongoDB ORM
type customerRepository struct {
	ormRepo    orm.Repository[Customer]
	collection *mongopkg.Collection
	logger     logger.Logger
}

func collection() string {
	return "customers"
}

// NewCustomerRepository creates a new customer repository
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
	// Create indexes using the ORM's index management
	indexes := []orm.Index{
		*orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
		*orm.CreateTextIndex("search_idx", "email", "username"),
		*orm.CreateUniqueIndex("phone_idx", bson.D{{Key: "phone", Value: 1}}).
			WithPartialFilterExpression(bson.M{"phone": bson.M{"$type": "string", "$ne": ""}}),
	}

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

	// Create ORM repository
	collection := mongoManager.GetCollection(collection())
	ormRepo := orm.NewRepository[Customer](collection, logger)

	return &customerRepository{
		ormRepo:    ormRepo,
		collection: collection,
		logger:     logger,
	}
}

// Register creates a new customer
func (r *customerRepository) Register(ctx context.Context, customer *Customer) error {
	// Set created/updated timestamps using Unix timestamps
	now := time.Now().Unix()
	customer.SetCreatedAt(now)

	// Use ORM to create customer
	err := r.ormRepo.Create(ctx, customer)
	if err != nil {
		r.logger.Error("Failed to register customer", map[string]interface{}{
			"error":    err.Error(),
			"email":    customer.Email,
			"username": customer.Username,
		})
		return err
	}

	r.logger.Info("Customer registered successfully", map[string]interface{}{
		"customer_id": customer.ID,
		"email":       customer.Email,
		"type":        customer.Type,
	})

	return nil
}

// Login updates the last login timestamp
func (r *customerRepository) Login(ctx context.Context, id, deviceToken string) error {
	// Get customer first
	customer, err := r.GetByID(ctx, id)
	if err != nil {
		return err
	}

	// append device token to customer if not exists
	// if !slices.Contains(customer.DeviceToken, deviceToken) {
	customer.DeviceToken = []string{deviceToken}
	// }

	// Update last login timestamp
	customer.LastLoginAt = &[]int64{time.Now().Unix()}[0]
	customer.SetUpdatedAt(time.Now().Unix())

	// Update in database
	err = r.ormRepo.Update(ctx, customer)
	if err != nil {
		r.logger.Error("Failed to update last login", map[string]interface{}{
			"error":       err.Error(),
			"customer_id": id,
		})
		return err
	}

	r.logger.Info("Last login updated successfully", map[string]interface{}{
		"customer_id": id,
	})

	return nil
}

// GetByID retrieves a customer by ID
func (r *customerRepository) GetByID(ctx context.Context, id string) (*Customer, error) {
	customer, err := r.ormRepo.FindByID(ctx, id)
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, ErrCustomerNotFound
		}
		r.logger.Error("Failed to get customer by ID", map[string]interface{}{
			"error":       err.Error(),
			"customer_id": id,
		})
		return nil, err
	}

	return customer, nil
}

// GetByEmail retrieves a customer by email
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) {
	filter := bson.M{"email": email}
	customer, err := r.ormRepo.FindOne(ctx, filter)
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, ErrCustomerNotFound
		}
		r.logger.Error("Failed to get customer by email", map[string]interface{}{
			"error": err.Error(),
			"email": email,
		})
		return nil, err
	}

	return customer, nil
}

// GetByPhone retrieves a customer by phone number
func (r *customerRepository) GetByPhone(ctx context.Context, phone string) (*Customer, error) {
	filter := bson.M{"phone": phone}
	customer, err := r.ormRepo.FindOne(ctx, filter)
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, ErrCustomerNotFound
		}
		r.logger.Error("Failed to get customer by phone", map[string]interface{}{
			"error": err.Error(),
			"phone": phone,
		})
		return nil, err
	}

	return customer, nil
}

// GetByUsername retrieves a customer by username
func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) {
	filter := bson.M{"username": username}
	customer, err := r.ormRepo.FindOne(ctx, filter)
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, ErrCustomerNotFound
		}
		r.logger.Error("Failed to get customer by username", map[string]interface{}{
			"error":    err.Error(),
			"username": username,
		})
		return nil, err
	}

	return customer, nil
}

// GetByIDs retrieves customers by IDs
func (r *customerRepository) GetByIDs(ctx context.Context, ids []string) ([]Customer, error) {
	objectIDs := make([]bson.ObjectID, 0)
	for _, id := range ids {
		objectID, err := bson.ObjectIDFromHex(id)
		if err != nil {
			return nil, err
		}
		objectIDs = append(objectIDs, objectID)
	}

	customers, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": objectIDs}}, orm.NewPaginationBuilder().Limit(len(ids)).Build())
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, errors.New("customers not found")
		}
		r.logger.Error("Failed to get customers by IDs", map[string]interface{}{
			"error": err.Error(),
			"ids":   ids,
		})
		return nil, err
	}
	return customers.Items, nil
}

// GetByRole retrieves customers by role
func (r *customerRepository) GetByRole(ctx context.Context, role CustomerRole) ([]Customer, error) {
	customers, err := r.ormRepo.FindAll(ctx, bson.M{"role": role}, orm.NewPaginationBuilder().Build())
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, errors.New("customers not found")
		}
		r.logger.Error("Failed to get customers by role", map[string]interface{}{
			"error": err.Error(),
			"role":  role,
		})
		return nil, err
	}
	return customers.Items, nil
}

// GetByCompanies retrieves customers by companies

func (r *customerRepository) GetByCompanies(ctx context.Context, companies []string) ([]Customer, error) {
	companyIDs := make([]bson.ObjectID, 0)
	for _, company := range companies {
		id, err := bson.ObjectIDFromHex(company)
		if err != nil {
			return nil, err
		}
		companyIDs = append(companyIDs, id)
	}

	customers, err := r.ormRepo.FindAll(ctx, bson.M{"companies": bson.M{"$in": companyIDs}}, orm.NewPaginationBuilder().Build())
	if err != nil {
		return nil, err
	}
	return customers.Items, nil
}

// GetByCompanyID retrieves customers by company ID with pagination
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error) {
	// Build pagination
	paginationBuilder := orm.NewPaginationBuilder().
		Limit(pagination.Limit).
		Skip(pagination.Offset).
		SortBy(pagination.SortBy, pagination.SortOrder).
		Build()

	// Filter by company ID and active status
	filter := bson.M{
		"company_id": companyID,
		"status":     bson.M{"$ne": CustomerStatusInactive},
	}

	// Use ORM to find customers
	result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
	if err != nil {
		r.logger.Error("Failed to get customers by company ID", map[string]interface{}{
			"error":      err.Error(),
			"company_id": companyID,
			"limit":      pagination.Limit,
			"offset":     pagination.Offset,
		})
		return nil, 0, err
	}

	// Convert []Customer to []*Customer
	customers := make([]*Customer, len(result.Items))
	for i := range result.Items {
		customers[i] = &result.Items[i]
	}

	return customers, result.TotalCount, nil
}

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

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

	r.logger.Info("Customer updated successfully", map[string]interface{}{
		"customer_id": customer.ID,
		"email":       customer.Email,
	})

	return nil
}

// UpdatePasswordReset atomically persists a new password hash and audit fields.
func (r *customerRepository) UpdatePasswordReset(ctx context.Context, id, hashedPassword, updatedBy string) error {
	now := time.Now().Unix()
	updateDoc := bson.M{
		"$set": bson.M{
			"password":   hashedPassword,
			"updated_at": now,
			"updated_by": updatedBy,
		},
	}

	objectID, err := bson.ObjectIDFromHex(id)
	if err != nil {
		return fmt.Errorf("invalid id format: %w", err)
	}

	result, err := r.collection.UpdateOne(ctx, bson.M{"_id": objectID}, updateDoc)
	if err != nil {
		r.logger.Error("Failed to update password reset", map[string]interface{}{
			"error":       err.Error(),
			"customer_id": id,
		})
		return err
	}
	if result.MatchedCount == 0 {
		return ErrCustomerNotFound
	}

	return nil
}

// Delete deletes a customer (hard delete)
func (r *customerRepository) Delete(ctx context.Context, id string) error {
	// Get customer first
	_, err := r.GetByID(ctx, id)
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return errors.New("customer not found")
		}
		r.logger.Error("Failed to delete customer", map[string]interface{}{
			"error":       err.Error(),
			"customer_id": id,
		})

		return err
	}

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

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

	return nil
}

// Search retrieves customers with search and filters
func (r *customerRepository) Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) (*orm.PaginatedResult[Customer], error) {
	filter := bson.M{}

	if form.Search != nil {
		search := strings.TrimSpace(*form.Search)
		if search != "" {
			filter["$text"] = bson.M{"$search": search}
		}
	}

	if form.FullName != nil {
		fullName := strings.TrimSpace(*form.FullName)
		if fullName != "" {
			filter["full_name"] = bson.M{
				"$regex":   regexp.QuoteMeta(fullName),
				"$options": "i",
			}
		}
	}

	if form.Email != nil {
		email := strings.TrimSpace(*form.Email)
		if email != "" {
			filter["email"] = bson.M{
				"$regex":   regexp.QuoteMeta(email),
				"$options": "i",
			}
		}
	}

	if form.Type != nil {
		filter["type"] = *form.Type
	}

	if form.Status != nil {
		filter["status"] = *form.Status
	}

	if form.Role != nil {
		filter["role"] = *form.Role
	}

	if form.CompanyID != nil {
		companyID := strings.TrimSpace(*form.CompanyID)
		if companyID != "" {
			filter["companies"] = companyID
		}
	}

	mongoPagination, err := orm.BuildListPagination(
		pagination.Limit,
		pagination.Offset,
		pagination.Cursor,
		pagination.SortBy,
		pagination.SortOrder,
		filter,
		orm.ListPaginationOptions{},
	)
	if err != nil {
		return nil, err
	}

	result, err := r.ormRepo.FindAll(ctx, filter, mongoPagination)
	if err != nil {
		r.logger.Error("Failed to search customers", map[string]interface{}{
			"error":  err.Error(),
			"search": form.Search,
		})
		return nil, err
	}

	return result, nil
}

// UpdateStatus updates customer status
func (r *customerRepository) UpdateStatus(ctx context.Context, id string, status CustomerStatus) error {
	// Get customer first
	customer, err := r.GetByID(ctx, id)
	if err != nil {
		return err
	}

	// Update status
	customer.Status = status
	customer.SetUpdatedAt(time.Now().Unix())

	// Update in database
	err = r.ormRepo.Update(ctx, customer)
	if err != nil {
		r.logger.Error("Failed to update customer status", map[string]interface{}{
			"error":       err.Error(),
			"customer_id": id,
			"status":      status,
		})
		return err
	}

	r.logger.Info("Customer status updated successfully", map[string]interface{}{
		"customer_id": id,
		"status":      status,
	})

	return nil
}
