using IWS5.Core.Helpers;
using IWS5.Core.Constants;
using IWS5.Core.Models;
using IWS5.Core.Models.TemplateModels;
using IWS5.DatabaseAccess.Interfaces;
using IWS5.Core.Enums;

namespace IWS5.Services.Helpers;

public class MessageProcessor(ISystemParameterRepository systemParameterRepository,
    IWorkItemReferenceRepository workItemReferenceRepository,
    IExceptionNoteRepository exceptionNoteRepository,
    IUserRepository userRepository,
    IUserPropertyRepository userPropertyRepository,
    IEventDefinitionRepository eventDefinitionRepository,
    IEventTypeRepository eventTypeRepository,
    IProcessRepository processRepository,
    IFieldDefinitionRepository fieldDefinitionRepository,
    IFieldInstanceStringRepository fieldInstanceStringRepository,
    IFieldInstanceOptionRepository fieldInstanceOptionRepository) : IMessageProcessor
{
    const string noDataMessage = "No data";

    public async Task<string> ProcessMessage(MessageModel messageModel)
    {
        if (string.IsNullOrEmpty(messageModel.Message))
        {
            return messageModel.Message;
        }

        messageModel.Message = ReplaceWorkItemVariables(messageModel);

        bool needsDatabaseReplacement = NeedsDatabaseReplacement(messageModel.Message);

        if (needsDatabaseReplacement)
        {
            messageModel.Message = await ReplaceWorkItemDataVariables(messageModel);
            messageModel.Message = await ReplaceWorkItemReferenceVariables(messageModel);
            messageModel.Message = await ReplaceUserVariables(messageModel);
            messageModel.Message = await ReplaceUserPropertyVariables(messageModel);
            messageModel.Message = await ReplaceProcessVariables(messageModel);
            messageModel.Message = await ReplaceEventVariables(messageModel);
            messageModel.Message = await ReplaceCustomFieldVariables(messageModel);
            messageModel.Message = await ReplaceExceptionNoteVariables(messageModel);
            messageModel.Message = await ReplaceUrlVariables(messageModel);
        }

        return messageModel.Message;
    }

    private bool NeedsDatabaseReplacement(string message)
    {
        return PDVariables.WorkItemDataVariables.Any(v => message.Contains("{{" + v + "}}")) ||
               PDVariables.WorkItemReferenceVariables.Any(v => message.Contains("{{" + v + "}}")) ||
               PDVariables.UserVariables.Any(v => message.Contains("{{" + v + "}}")) ||
               PDVariables.UserPropertyVariables.Any(v => message.Contains("{{" + v + "}}")) ||
               PDVariables.ProcessDefinitionVariables.Any(v => message.Contains("{{" + v + "}}")) ||
               PDVariables.EventDefinitionVariables.Any(v => message.Contains("{{" + v + "}}")) ||
               PDVariables.ExceptionVariables.Any(v => message.Contains("{{" + v + "}}")) ||
               PDVariables.UrlVariables.Any(v => message.Contains("{{" + v + "}}")) ||
               message.Contains("{{@");
    }

    private string ReplaceWorkItemVariables(MessageModel messageModel)
    {
        var replacements = new Dictionary<string, string>
        {
            { "workitemid", messageModel.WorkItemId.ToString() },
            { "sourceuser", !string.IsNullOrEmpty(messageModel.SourceUser) ? messageModel.SourceUser : noDataMessage },
            { "eventid", messageModel.EventId.ToString() },
            { "lastaction", !string.IsNullOrEmpty(messageModel.LastAction) ? messageModel.LastAction : noDataMessage }
        };

        if (messageModel.SourceQueueId > 0)
        {
            replacements.Add("sourcequeueid", messageModel.SourceQueueId.ToString());
        } else
        {
            replacements.Add("sourcequeueid", noDataMessage);
        }

        foreach (var replacement in replacements)
        {
            if (PDVariables.WorkItemVariables.Any(v =>
                    string.Equals(v, replacement.Key, StringComparison.OrdinalIgnoreCase)))
            {
                messageModel.Message = messageModel.Message.Replace("{{" + replacement.Key.ToLower() + "}}", replacement.Value);
            }
        }

        return messageModel.Message;
    }

    #region SystemVariables

    private async Task<string> ReplaceUrlVariables(MessageModel messageModel)
    {
        var needsExceptionNoteData =
            PDVariables.UrlVariables.Any(v => messageModel.Message.Contains("{{" + v + "}}"));

        if (!needsExceptionNoteData)
            return messageModel.Message;

        var systemParameters = await GetUrlParameters();
        if (systemParameters != null && systemParameters.Count > 0)
        {
            messageModel.Message = ReplaceUrlParameterValues(messageModel.Message, systemParameters);
        }

        return messageModel.Message;
    }

    private async Task<Dictionary<string, string>> GetUrlParameters()
    {
        var parameters = await systemParameterRepository.ListAsync(p => PDVariables.UrlVariables.Contains(p.ParamName.ToLower()));

        var dict = new Dictionary<string, string>();
        foreach (var parameter in parameters)
        {
            dict.Add(parameter.ParamName.ToLower(), parameter.ParamValue);
        }

        return dict;
    }

    private string ReplaceUrlParameterValues(string message, Dictionary<string, string> parameters)
    {
        foreach (var dataVar in PDVariables.UrlVariables)
        {
            if (message.Contains("{{" + dataVar + "}}"))
            {
                string value = GetUrlParameterValue(dataVar, parameters);
                message = message.Replace("{{" + dataVar + "}}", value);
            }
        }

        return message;
    }

    private string GetUrlParameterValue(string variable, Dictionary<string, string> parameters)
    {
        switch (variable.ToLower())
        {
            case "rapidurl":
                return parameters.TryGetValue(variable, out var rapidUrlParameter) ? rapidUrlParameter : noDataMessage;
            case "supportemailaddress":
                return parameters.TryGetValue(variable, out var supportEmailParameter)
                    ? supportEmailParameter
                    : noDataMessage;
            default: return noDataMessage;
        }
    }

    #endregion

    #region ExceptionNote

    private async Task<string> ReplaceExceptionNoteVariables(MessageModel messageModel)
    {
        var needsExceptionNoteData =
            PDVariables.ExceptionVariables.Any(v => messageModel.Message.Contains("{{" + v + "}}"));

        if (!needsExceptionNoteData)
            return messageModel.Message;

        var exceptionNoteModel = await GetExceptionNoteModelFromDb(messageModel.WorkItemId);
        messageModel.Message = ReplaceExceptionNoteDataValues(messageModel.Message,
            exceptionNoteModel ?? new ExceptionNoteModel { ExceptionNote = noDataMessage });

        return messageModel.Message;
    }

    private async Task<ExceptionNoteModel> GetExceptionNoteModelFromDb(int workItemId)
    {
        var workItemReference = await workItemReferenceRepository.FindAsync(wir => wir.WorkItemId == workItemId);
        var exceptionNote = await exceptionNoteRepository.FindAsync(en => en.WorkItemReferenceId == workItemReference.WorkItemReferenceId);

        if (exceptionNote != null)
        {
            return new ExceptionNoteModel
            {
                ExceptionNote = exceptionNote.ExceptionReason?.ToString()
            };
        }

        return null;
    }

    private string ReplaceExceptionNoteDataValues(string message, ExceptionNoteModel exceptionNoteData)
    {
        foreach (var dataVar in PDVariables.ExceptionVariables)
        {
            if (message.Contains("{{" + dataVar + "}}"))
            {
                string value = GetExceptionNoteDataValue(dataVar, exceptionNoteData);
                message = message.Replace("{{" + dataVar + "}}", value);
            }
        }

        return message;
    }

    private string GetExceptionNoteDataValue(string variable, ExceptionNoteModel exceptionNoteData)
    {
        switch (variable.ToLower())
        {
            case "exceptionnote": return exceptionNoteData.ExceptionNote ?? noDataMessage;
            default: return noDataMessage;
        }
    }

    #endregion

    #region WorkItem Variables

    private async Task<string> ReplaceWorkItemDataVariables(MessageModel messageModel)
    {
        var needsWorkItemData =
            PDVariables.WorkItemDataVariables.Any(v => messageModel.Message.Contains("{{" + v + "}}"));

        if (!needsWorkItemData)
            return messageModel.Message;

        var workItemData = await GetWorkItemDataFromDb(messageModel.WorkItemId);

        if (workItemData != null)
        {
            messageModel.Message = ReplaceWorkItemDataValues(messageModel.Message, workItemData);
        }

        return messageModel.Message;
    }

    private async Task<WorkItemDataModel> GetWorkItemDataFromDb(int workItemId)
    {
        var workItemReference = await workItemReferenceRepository.FindAsync(wir => wir.WorkItemId == workItemId, wir => wir.WorkItem);
        var workItem = workItemReference.WorkItem;

        if (workItem != null)
        {
            return new WorkItemDataModel
            {
                Created = workItem.Created,
                Creator = workItem.Creator,
                LockedBy = workItem.LockedBy?.ToString(),
                Priority = workItem.Priority,
                WorkItemName = workItem.WorkItemName,
                ProcessId = workItem.ProcessId
            };
        }

        return null;
    }

    private string ReplaceWorkItemDataValues(string message, WorkItemDataModel workItemData)
    {
        foreach (var dataVar in PDVariables.WorkItemDataVariables)
        {
            if (message.Contains("{{" + dataVar + "}}"))
            {
                string value = GetWorkItemDataValue(dataVar, workItemData);
                message = message.Replace("{{" + dataVar + "}}", value);
            }
        }

        return message;
    }

    private string GetWorkItemDataValue(string variable, WorkItemDataModel workItemData)
    {
        switch (variable.ToLower())
        {
            case "created": return workItemData.Created != default ? workItemData.Created.ToString("o") : noDataMessage;
            case "creator": return workItemData.Creator ?? noDataMessage;
            case "lockedby": return workItemData.LockedBy ?? noDataMessage;
            case "priority": return workItemData.Priority.ToString();
            case "workitemname": return workItemData.WorkItemName ?? noDataMessage;
            case "processid": return workItemData.ProcessId.ToString();
            default: return noDataMessage;
        }
    }

    #endregion

    #region WorkItem Reference Variables

    private async Task<string> ReplaceWorkItemReferenceVariables(MessageModel messageModel)
    {
        var needsReferenceData =
            PDVariables.WorkItemReferenceVariables.Any(v => messageModel.Message.Contains("{{" + v + "}}"));

        if (!needsReferenceData)
            return messageModel.Message;

        var workItemReference = await GetWorkItemReferenceFromDb(messageModel.WorkItemId);

        if (workItemReference != null)
        {
            messageModel.Message = ReplaceWorkItemReferenceValues(messageModel.Message, workItemReference, messageModel.KnownQueueId);
        }

        return messageModel.Message;
    }

    private async Task<WorkItemReference> GetWorkItemReferenceFromDb(int workItemId)
    {
        var workItemReference = await workItemReferenceRepository.FindAsync(wir => wir.WorkItemId == workItemId);

        if (workItemReference != null)
        {
            return new WorkItemReference
            {
                WorkItemReferenceID = workItemReference.WorkItemReferenceId.ToString(),
                ArrivalDate = workItemReference.ArrivalDate?.ToString(),
                AssignedTo = workItemReference.AssignedTo?.ToString(),
                ExpirationTime = workItemReference.ExpirationTime?.ToString(),
                Expired = workItemReference.Expired.ToString(),
                QueueId = workItemReference.QueueId?.ToString(),
                LastAction = workItemReference.LastAction?.ToString(),
                Status = workItemReference.Status.ToString()
            };
        }

        return null;
    }

    private string ReplaceWorkItemReferenceValues(string message, WorkItemReference reference,
        int? knownQueueId = null)
    {
        foreach (var refVar in PDVariables.WorkItemReferenceVariables)
        {
            if (message.Contains("{{" + refVar + "}}"))
            {
                string value;
                if (refVar == "queueid" && knownQueueId != null)
                {
                    value = knownQueueId.ToString();
                }
                else
                {
                    value = GetWorkItemReferenceValue(refVar, reference);
                }

                message = message.Replace("{{" + refVar + "}}", value);
            }
        }

        return message;
    }

    private string GetWorkItemReferenceValue(string variable, WorkItemReference reference)
    {
        switch (variable.ToLower())
        {
            case "workitemreferenceid": return reference.WorkItemReferenceID ?? noDataMessage;
            case "arrivaldate": return reference.ArrivalDate ?? noDataMessage;
            case "assignedto": return reference.AssignedTo ?? noDataMessage;
            case "expirationtime": return reference.ExpirationTime ?? noDataMessage;
            case "expired": return reference.Expired ?? noDataMessage;
            case "queueid": return reference.QueueId ?? noDataMessage;
            case "lastactionuser":
                {
                    var lastAction = reference.LastAction;
                    if (lastAction == null)
                        return noDataMessage;

                    var colonIndex = lastAction.IndexOf(':');
                    return colonIndex > 0 ? lastAction.Substring(0, colonIndex) : lastAction;
                }
            case "status": return reference.Status ?? noDataMessage;
            default: return noDataMessage;
        }
    }

    #endregion

    #region User Variables

    private async Task<string> ReplaceUserVariables(MessageModel messageModel)
    {
        var needsUserData = PDVariables.UserVariables.Any(v => messageModel.Message.Contains("{{" + v + "}}"));

        if (!needsUserData)
            return messageModel.Message;

        var userTemplateModel = await GetUserDataFromDb(messageModel.WorkItemId);

        if (userTemplateModel != null)
        {
            messageModel.Message = ReplaceUserValues(messageModel.Message, userTemplateModel);
        }

        return messageModel.Message;
    }

    private async Task<UserTemplateModel> GetUserDataFromDb(int workItemId)
    {
        var workItemReference = await workItemReferenceRepository.FindAsync(wir => wir.WorkItemId == workItemId, wir => wir.WorkItem);
        var workItem = workItemReference.WorkItem;

        if (workItem != null)
        {
            var creator = await userRepository.FindAsync(u => u.UserName == workItem.Creator);
            var sourceUser = await userRepository.FindAsync(u => u.UserName == workItemReference.SourceUser);
            var lastActionUser = workItemReference.LastAction != null ? await userRepository.FindAsync(u => u.UserName == workItemReference.LastAction.Substring(0,
                (workItemReference.LastAction.Contains(':') ? workItemReference.LastAction.IndexOf(':') : workItemReference.LastAction.Length))) : null;

            return new UserTemplateModel
            {
                CreatorDisplayName = creator != null ? creator.DisplayName?.ToString() : noDataMessage,
                SourceUserDisplayName = sourceUser != null ? sourceUser.DisplayName?.ToString() : noDataMessage,
                LastActionUserDisplayName = lastActionUser != null ? lastActionUser.DisplayName?.ToString() : noDataMessage
            };
        }

        return null;
    }

    private string ReplaceUserValues(string message, UserTemplateModel userModel)
    {
        foreach (var userVar in PDVariables.UserVariables)
        {
            if (message.Contains("{{" + userVar + "}}"))
            {
                string value = GetUserValue(userVar, userModel);
                message = message.Replace("{{" + userVar + "}}", value);
            }
        }

        return message;
    }

    private string GetUserValue(string variable, UserTemplateModel userModel)
    {
        switch (variable.ToLower())
        {
            case "sourceuserdisplayname": return userModel.SourceUserDisplayName ?? noDataMessage;
            case "lastactionuserdisplayname": return userModel.LastActionUserDisplayName ?? noDataMessage;
            case "creatordisplayname": return userModel.CreatorDisplayName ?? noDataMessage;
            default: return noDataMessage;
        }
    }

    #endregion

    #region User Property Variables

    private async Task<string> ReplaceUserPropertyVariables(MessageModel messageModel)
    {
        var needsUserPropertyData =
            PDVariables.UserPropertyVariables.Any(v => messageModel.Message.Contains("{{" + v + "}}"));

        if (!needsUserPropertyData)
            return messageModel.Message;

        var userPropertyModel = await GetUserPropertyDataFromDb(messageModel.WorkItemId);

        if (userPropertyModel != null)
        {
            messageModel.Message = ReplaceUserPropertyValues(messageModel.Message, userPropertyModel);
        }

        return messageModel.Message;
    }

    private async Task<UserPropertyModel> GetUserPropertyDataFromDb(int workItemId)
    {
        var workItemReference = await workItemReferenceRepository.FindAsync(wir => wir.WorkItemId == workItemId, wir => wir.WorkItem);
        var workItem = workItemReference.WorkItem;

        if (workItem != null)
        {
            var creatorProperty = await userPropertyRepository.FindAsync(up => up.UserName == workItem.Creator && up.PropertyName == "Email");
            var sourceUserProperty = await userPropertyRepository.FindAsync(up => up.UserName == workItemReference.SourceUser && up.PropertyName == "Email");
            var lastActionUserProperty = workItemReference.LastAction != null ? await userPropertyRepository.FindAsync(up => up.UserName == workItemReference.LastAction.Substring(0,
                (workItemReference.LastAction.Contains(':') ? workItemReference.LastAction.IndexOf(':') : workItemReference.LastAction.Length)) && up.PropertyName == "Email") : null;

            return new UserPropertyModel
            {
                CreatorEmail = creatorProperty != null ? creatorProperty.PropertyValue : noDataMessage,
                SourceUserEmail = sourceUserProperty != null ? sourceUserProperty.PropertyValue : noDataMessage,
                LastActionUserEmail = lastActionUserProperty != null ? lastActionUserProperty.PropertyValue : noDataMessage
            };
        }

        return null;
    }

    private string ReplaceUserPropertyValues(string message, UserPropertyModel userPropertyModel)
    {
        foreach (var userPropertyVar in PDVariables.UserPropertyVariables)
        {
            if (message.Contains("{{" + userPropertyVar + "}}"))
            {
                string value = GetUserPropertyValue(userPropertyVar, userPropertyModel);
                message = message.Replace("{{" + userPropertyVar + "}}", value);
            }
        }

        return message;
    }

    private string GetUserPropertyValue(string variable, UserPropertyModel userPropertyModel)
    {
        switch (variable.ToLower())
        {
            case "sourceuseremail": return userPropertyModel.SourceUserEmail ?? noDataMessage;
            case "lastactionuseremail": return userPropertyModel.LastActionUserEmail ?? noDataMessage;
            case "creatoremail": return userPropertyModel.CreatorEmail ?? noDataMessage;
            default: return noDataMessage;
        }
    }

    #endregion

    #region Process Variables

    private async Task<string> ReplaceProcessVariables(MessageModel messageModel)
    {
        var needsProcessData =
            PDVariables.ProcessDefinitionVariables.Any(v => messageModel.Message.Contains("{{" + v + "}}"));

        if (!needsProcessData)
            return messageModel.Message;

        var processModel = await GetProcessDataFromDb(messageModel.WorkItemId);

        if (processModel != null)
        {
            messageModel.Message = ReplaceProcessValues(messageModel.Message, processModel);
        }

        return messageModel.Message;
    }

    private async Task<ProcessModel> GetProcessDataFromDb(int workItemId)
    {
        var workItemReference = await workItemReferenceRepository.FindAsync(wir => wir.WorkItemId == workItemId);
        var eventDefinition = await eventDefinitionRepository.FindAsync(ed => ed.EventId == workItemReference.EventId);
        var processDefinition = await processRepository.FindAsync(pd => pd.ProcessId == eventDefinition.ProcessId);

        if (processDefinition != null)
        {
            return new ProcessModel
            {
                ProcessName = processDefinition.ProcessName,
                ProcessVersion = processDefinition.ProcessVersion.ToString()
            };
        }

        return null;
    }

    private string ReplaceProcessValues(string message, ProcessModel processModel)
    {
        foreach (var processVar in PDVariables.ProcessDefinitionVariables)
        {
            if (message.Contains("{{" + processVar + "}}"))
            {
                string value = GetProcessValue(processVar, processModel);
                message = message.Replace("{{" + processVar + "}}", value);
            }
        }

        return message;
    }

    private string GetProcessValue(string variable, ProcessModel processModel)
    {
        switch (variable.ToLower())
        {
            case "processname": return processModel.ProcessName ?? noDataMessage;
            case "processversion": return processModel.ProcessVersion ?? noDataMessage;
            default: return noDataMessage;
        }
    }

    #endregion

    #region Event Variables

    private async Task<string> ReplaceEventVariables(MessageModel messageModel)
    {
        var needsEventData =
            PDVariables.EventDefinitionVariables.Any(v => messageModel.Message.Contains("{{" + v + "}}"));

        if (!needsEventData)
            return messageModel.Message;

        var eventModel = await GetEventDataFromDb(messageModel.WorkItemId);

        if (eventModel != null)
        {
            messageModel.Message = ReplaceEventValues(messageModel.Message, eventModel);
        }

        return messageModel.Message;
    }

    private async Task<EventMessageModel> GetEventDataFromDb(int workItemId)
    {
        var workItemReference = await workItemReferenceRepository.FindAsync(wir => wir.WorkItemId == workItemId);
        var eventDefinition = await eventDefinitionRepository.FindAsync(ed => ed.EventId == workItemReference.EventId);
        var eventType = await eventTypeRepository.FindAsync(et => et.Id == eventDefinition.EventTypeId);

        if (eventType != null)
        {
            return new EventMessageModel
            {
                EventName = eventDefinition.EventName,
                Server = eventDefinition.Server?.ToString(),
                EventType = eventType.ShortName
            };
        }

        return null;
    }

    private string ReplaceEventValues(string message, EventMessageModel eventMessageModel)
    {
        foreach (var eventVar in PDVariables.EventDefinitionVariables)
        {
            if (message.Contains("{{" + eventVar + "}}"))
            {
                string value = GetEventValue(eventVar, eventMessageModel);
                message = message.Replace("{{" + eventVar + "}}", value);
            }
        }

        return message;
    }

    private string GetEventValue(string variable, EventMessageModel eventMessageModel)
    {
        switch (variable.ToLower())
        {
            case "eventname": return eventMessageModel.EventName ?? noDataMessage;
            case "server": return eventMessageModel.Server ?? noDataMessage;
            case "eventtype": return eventMessageModel.EventType ?? noDataMessage;
            default: return noDataMessage;
        }
    }

    #endregion

    #region Custom Field Variables

    private async Task<string> ReplaceCustomFieldVariables(MessageModel messageModel)
    {
        if (!messageModel.Message.Contains("{{@"))
            return messageModel.Message;

        // Extract all custom field variables from the message
        var customFieldVariables = new HashSet<string>();
        var startIndex = 0;

        while ((startIndex = messageModel.Message.IndexOf("{{@", startIndex)) >= 0)
        {
            var endIndex = messageModel.Message.IndexOf("}}", startIndex);
            if (endIndex > startIndex)
            {
                var fieldVariable = messageModel.Message.Substring(startIndex + 3, endIndex - startIndex - 3);
                customFieldVariables.Add(fieldVariable);
                startIndex = endIndex;
            }
            else
            {
                break;
            }
        }

        if (customFieldVariables.Count == 0)
            return messageModel.Message;

        // Get field values from the database
        var fieldValues = await GetCustomFieldValues(messageModel.WorkItemId, customFieldVariables);

        // Replace each occurrence in the message
        foreach (var field in customFieldVariables)
        {
            var value = fieldValues.ContainsKey(field) ? fieldValues[field] : noDataMessage;
            messageModel.Message = messageModel.Message.Replace("{{@" + field + "}}", value);
        }

        return messageModel.Message;
    }

    private async Task<Dictionary<string, string>> GetCustomFieldValues(int workItemId, HashSet<string> fieldNames)
    {
        var fieldValues = new Dictionary<string, string>();

        foreach (var fieldName in fieldNames)
        {
            var fieldDefinition = await fieldDefinitionRepository.FindAsync(fd => fd.FieldName == fieldName);

            if (fieldDefinition.DataType == (byte)FieldInstanceType.String)
            {
                var fieldInstanceString = await fieldInstanceStringRepository.FindAsync(fis => fis.FieldId == fieldDefinition.FieldId && fis.WorkItemId == workItemId);
                if (fieldInstanceString != null)
                {
                    fieldValues.Add(fieldName, fieldInstanceString.FieldValue);
                } else
                {
                    fieldValues.Add(fieldName, fieldDefinition.InitialValue ?? noDataMessage);
                }
            } else if (fieldDefinition.DataType == (byte)FieldInstanceType.Option)
            {
                var fieldInstanceOption = await fieldInstanceOptionRepository.FindAsync(fio => fio.FieldId == fieldDefinition.FieldId && fio.WorkItemId == workItemId);
                if (fieldInstanceOption != null)
                {
                    fieldValues.Add(fieldName, fieldInstanceOption.FieldValue);
                } else
                {
                    fieldValues.Add(fieldName, noDataMessage);
                }
            }
        }

        return fieldValues;
    }

    #endregion
}