using IWS5.Core.Enums;
using IWS5.Core.Models.Request;
using IWS5.DatabaseAccess.Helpers;
using IWS5.DatabaseAccess.Interfaces.Importer;
using IWS5.DatabaseAccess.IwsDataContext;
using IWS5.DatabaseAccess.IwsDataModels;
using Microsoft.EntityFrameworkCore;
using Polly;
using Polly.Retry;
using Serilog;
using Serilog.Context;

namespace IWS5.DatabaseAccess.Repositories.Importer
{
    public class WorkItemRepository(IDbContextFactory<GreenfieldPd2015Context> contextFactory, ILogger logger)
    : IWorkItemRepository
    {
        private readonly AsyncRetryPolicy _retryPolicy = Policy
                .Handle<DbUpdateException>()
                .Or<TimeoutException>()
                .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                    (exception, timeSpan, retryCount, context) =>
                    {
                        using (LogContext.PushProperty("LogContext", nameof(WorkItemRepository)))
                            logger.Error("Retry {RetryCount} due to {ExceptionMessage}", retryCount, exception.Message);
                    });

        public async Task InsertWorkItemAsync(CreateWorkItemRequest request, string createdBy)
        {
            await using var context = await contextFactory.CreateDbContextAsync();

            using (LogContext.PushProperty("LogContext", nameof(WorkItemRepository)))
            {
                await _retryPolicy.ExecuteAsync(async () =>
                {
                    using var transaction = await context.Database.BeginTransactionAsync();
                    try
                    {
                        logger.Information("Starting to create work item: {WorkItemName}", request.WorkItemName);

                        // Step 1: Insert WorkItem
                        var workItem = new AtworkItem
                        {
                            WorkItemName = request.WorkItemName,
                            Created = DateTime.UtcNow,
                            Creator = createdBy,
                            Priority = 50,
                            ProcessId = request.ProcessId,
                            Expired = 0
                        };

                        context.AtworkItems.Add(workItem);
                        await context.SaveChangesAsync();

                        // Step 2: Retrieve Event Definition
                        var eventDef = await context.AteventDefinitions
                            .FirstOrDefaultAsync(e =>
                                e.ProcessId == request.ProcessId &&
                                e.EventName == "Start");

                        if (eventDef == null)
                        {
                            logger.Warning("No matching event definition found for ProcessId: {ProcessId}", request.ProcessId);
                            throw new InvalidOperationException($"Event definition not found for ProcessId: {request.ProcessId}");
                        }

                        // Step 3: Insert WorkItemReference
                        var workItemReference = new AtworkItemReference
                        {
                            WorkItemId = workItem.WorkItemId,
                            Expired = 0,
                            Status = 0,
                            EventId = eventDef.EventId,
                            ServerRequest = 1,
                            ServerRequestParam = request.ProcessId
                        };

                        context.AtworkItemReferences.Add(workItemReference);
                        await context.SaveChangesAsync();

                        foreach (var folderField in (request.FolderFields ?? Enumerable.Empty<CreateFolderFieldValueRequest>()))
                        {
                            try
                            {
                                switch (folderField.FolderFieldType)
                                {
                                    case FieldInstanceType.Date:
                                        var folderFieldDate = new AtfieldInstanceDate
                                        {
                                            FieldId = folderField.FolderFieldId,
                                            FieldName = folderField.FolderFieldName,
                                            FieldValue = DateTime.Parse(folderField.FolderFieldValue),
                                            WorkItemId = workItem.WorkItemId,
                                        };
                                        context.AtfieldInstanceDates.Add(folderFieldDate);
                                        break;
                                    case FieldInstanceType.Number:
                                        var folderFieldNumber = new AtfieldInstanceNumber
                                        {
                                            FieldId = folderField.FolderFieldId,
                                            FieldName = folderField?.FolderFieldName,
                                            FieldValue = decimal.Parse(folderField.FolderFieldValue),
                                            WorkItemId = workItem.WorkItemId,
                                        };
                                        context.AtfieldInstanceNumbers.Add(folderFieldNumber);
                                        break;
                                    case FieldInstanceType.String:
                                        var folderFieldString = new AtfieldInstanceString
                                        {
                                            FieldId = folderField.FolderFieldId,
                                            FieldName = folderField.FolderFieldName,
                                            FieldValue = folderField.FolderFieldValue,
                                            WorkItemId = workItem.WorkItemId,
                                        };
                                        context.AtfieldInstanceStrings.Add(folderFieldString);
                                        break;
                                    default:
                                        throw new ArgumentOutOfRangeException(nameof(folderField.FolderFieldType),
                                            folderField.FolderFieldType, null);
                                }
                            }
                            catch (Exception ex)
                            {
                                logger.Error(ex, "Could not add folder field: {id}, {Name}, {Value}", folderField.FolderFieldId, folderField.FolderFieldName, folderField.FolderFieldValue);
                                continue;
                            }
                        }
                        context.SaveChanges();

                        // Step 5: Insert Documents
                        var documentEntities = (request.Documents ?? Enumerable.Empty<CreateDocumentRequest>())
                        .Select(document => new Atdocument
                        {
                            DocumentName = document.DocumentName,
                            Document = Convert.FromBase64String(document.DocumentFile ?? throw new InvalidOperationException("DocumentFile is null")),
                            WorkItemId = workItem.WorkItemId,
                            CabinetId = document.DocumentTemplateId,
                            UserName = createdBy,
                            ReviewStatusId = 1,
                            LastModified = DateTime.UtcNow,
                            DisplayOrder = document.DisplayOrder ?? 1,
                        }).ToList();

                        context.Atdocuments.AddRange(documentEntities);
                        await context.SaveChangesAsync();

                        if (request.Documents != null)
                        {
                            foreach (var document in request.Documents)
                            {
                                var template = await context.AtdocumentCabinets
                                    .FirstOrDefaultAsync(c => c.CabinetId == document.DocumentTemplateId);

                                if (template != null)
                                {
                                    await DocumentTemplateDefTableHelper.InsertRecordIntoTemplateTableAsync(
                                        template.CabinetName, documentEntities.First(d => d.DocumentName == document.DocumentName).DocumentId,
                                        document.DocumentFields ?? Enumerable.Empty<UpdateDocumentFieldRequest>(), context);
                                }
                            }
                        }

                        await transaction.CommitAsync();
                        logger.Information("Successfully created work item: {WorkItemName}", request.WorkItemName);
                    }
                    catch (Exception ex)
                    {
                        await transaction.RollbackAsync();
                        logger.Error(ex, "Failed to create work item: {WorkItemName}", request.WorkItemName);
                        throw;
                    }
                });
            }
        }
    }
}
