using FluentValidation;
using IWS5.Core.DTO;
using IWS5.Core.Enums;
using IWS5.Core.Exceptions;
using IWS5.Core.Helpers;
using IWS5.Core.Models;
using IWS5.Core.Models.Request;
using IWS5.Core.Services;
using IWS5.DatabaseAccess.Interfaces;
using IWS5.DatabaseAccess.IwsDataModels;
using Serilog;

namespace IWS5.Services;

public class NoteService(
    INoteRepository noteRepository,
    ILogger logger,
    IWorkItemReferenceRepository workItemReferenceRepository,
    IProcessRepository processRepository,
    IRightsRepository rightsRepository,
    IValidator<CreateNoteRequestItem> createNoteRequestItemValidator,
    IMessageProcessor messageProcessor)
    : INoteService
{
    public async Task<Response<NoteListDTO>> GetNotes(NotesRequest notesRequest)
    {
        logger.Information(
            "Starting notes retrieval for WorkItemReferenceId {WorkItemReferenceId} requested by User {UserName}",
            notesRequest.WorkItemReferenceId, notesRequest.UserName);

        try
        {
            var workItem = await workItemReferenceRepository.FindAsync(
                wReference => wReference.WorkItemReferenceId == notesRequest.WorkItemReferenceId,
                wRef => wRef.WorkItem);

            if (!string.IsNullOrEmpty(workItem.WorkItem.LockedBy) &&
                workItem.WorkItem.LockedBy != notesRequest.UserName)
            {
                logger.Error(
                    "User {UserName} is not allowed to view note values for WorkItemId {WorkItemId} because it is locked",
                    notesRequest.UserName, workItem.WorkItemId);
                throw new InvalidOperationException(
                    $"User {notesRequest.UserName} is not allowed to view note values for WorkItemId {workItem.WorkItemId} because it is locked");
            }

            var dataResponse = await noteRepository.ListAsync(note => note.WorkItemId == workItem.WorkItemId);

            logger.Information("Successfully retrieved notes data for WorkItemReferenceId {WorkItemReferenceId}",
                notesRequest.WorkItemReferenceId);
            var dataResult = new NoteListDTO
            {
                Notes = dataResponse.Select(note => MapToNoteDto(note)).OrderBy(note=>note.SubmitDate).ToArray()
            };

            return new Response<NoteListDTO>(dataResult, true, null);
        }
        catch (Exception ex)
        {
            logger.Error(ex,
                "Error occurred while retrieving notes for WorkItemReferenceId {WorkItemReferenceId} requested by User {UserName}",
                notesRequest.WorkItemReferenceId);
            throw;
        }
    }

    public async Task<Response<NoteListItem>> AddNote(CreateNoteRequest request)
    {
        await createNoteRequestItemValidator.ValidateAndThrowAsync(request.Note);
        var workItem =
            await workItemReferenceRepository.FindAsync(
                wReference => wReference.WorkItemReferenceId == request.Note.WorkItemId, wRef => wRef.WorkItem);

        if (!string.IsNullOrEmpty(workItem.WorkItem.LockedBy) && workItem.WorkItem.LockedBy != request.UserName)
        {
            logger.Error(
                "User {UserName} is not allowed to view note values for WorkItemId {WorkItemId} because it is locked",
                request.UserName, workItem.WorkItemId);
            throw new InvalidOperationException(
                $"User {request.UserName} is not allowed to view note values for WorkItemId {workItem.WorkItemId} because it is locked");
        }

        var process =
            await processRepository.FindAsync(process => process.ProcessId == workItem.WorkItem.ProcessId);
        var rights = await rightsRepository
            .ListUserRelatedRights(request.UserName, ObjectType.Process, ATRightAllowRight.AddNote, process.ObjectId);
        if (rights.Count == 0)
        {
            logger.Error("User {UserName} is not allowed to add notes", request.UserName);
            throw new ForbiddenAccessException($"User {request.UserName} is not allowed to add notes");
        }

        try
        {
            var noteContent = await messageProcessor.ProcessMessage(new MessageModel
            {
                Message = request.Note.Note,
                WorkItemId = workItem.WorkItemId,
                SourceUser = workItem.SourceUser?.ToString(),
                SourceQueueId = workItem.SourceQueueId.GetValueOrDefault(),
                EventId = workItem.EventId.GetValueOrDefault(),
                LastAction = workItem.LastAction?.ToString(),
            });

            var note = new Atnote
            {
                WorkItemId = workItem.WorkItemId,
                Submitter = request.UserName,
                DocumentId = request.Note.DocumentId,
                SubmitDate = DateTime.UtcNow,
                Note = noteContent
            };

            await noteRepository.AddAsync(note);
            await noteRepository.SaveChangesAsync();
            if (note.DocumentId != null)
            {
                logger.Information("Successfully added note data for Document {DocumentId}", note.DocumentId);
            }
            else
            {
                logger.Information("Successfully added note data for WorkItem {WorkItemId}", note.WorkItemId);
            }

            var noteResponse = MapToNoteDto(note);
            return new Response<NoteListItem>(noteResponse, true, null);
        }
        catch (Exception ex)
        {
            logger.Error(ex, "Error occurred while adding note requested by User {Submitter}", request.UserName);
            throw;
        }
    }
    
    private static NoteListItem MapToNoteDto(Atnote note)
    {
        return new NoteListItem
        {
            NoteId = note.NoteId,
            WorkItemId = note.WorkItemId,
            DocumentId = note.DocumentId,
            Submitter = note.Submitter,
            SubmitDate = note.SubmitDate,
            Note = note.Note
        };
    }
}