package customer

import (
	"context"
	"errors"
	"time"
	"tm/pkg/logger"
	mongopkg "tm/pkg/mongo"

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

// Repository defines the interface for customer data operations
type Repository interface {
	Create(ctx context.Context, customer *Customer) 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)
	GetByCompanyName(ctx context.Context, companyName string) (*Customer, error)
	GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error)
	GetByTaxID(ctx context.Context, taxID string) (*Customer, error)
	GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error)
	Update(ctx context.Context, customer *Customer) error
	Delete(ctx context.Context, id string) error
	List(ctx context.Context, limit, offset int) ([]*Customer, error)
	Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error)
	CountByCompanyID(ctx context.Context, companyID string) (int64, error)
	CountByType(ctx context.Context, customerType CustomerType) (int64, error)
	CountByStatus(ctx context.Context, status CustomerStatus) (int64, error)
	UpdateStatus(ctx context.Context, id string, status CustomerStatus) error
	UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error
}

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

// NewCustomerRepository creates a new customer repository
func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository {
	// Create indexes using the ORM's index management
	indexes := []mongopkg.Index{
		*mongopkg.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}),
		*mongopkg.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
		*mongopkg.NewIndex("company_name_idx", bson.D{{Key: "company_name", Value: 1}}),
		*mongopkg.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}),
		*mongopkg.NewIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}),
		*mongopkg.NewIndex("company_ids_idx", bson.D{{Key: "company_ids", Value: 1}}), // Index for CompanyIDs array
		*mongopkg.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}),
		*mongopkg.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
		*mongopkg.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}),
		*mongopkg.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}),
		*mongopkg.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}),
		*mongopkg.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}),
		*mongopkg.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}),
		*mongopkg.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
		*mongopkg.CreateTextIndex("search_idx", "first_name", "last_name", "full_name", "company_name", "email", "username"),
	}

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

	// Create ORM repository
	ormRepo := mongopkg.NewRepository[Customer](mongoManager.GetCollection("customers"), logger)

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

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

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

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

	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, mongopkg.ErrDocumentNotFound) {
			return nil, errors.New("customer not found")
		}
		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, mongopkg.ErrDocumentNotFound) {
			return nil, errors.New("customer not found")
		}
		r.logger.Error("Failed to get customer by email", map[string]interface{}{
			"error": err.Error(),
			"email": email,
		})
		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, mongopkg.ErrDocumentNotFound) {
			return nil, errors.New("customer not found")
		}
		r.logger.Error("Failed to get customer by username", map[string]interface{}{
			"error":    err.Error(),
			"username": username,
		})
		return nil, err
	}

	return customer, nil
}

// GetByCompanyName retrieves a customer by company name
func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) {
	filter := bson.M{"company_name": companyName}
	customer, err := r.ormRepo.FindOne(ctx, filter)
	if err != nil {
		if errors.Is(err, mongopkg.ErrDocumentNotFound) {
			return nil, errors.New("customer not found")
		}
		r.logger.Error("Failed to get customer by company name", map[string]interface{}{
			"error":        err.Error(),
			"company_name": companyName,
		})
		return nil, err
	}

	return customer, nil
}

// GetByRegistrationNumber retrieves a customer by registration number
func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) {
	filter := bson.M{"registration_number": registrationNumber}
	customer, err := r.ormRepo.FindOne(ctx, filter)
	if err != nil {
		if errors.Is(err, mongopkg.ErrDocumentNotFound) {
			return nil, errors.New("customer not found")
		}
		r.logger.Error("Failed to get customer by registration number", map[string]interface{}{
			"error":               err.Error(),
			"registration_number": registrationNumber,
		})
		return nil, err
	}

	return customer, nil
}

// GetByTaxID retrieves a customer by tax ID
func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) {
	filter := bson.M{"tax_id": taxID}
	customer, err := r.ormRepo.FindOne(ctx, filter)
	if err != nil {
		if errors.Is(err, mongopkg.ErrDocumentNotFound) {
			return nil, errors.New("customer not found")
		}
		r.logger.Error("Failed to get customer by tax ID", map[string]interface{}{
			"error":  err.Error(),
			"tax_id": taxID,
		})
		return nil, err
	}

	return customer, nil
}

