using AutoMapper; using Microsoft.EntityFrameworkCore; using LittleShop.Data; using LittleShop.DTOs; using LittleShop.Models; namespace LittleShop.Services; public class CustomerService : ICustomerService { private readonly LittleShopContext _context; private readonly IMapper _mapper; private readonly ILogger _logger; public CustomerService(LittleShopContext context, IMapper mapper, ILogger logger) { _context = context; _mapper = mapper; _logger = logger; } public async Task GetCustomerByIdAsync(Guid id) { var customer = await _context.Customers .Include(c => c.Orders) .FirstOrDefaultAsync(c => c.Id == id); if (customer == null) return null; var dto = _mapper.Map(customer); dto.DisplayName = customer.DisplayName; dto.CustomerType = customer.CustomerType; return dto; } public async Task GetCustomerByTelegramUserIdAsync(long telegramUserId) { var customer = await _context.Customers .Include(c => c.Orders) .FirstOrDefaultAsync(c => c.TelegramUserId == telegramUserId); if (customer == null) return null; var dto = _mapper.Map(customer); dto.DisplayName = customer.DisplayName; dto.CustomerType = customer.CustomerType; return dto; } public async Task CreateCustomerAsync(CreateCustomerDto createCustomerDto) { // Check if customer already exists var existingCustomer = await _context.Customers .FirstOrDefaultAsync(c => c.TelegramUserId == createCustomerDto.TelegramUserId); if (existingCustomer != null) { throw new InvalidOperationException($"Customer with Telegram ID {createCustomerDto.TelegramUserId} already exists"); } var customer = _mapper.Map(createCustomerDto); customer.Id = Guid.NewGuid(); customer.CreatedAt = DateTime.UtcNow; customer.UpdatedAt = DateTime.UtcNow; customer.LastActiveAt = DateTime.UtcNow; customer.IsActive = true; // Set data retention date (default: 2 years after creation) customer.DataRetentionDate = DateTime.UtcNow.AddYears(2); _context.Customers.Add(customer); await _context.SaveChangesAsync(); _logger.LogInformation("Created new customer {CustomerId} for Telegram user {TelegramUserId}", customer.Id, customer.TelegramUserId); var dto = _mapper.Map(customer); dto.DisplayName = customer.DisplayName; dto.CustomerType = customer.CustomerType; return dto; } public async Task UpdateCustomerAsync(Guid id, UpdateCustomerDto updateCustomerDto) { var customer = await _context.Customers.FindAsync(id); if (customer == null) return null; _mapper.Map(updateCustomerDto, customer); customer.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Updated customer {CustomerId}", id); var dto = _mapper.Map(customer); dto.DisplayName = customer.DisplayName; dto.CustomerType = customer.CustomerType; return dto; } public async Task DeleteCustomerAsync(Guid id) { var customer = await _context.Customers.FindAsync(id); if (customer == null) return false; // Instead of hard delete, mark as inactive for data retention compliance customer.IsActive = false; customer.DataRetentionDate = DateTime.UtcNow.AddDays(30); // Delete in 30 days customer.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Marked customer {CustomerId} for deletion", id); return true; } public async Task> GetAllCustomersAsync() { var customers = await _context.Customers .Where(c => c.IsActive) .Include(c => c.Orders) .OrderByDescending(c => c.LastActiveAt) .ToListAsync(); return customers.Select(c => { var dto = _mapper.Map(c); dto.DisplayName = c.DisplayName; dto.CustomerType = c.CustomerType; return dto; }); } public async Task> SearchCustomersAsync(string searchTerm) { var query = _context.Customers .Where(c => c.IsActive) .Include(c => c.Orders) .AsQueryable(); if (!string.IsNullOrWhiteSpace(searchTerm)) { searchTerm = searchTerm.ToLower(); query = query.Where(c => c.TelegramUsername.ToLower().Contains(searchTerm) || c.TelegramDisplayName.ToLower().Contains(searchTerm) || c.TelegramFirstName.ToLower().Contains(searchTerm) || c.TelegramLastName.ToLower().Contains(searchTerm) || (c.Email != null && c.Email.ToLower().Contains(searchTerm))); } var customers = await query .OrderByDescending(c => c.LastActiveAt) .Take(50) // Limit search results .ToListAsync(); return customers.Select(c => { var dto = _mapper.Map(c); dto.DisplayName = c.DisplayName; dto.CustomerType = c.CustomerType; return dto; }); } public async Task GetOrCreateCustomerAsync(long telegramUserId, string displayName, string username = "", string firstName = "", string lastName = "") { // Try to find existing customer var customer = await _context.Customers .Include(c => c.Orders) .FirstOrDefaultAsync(c => c.TelegramUserId == telegramUserId); if (customer != null) { // Update customer information if provided bool updated = false; if (!string.IsNullOrEmpty(displayName) && customer.TelegramDisplayName != displayName) { customer.TelegramDisplayName = displayName; updated = true; } if (!string.IsNullOrEmpty(username) && customer.TelegramUsername != username) { customer.TelegramUsername = username; updated = true; } if (!string.IsNullOrEmpty(firstName) && customer.TelegramFirstName != firstName) { customer.TelegramFirstName = firstName; updated = true; } if (!string.IsNullOrEmpty(lastName) && customer.TelegramLastName != lastName) { customer.TelegramLastName = lastName; updated = true; } customer.LastActiveAt = DateTime.UtcNow; if (updated) { customer.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Updated existing customer {CustomerId} information", customer.Id); } else { await _context.SaveChangesAsync(); // Just update LastActiveAt } var existingDto = _mapper.Map(customer); existingDto.DisplayName = customer.DisplayName; existingDto.CustomerType = customer.CustomerType; return existingDto; } // Create new customer customer = new Customer { Id = Guid.NewGuid(), TelegramUserId = telegramUserId, TelegramUsername = username, TelegramDisplayName = displayName, TelegramFirstName = firstName, TelegramLastName = lastName, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow, LastActiveAt = DateTime.UtcNow, DataRetentionDate = DateTime.UtcNow.AddYears(2), IsActive = true, AllowOrderUpdates = true, AllowMarketing = false, Language = "en", Timezone = "UTC" }; _context.Customers.Add(customer); await _context.SaveChangesAsync(); _logger.LogInformation("Created new customer {CustomerId} for Telegram user {TelegramUserId} ({DisplayName})", customer.Id, telegramUserId, displayName); var dto = _mapper.Map(customer); dto.DisplayName = customer.DisplayName; dto.CustomerType = customer.CustomerType; return dto; } public async Task UpdateCustomerMetricsAsync(Guid customerId) { var customer = await _context.Customers .Include(c => c.Orders) .FirstOrDefaultAsync(c => c.Id == customerId); if (customer == null) return; customer.UpdateMetrics(); await _context.SaveChangesAsync(); _logger.LogInformation("Updated metrics for customer {CustomerId}", customerId); } public async Task BlockCustomerAsync(Guid customerId, string reason) { var customer = await _context.Customers.FindAsync(customerId); if (customer == null) return false; customer.IsBlocked = true; customer.BlockReason = reason; customer.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogWarning("Blocked customer {CustomerId} - Reason: {Reason}", customerId, reason); return true; } public async Task UnblockCustomerAsync(Guid customerId) { var customer = await _context.Customers.FindAsync(customerId); if (customer == null) return false; customer.IsBlocked = false; customer.BlockReason = null; customer.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Unblocked customer {CustomerId}", customerId); return true; } }