using IWS5.Core.Enums;
using IWS5.Core.Helpers;
using IWS5.Core.Models;
using IWS5.Core.Models.Processing;
using IWS5.Server.DataAccess;
using IWS5.Server.Helpers;
using Microsoft.Data.SqlClient;
using ILogger = Serilog.ILogger;

namespace IWS5.Server.EventProcessors;

public class TrackingProcessor(ILogger logger, IConfiguration configuration, IMessageProcessor messageProcessor) : EventProcessor(logger, configuration)
{
    public override async Task<bool> ProcessEventAsync(EventModel eventData, int processId)
    {
        string sqlStr = "";
        string buttonId = "";

        try
        {
            using (var sqlConnection = new SqlConnection(ConnectionString))
            {
                var trackingDefinition =
                    TrackingRepository.GetTrackingDefinition(sqlConnection, processId, eventData.CurrentEventId);
                if (trackingDefinition == null)
                {
                    return false;
                }

                int? knownQueueId = null;
                if (eventData.CurrentEventType == ATEventType.Queue || eventData.CurrentEventType == ATEventType.User)
                {
                    knownQueueId = Repository.GetQueueId(sqlConnection, eventData.CurrentEventId);
                }
                var processedMessage = await messageProcessor.ProcessMessage(new MessageModel { 
                    Message = trackingDefinition.Message,
                    WorkItemId = eventData.WorkItemId,
                    SourceUser = eventData.SourceUser,
                    SourceQueueId = eventData.SourceQueueId.GetValueOrDefault(),
                    EventId = eventData.CurrentEventId,
                    LastAction = eventData.LastAction,
                    KnownQueueId = knownQueueId
                });

                //Create the values by finding [] values
                if (!string.IsNullOrEmpty(eventData.LastAction))
                {
                    var actionButtonName = "";
                    int colonIndex = eventData.LastAction.IndexOf(':');
                    if (colonIndex > 0 && colonIndex < eventData.LastAction.Length - 1)
                    {
                        actionButtonName = eventData.LastAction.Substring(colonIndex + 1);
                    }

                    var getButtonIndexQuery = $"SELECT [Id] FROM [ATButton] WHERE [ButtonName] = @actionButtonName";
                    buttonId = Repository.GetRecordValue(sqlConnection, getButtonIndexQuery,
                        new Dictionary<string, object> { { "@actionButtonName", actionButtonName } });
                }

                //Write the tracking record
                string recCnt = "0";

                if (eventData.CurrentEventType == ATEventType.Timer)
                {
                    sqlStr =
                        $"Select count(1) as recCnt from ATTimer WHERE workItemId = @workItemId AND ProcessId = @processId AND EventId = @eventId";
                    recCnt = Repository.GetRecordValue(sqlConnection, sqlStr, new Dictionary<string, object>
                    {
                        { "@workItemId", eventData.WorkItemId },
                        { "@processId", processId },
                        { "@eventId", eventData.CurrentEventId }
                    });
                }

                if (recCnt == "0")
                {
                    TrackingRepository.InsertTrackingRecord(sqlConnection, trackingDefinition.Id, eventData.WorkItemId,
                        eventData.LastAction, eventData.SourceQueueId.GetValueOrDefault(), eventData.SourceUser, buttonId, processedMessage);
                    sqlStr =
                        @"Insert Into ATTrackingRecord (TrackingDate, WorkItemId, UserName, TrackingDefinitionId, LastActionId, Message) 
                                                    Values (@trackingDate, @workItemId,@userName, @trackingDefinitionId, @lastActionid, @message)";

                    if (trackingDefinition.EnabledExternal)
                    {
                        var externalTrackingTemplates =
                            TrackingRepository.GetExternalTrackingData(sqlConnection, trackingDefinition.Id);
                        foreach (var externalTrackingTemplate in externalTrackingTemplates)
                        {
                            using (var externalConnection =
                                   new SqlConnection(externalTrackingTemplate.ConnectionString))
                            {
                                var tableToInsertDataTo =
                                    externalTrackingTemplate.ExternalTrackingDefinitions.First().DbTableName;
                                var stringColumns = externalTrackingTemplate.ExternalTrackingDefinitions
                                    .Select(def => def.DbColumnName.Replace("[", "").Replace("]", ""))
                                    .ToList();
                                var externalSqlStr =
                                    $"INSERT INTO {tableToInsertDataTo} ({string.Join(", ", stringColumns)}) VALUES ({string.Join(", ", stringColumns.Select(c => "@" + c))})";
                                var command = new SqlCommand(externalSqlStr, externalConnection);
                                foreach (var externalTrackingExternalTrackingDefinition in externalTrackingTemplate
                                             .ExternalTrackingDefinitions)
                                {
                                    string value = string.Empty;
                                    switch (externalTrackingExternalTrackingDefinition.SourceType)
                                    {
                                        case ATExternalTrackingSourceType.PDSystemValue:
                                            value = await messageProcessor.ProcessMessage(new MessageModel { 
                                                Message = "{{" + externalTrackingExternalTrackingDefinition.SourceValue + "}}",
                                                WorkItemId = eventData.WorkItemId,
                                                SourceUser = eventData.SourceUser,
                                                SourceQueueId = eventData.SourceQueueId.GetValueOrDefault(),
                                                EventId = eventData.CurrentEventId,
                                                LastAction = eventData.LastAction
                                            });
                                            break;
                                        case ATExternalTrackingSourceType.FolderField:
                                            value = FolderFieldRepository.GetFolderFieldInstanceValue(sqlConnection,
                                                eventData.WorkItemId,
                                                externalTrackingExternalTrackingDefinition.SourceValue,
                                                processId);
                                            break;
                                        case ATExternalTrackingSourceType.TextEntry:
                                            value = externalTrackingExternalTrackingDefinition.SourceValue;
                                            break;
                                    }

                                    var valueToInsert = ParseValueBySqlDbType(value,
                                        externalTrackingExternalTrackingDefinition.DbDataType);
                                    var parameterName = externalTrackingExternalTrackingDefinition.DbColumnName
                                        .Replace("[", "")
                                        .Replace("]", "");
                                    command.Parameters.AddWithValue("@" + parameterName, valueToInsert);
                                }

                                externalConnection.Open();
                                command.ExecuteNonQuery();
                                externalConnection.Close();
                            }
                        }
                    }
                }

                return true;
            }
        }
        catch (Exception ex)
        {
            logger.Error("Error in WriteTracking. SQL: {Sql}. Exception: {ExceptionMessage}", sqlStr, ex.Message);
            return false;
        }
    }

