using CashLineERP.Application.Workflow;
using CashLineERP.SharedKernel.Workflow;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class WorkflowReadRepository : IWorkflowReadRepository
{
    private readonly CashLineErpDbContext _dbContext;

    public WorkflowReadRepository(CashLineErpDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IReadOnlyCollection<WorkflowQueueItemView> GetPendingQueue()
    {
        var queueRows = _dbContext.WorkflowInstances
            .AsNoTracking()
            .Where(instance => instance.CurrentStatusCode == WorkflowStatusCode.Submitted)
            .OrderByDescending(instance => instance.SubmittedAtUtc ?? instance.CreatedAtUtc)
            .Select(instance => new
            {
                instance.Id,
                instance.ModuleCode,
                instance.DocumentType,
                instance.DocumentId,
                instance.CurrentStatusCode,
                SubmittedBy = _dbContext.Users
                    .Where(user => user.Id == instance.MakerUserId)
                    .Select(user => user.LoginName)
                    .FirstOrDefault(),
                SubmittedAtUtc = instance.SubmittedAtUtc ?? instance.CreatedAtUtc
            })
            .ToArray();

        return queueRows
            .Select(instance => new WorkflowQueueItemView(
                instance.Id,
                instance.ModuleCode,
                instance.DocumentType,
                FormatDocumentNumber(instance.DocumentType, instance.DocumentId),
                instance.CurrentStatusCode,
                instance.SubmittedBy ?? "UNKNOWN",
                instance.SubmittedAtUtc))
            .ToArray();
    }

    public IReadOnlyCollection<WorkflowRuleAdminView> GetWorkflowRules(bool activeOnly = false) =>
        _dbContext.WorkflowRules
            .AsNoTracking()
            .Where(rule => !activeOnly || rule.IsActive)
            .OrderBy(rule => rule.ModuleCode)
            .ThenBy(rule => rule.DocumentType)
            .ThenBy(rule => rule.ActionCode)
            .Select(rule => new WorkflowRuleAdminView(
                rule.Id,
                rule.ModuleCode,
                rule.DocumentType,
                rule.ActionCode,
                rule.MakerRoleCode,
                rule.CheckerRoleCode,
                rule.ThresholdAmount,
                rule.CurrencyCode,
                rule.BranchId,
                rule.IsActive))
            .ToArray();

    public IReadOnlyCollection<WorkflowInstanceSummaryView> GetWorkflowInstances(string? moduleCode = null, string? documentType = null, string? statusCode = null)
    {
        var normalizedModuleCode = NormalizeOptionalCode(moduleCode);
        var normalizedDocumentType = NormalizeOptionalCode(documentType);
        var normalizedStatusCode = NormalizeOptionalCode(statusCode);

        return _dbContext.WorkflowInstances
            .AsNoTracking()
            .Where(instance =>
                (normalizedModuleCode == null || instance.ModuleCode == normalizedModuleCode) &&
                (normalizedDocumentType == null || instance.DocumentType == normalizedDocumentType) &&
                (normalizedStatusCode == null || instance.CurrentStatusCode == normalizedStatusCode))
            .OrderByDescending(instance => instance.SubmittedAtUtc ?? instance.CreatedAtUtc)
            .ThenByDescending(instance => instance.Id)
            .Select(instance => new WorkflowInstanceSummaryView(
                instance.Id,
                instance.ModuleCode,
                instance.DocumentType,
                instance.DocumentId,
                FormatDocumentNumber(instance.DocumentType, instance.DocumentId),
                instance.WorkflowRuleId,
                instance.CurrentStatusCode,
                _dbContext.Users.Where(user => user.Id == instance.MakerUserId).Select(user => user.LoginName).FirstOrDefault() ?? "UNKNOWN",
                _dbContext.Users.Where(user => user.Id == instance.CheckerUserId).Select(user => user.LoginName).FirstOrDefault(),
                instance.SubmittedAtUtc,
                instance.DecidedAtUtc))
            .ToArray();
    }

    public WorkflowInstanceDetailView? GetWorkflowInstance(long workflowInstanceId)
    {
        var instance = _dbContext.WorkflowInstances
            .AsNoTracking()
            .Include(entry => entry.Actions)
            .SingleOrDefault(entry => entry.Id == workflowInstanceId);

        if (instance is null)
        {
            return null;
        }

        return new WorkflowInstanceDetailView(
            instance.Id,
            instance.ModuleCode,
            instance.DocumentType,
            instance.DocumentId,
            FormatDocumentNumber(instance.DocumentType, instance.DocumentId),
            instance.WorkflowRuleId,
            instance.MakerUserId,
            _dbContext.Users.Where(user => user.Id == instance.MakerUserId).Select(user => user.LoginName).FirstOrDefault() ?? "UNKNOWN",
            instance.CheckerUserId,
            _dbContext.Users.Where(user => user.Id == instance.CheckerUserId).Select(user => user.LoginName).FirstOrDefault(),
            instance.SubmittedAtUtc,
            instance.DecidedAtUtc,
            instance.CurrentStatusCode,
            instance.Actions
                .OrderBy(action => action.OccurredAtUtc)
                .ThenBy(action => action.Id)
                .Select(action => new WorkflowActionView(
                    action.Id,
                    action.ActionCode,
                    action.PreviousStatusCode,
                    action.NewStatusCode,
                    action.ActorUserId,
                    _dbContext.Users.Where(user => user.Id == action.ActorUserId).Select(user => user.LoginName).FirstOrDefault() ?? "UNKNOWN",
                    action.Comment,
                    action.OccurredAtUtc))
                .ToArray());
    }

    private static string? NormalizeOptionalCode(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim().ToUpperInvariant();

    private static string FormatDocumentNumber(string documentType, long documentId)
    {
        var prefix = documentType switch
        {
            "PURCHASE_REQUISITION" => "PR",
            "PURCHASE_ORDER" => "PO",
            "SALES_ORDER" => "SO",
            "DISPATCH" => "DSP",
            _ => "DOC"
        };

        return $"{prefix}-{documentId:D6}";
    }
}