// GetByCompanyID retrieves customers by company ID with pagination
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) {
	// Build pagination
	pagination := mongopkg.NewPaginationBuilder().
		Limit(limit).
		Skip(offset).
		SortDesc("created_at").
		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, pagination)
	if err != nil {
		r.logger.Error("Failed to get customers by company ID", map[string]interface{}{
			"error":      err.Error(),
			"company_id": companyID,
			"limit":      limit,
			"offset":     offset,
		})
		return nil, err
	}

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

	return customers, 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
}

// Delete deletes a customer (soft delete by setting status to inactive)
func (r *customerRepository) Delete(ctx context.Context, id string) error {
	// Get customer first
	customer, err := r.GetByID(ctx, id)
	if err != nil {
		return err
	}

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

	// Update in database
	err = r.ormRepo.Update(ctx, customer)
	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
}

// List retrieves customers with pagination
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) {
	// Build pagination
	pagination := mongopkg.NewPaginationBuilder().
		Limit(limit).
		Skip(offset).
		SortDesc("created_at").
		Build()

	// Only active customers by default
	filter := bson.M{"status": bson.M{"$ne": CustomerStatusInactive}}

	// Use ORM to find customers
	result, err := r.ormRepo.FindAll(ctx, filter, pagination)
	if err != nil {
		r.logger.Error("Failed to list customers", map[string]interface{}{
			"error":  err.Error(),
			"limit":  limit,
			"offset": offset,
		})
		return nil, err
	}

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

	return customers, nil
}

// Search retrieves customers with search and filters
func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) {
	// Build filter
	filter := bson.M{}

	if search != "" {
		filter["$text"] = bson.M{"$search": search}
	}

	if customerType != nil {
		filter["type"] = *customerType
	}

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

	if companyID != nil {
		filter["company_id"] = *companyID
	}

	if industry != nil {
		filter["industry"] = *industry
	}

	if isVerified != nil {
		filter["is_verified"] = *isVerified
	}

	if isCompliant != nil {
		filter["is_compliant"] = *isCompliant
	}

	if language != nil {
		filter["language"] = *language
	}

	if currency != nil {
		filter["currency"] = *currency
	}

	// Build pagination
	pagination := mongopkg.NewPaginationBuilder().
		Limit(limit).
		Skip(offset)

	// Set sort
	if sortBy != "" {
		if sortOrder == "desc" {
			pagination.SortDesc(sortBy)
		} else {
			pagination.SortAsc(sortBy)
		}
	} else {
		pagination.SortDesc("created_at") // Default sort
	}

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

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

	return customers, nil
}

// CountByCompanyID counts customers by company ID
func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) {
	filter := bson.M{
		"company_id": companyID,
		"status":     bson.M{"$ne": CustomerStatusInactive},
	}

	count, err := r.ormRepo.Count(ctx, filter)
	if err != nil {
		r.logger.Error("Failed to count customers by company ID", map[string]interface{}{
			"error":      err.Error(),
			"company_id": companyID,
		})
		return 0, err
	}

	return count, nil
}

// CountByType counts customers by type
func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) {
	filter := bson.M{
		"type":   customerType,
		"status": bson.M{"$ne": CustomerStatusInactive},
	}

	count, err := r.ormRepo.Count(ctx, filter)
	if err != nil {
		r.logger.Error("Failed to count customers by type", map[string]interface{}{
			"error":         err.Error(),
			"customer_type": customerType,
		})
		return 0, err
	}

	return count, nil
}

// CountByStatus counts customers by status
func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) {
	filter := bson.M{"status": status}

	count, err := r.ormRepo.Count(ctx, filter)
	if err != nil {
		r.logger.Error("Failed to count customers by status", map[string]interface{}{
			"error":  err.Error(),
			"status": status,
		})
		return 0, err
	}

	return count, 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
}

// UpdateVerification updates customer verification status
func (r *customerRepository) UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error {
	// Get customer first
	customer, err := r.GetByID(ctx, id)
	if err != nil {
		return err
	}

	// Update verification fields
	customer.IsVerified = isVerified
	customer.IsCompliant = isCompliant
	customer.ComplianceNotes = complianceNotes
	customer.SetUpdatedAt(time.Now().Unix())

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

	r.logger.Info("Customer verification updated successfully", map[string]interface{}{
		"customer_id":  id,
		"is_verified":  isVerified,
		"is_compliant": isCompliant,
	})

	return nil
}