    private static object ParseValueBySqlDbType(string value, string sqlDbType)
    {
        if (string.IsNullOrEmpty(value))
            return null;

        // Match type and size, e.g. "numeric(18, 0)", "char(10)"
        var typeMatch = System.Text.RegularExpressions.Regex.Match(
            sqlDbType, @"^(\w+)\s*(?:\((\d+)(?:,\s*(\d+))?\))?$");
        var baseType = typeMatch.Groups[1].Value.ToLower();
        var precision = typeMatch.Groups[2].Success ? int.Parse(typeMatch.Groups[2].Value) : (int?)null;
        var scale = typeMatch.Groups[3].Success ? int.Parse(typeMatch.Groups[3].Value) : (int?)null;

        switch (baseType)
        {
            case "int":
            case "smallint":
            case "tinyint":
                if (int.TryParse(value, out int intResult))
                    return intResult;
                break;
            case "bigint":
                if (long.TryParse(value, out long bigIntResult))
                    return bigIntResult;
                break;
            case "float":
            case "real":
                if (double.TryParse(value, out double doubleResult))
                    return doubleResult;
                break;
            case "decimal":
            case "numeric":
            case "money":
            case "smallmoney":
                if (scale.HasValue && scale.Value == 0)
                {
                    if (long.TryParse(value, out long longResult))
                        return longResult;
                }

                if (decimal.TryParse(value, out decimal decimalResult))
                {
                    if (scale.HasValue)
                        decimalResult = Math.Round(decimalResult, scale.Value);
                    return decimalResult;
                }

                break;
            case "bit":
                if (value == "1" || value.ToLower() == "true")
                    return true;
                if (value == "0" || value.ToLower() == "false")
                    return false;
                break;
            case "datetime":
            case "smalldatetime":
            case "date":
            case "time":
            case "datetime2":
            case "datetimeoffset":
                if (DateTime.TryParse(value, out DateTime dateResult))
                    return dateResult;
                break;
            case "char":
            case "nchar":
                if (precision.HasValue)
                {
                    if (value.Length > precision.Value)
                        return value.Substring(0, precision.Value);
                    else
                        return value.PadRight(precision.Value, ' ');
                }

                return value;
            case "varchar":
            case "nvarchar":
                if (precision.HasValue && value.Length > precision.Value)
                    return value.Substring(0, precision.Value);
                return value;
            case "text":
            case "ntext":
            default:
                return value;
        }

        // If parsing fails, return the original string
        return value;
    }
}