using Fitness_core_BE.Helpers; using Fitness_core_BE.Infra.UoW; using Fitness_core_BE.Interfaces; using Fitness_core_BE.Interfaces.Services; using Fitness_core_BE.Models; using System.Net.Sockets; namespace Fitness_core_BE.Services.Ticket { public class TicketService : ITicketService { private readonly ITicketRepository _repository; private readonly UserRoleHelper _userRoleHelper; private readonly IUnitOfWork _uow; private readonly IActivityLogService _activityLogService; public TicketService(ITicketRepository repository, UserRoleHelper userRoleHelper, IUnitOfWork uow, IActivityLogService activityLogService) { _repository = repository; _userRoleHelper = userRoleHelper; _uow = uow; _activityLogService = activityLogService; } public async Task GetTicketList(object payload, HttpContext httpContext) { try { var tickets = await _repository.GetTicketListAsync(); return tickets; } catch { throw; } } public async Task GetTicketByUserId(object payload, HttpContext httpContext) { try { // Guid userId = Guid.TryParse(httpContext.Items["UserId"]?.ToString(), out var parsedUserId) // ? parsedUserId // : throw new UnauthorizedAccessException("User ID not found in context"); // Guid userId = Guid.Parse("a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d"); // Testing purposes Guid userId = DataExtractor.GetUserIdFromContext(httpContext); var tickets = await _repository.GetTicketByUserIdAsync(userId); return tickets; } catch { throw; } } public async Task GetTicketById(object payload, HttpContext httpContext) { try { var data = DeserializePayload.Deserialize(payload); Guid ticketId = Guid.Parse(data["ticketId"].GetString()!); var ticket = await _repository.GetByTicketIdAsync(ticketId); if (ticket == null) throw new KeyNotFoundException($"Ticket with ID {ticketId} not found"); if (ticket.UserId != DataExtractor.GetUserIdFromContext(httpContext)) throw new UnauthorizedAccessException("You do not have permission to view this ticket."); return ticket; } catch { throw; } } public async Task GetTicketCategoryList(object payload, HttpContext httpContext) { try { var categories = await _repository.GetTicketCategoryListAsync(); return categories; } catch { throw; } } public async Task CreateTicket(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); // Parse userId from context // Guid userId = Guid.TryParse(httpContext.Items["UserId"]?.ToString(), out var parsedUserId) // ? parsedUserId // : throw new UnauthorizedAccessException("User ID not found in context"); // Guid userId = Guid.Parse("a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d"); // Testing purposes Guid userId = DataExtractor.GetUserIdFromContext(httpContext); var ticket = new Fitness_core_BE.Models.Ticket { MainReason = data["mainReason"].GetString()!, FKTicketCategoryId = data.ContainsKey("ticketCategoryId") && !string.IsNullOrEmpty(data["ticketCategoryId"].GetString()) ? Guid.Parse(data["ticketCategoryId"].GetString()!) : (Guid?)null, TicketStatus = data.ContainsKey("ticketStatus") && !string.IsNullOrEmpty(data["ticketStatus"].GetString()) ? Enum.Parse(data["ticketStatus"].GetString()!) : Fitness_core_BE.Models.Enums.TicketStatusEnum.Pending, FKUserId = userId, CreateDate = DateTime.UtcNow }; await _repository.CreateTicketAsync(ticket); await _uow.CommitAsync(); // Log activity - non-blocking, failures won't crash the operation try { await _activityLogService.LogActivity(new { description = $"Ticket created with ID {ticket.TicketId}" }, httpContext); } catch (Exception logEx) { Console.WriteLine($"Activity log failed (non-critical): {logEx.Message}"); } return ticket; } catch { await _uow.RollbackAsync(); throw; } } public async Task UpdateTicket(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); Guid ticketId = Guid.Parse(data["ticketId"].GetString()!); var ticket = await _repository.GetByTicketIdAsync(ticketId) ?? throw new KeyNotFoundException($"Ticket with ID {ticketId} not found."); Guid userId = DataExtractor.GetUserIdFromContext(httpContext); if (ticket.UserId != userId) throw new UnauthorizedAccessException("You do not have permission to update this ticket."); ticket.MainReason = data.ContainsKey("mainReason") && !string.IsNullOrEmpty(data["mainReason"].GetString()) ? data["mainReason"].GetString()! : ticket.MainReason; ticket.TicketCategoryId = data.ContainsKey("ticketCategoryId") && !string.IsNullOrEmpty(data["ticketCategoryId"].GetString()) ? Guid.Parse(data["ticketCategoryId"].GetString()!) : ticket.TicketCategoryId; await _repository.UpdateTicketAsync(ticket); await _uow.CommitAsync(); return ticket; } catch { await _uow.RollbackAsync(); throw; } } public async Task SendMessage(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); // Parse userId from context // Guid userId = Guid.TryParse(httpContext.Items["UserId"]?.ToString(), out var parsedUserId) // ? parsedUserId // : throw new UnauthorizedAccessException("User ID not found in context"); Guid userId = DataExtractor.GetUserIdFromContext(httpContext); Guid? adminId = await _userRoleHelper.GetAdminId(userId); bool isAdmin = adminId.HasValue; Guid ticketId = Guid.Parse(data["ticketId"].GetString()!); var ticket = await _repository.GetByTicketIdAsync(ticketId) ?? throw new KeyNotFoundException($"Ticket with ID {ticketId} not found."); if (!isAdmin && ticket.UserId != userId) throw new UnauthorizedAccessException("You do not have permission to message this ticket."); var ticketMessage = new TicketMessage { Message = data["message"].GetString()!, FKTicketId = ticketId, IsSentByAdmin = isAdmin, AttachmentURL = data.ContainsKey("attachmentURL") ? data["attachmentURL"].GetString() : null, FKDocumentId = DataExtractor.GetNullableInt(data, "documentId"), CreateDate = DateTime.UtcNow }; // First admin reply moves a ticket from Pending to Open — nothing previously // set this transition, so tickets sat in Pending forever once created. if (isAdmin) { if (ticket.TicketStatus == Models.Enums.TicketStatusEnum.Pending) { await _repository.UpdateTicketStatusAsync(ticketId, Models.Enums.TicketStatusEnum.Open); } } //Guid ticCatId = DataExtractor.GetRequiredGuid(data, "ticketCategoryId"); //Guid? TcatId = await _repository.GetTicketCatId("Chat"); //if (ticCatId == TcatId) //{ //} await _repository.SendMessageAsync(ticketMessage); await _uow.CommitAsync(); return ticketMessage; } catch { await _uow.RollbackAsync(); throw; } } public async Task SubmitTicketFeedback(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); Guid ticketId = Guid.Parse(data["ticketId"].GetString()!); int rating = DataExtractor.GetRequiredInt(data, "rating"); string? feedback = data.TryGetValue("feedback", out var feedbackContent) && !string.IsNullOrEmpty(feedbackContent.GetString()) ? DataExtractor.GetOptionalString(data, "feedback") : null; if (rating < 1 || rating > 5) throw new ArgumentException("Rating must be between 1 and 5."); var ticket = await _repository.GetByTicketIdAsync(ticketId) ?? throw new KeyNotFoundException($"Ticket with ID {ticketId} not found."); if (ticket.UserId != DataExtractor.GetUserIdFromContext(httpContext)) throw new UnauthorizedAccessException("You do not have permission to submit feedback for this ticket."); if (ticket.TicketStatus != Models.Enums.TicketStatusEnum.Closed) throw new InvalidOperationException("Feedback can only be submitted after the ticket is closed."); await _repository.SubmitTicketFeedbackAsync(ticketId, rating, feedback); await _uow.CommitAsync(); return new { message = $"The feedback for ticket with ID {ticketId} submitted successfully." }; } catch { await _uow.RollbackAsync(); throw; } } public async Task CloseTicket(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); Guid ticketId = Guid.Parse(data["ticketId"].GetString()!); var ticket = await _repository.GetByTicketIdAsync(ticketId) ?? throw new KeyNotFoundException($"Ticket with ID {ticketId} not found."); if (ticket.UserId != DataExtractor.GetUserIdFromContext(httpContext)) throw new UnauthorizedAccessException("You do not have permission to close this ticket."); await _repository.UpdateTicketStatusAsync(ticketId, Fitness_core_BE.Models.Enums.TicketStatusEnum.Closed); await _uow.CommitAsync(); var updatedTicket = await _repository.GetByTicketIdAsync(ticketId); if (updatedTicket == null) throw new KeyNotFoundException($"Ticket with ID {ticketId} not found after update"); return updatedTicket; } catch { await _uow.RollbackAsync(); throw; } } public async Task DeleteTicket(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); Guid ticketId = Guid.Parse(data["ticketId"].GetString()!); var ticket = await _repository.GetByTicketIdAsync(ticketId) ?? throw new KeyNotFoundException($"Ticket with ID {ticketId} not found."); Guid userId = DataExtractor.GetUserIdFromContext(httpContext); if (ticket.UserId != userId) throw new UnauthorizedAccessException("You do not have permission to delete this ticket."); await _repository.DeleteAsync(ticketId); await _uow.CommitAsync(); var deletedTicket = await _repository.GetByTicketIdAsync(ticketId); if (deletedTicket == null) return true; throw new ArgumentException($"Ticket with ID {ticketId} has not been deleted"); } catch { await _uow.RollbackAsync(); throw; } } } }