using CashLineERP.Application.Inventory;
using CashLineERP.Domain.Inventory;
using CashLineERP.Domain.Procurement;
using CashLineERP.Domain.Workflow;
using CashLineERP.SharedKernel.Workflow;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class ReplenishmentRuleRepository : IReplenishmentRuleRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<ReplenishmentRuleDetailView> GetReplenishmentRules(bool activeOnly = false) =>
        _dbContext.ReplenishmentRules
            .AsNoTracking()
            .Where(rule => !activeOnly || rule.IsActive)
            .OrderByDescending(rule => rule.Priority)
            .ThenBy(rule => rule.RuleCode)
            .ToArray()
            .Select(Map)
            .ToArray();

    public ReplenishmentRuleDetailView? GetReplenishmentRule(long replenishmentRuleId)
    {
        var rule = _dbContext.ReplenishmentRules
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == replenishmentRuleId);

        return rule is null ? null : Map(rule);
    }

    public ReplenishmentRuleDetailView Create(CreateReplenishmentRuleCommand command)
    {
        var nextSequence = (_dbContext.ReplenishmentRules.Max(rule => (long?)rule.Id) ?? 0L) + 1L;
        var rule = new ReplenishmentRule(
            ruleCode: $"RPL-{nextSequence:D6}",
            ruleName: command.RuleName.Trim(),
            branchId: command.BranchId,
            targetWarehouseId: command.TargetWarehouseId,
            preferredSourceWarehouseId: command.PreferredSourceWarehouseId,
            itemId: command.ItemId,
            unitOfMeasureCode: command.UnitOfMeasureCode.Trim().ToUpperInvariant(),
            reorderPointQuantity: command.ReorderPointQuantity,
            targetSafetyStockQuantity: command.TargetSafetyStockQuantity,
            targetStockQuantity: command.TargetStockQuantity,
            sourceSafetyStockQuantity: command.SourceSafetyStockQuantity,
            priority: command.Priority,
            effectiveFromDate: command.EffectiveFromDate,
            effectiveToDate: command.EffectiveToDate,
            notes: command.Notes);

        _dbContext.ReplenishmentRules.Add(rule);
        _dbContext.SaveChanges();
        return GetReplenishmentRule(rule.Id)!;
    }

    public ReplenishmentRuleDetailView Submit(long replenishmentRuleId, long actorUserId)
    {
        var rule = _dbContext.ReplenishmentRules.Single(entry => entry.Id == replenishmentRuleId);
        rule.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "INVENTORY",
            documentType: "REPLENISHMENT_RULE",
            documentId: rule.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Replenishment rule submitted for checker approval.");

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetReplenishmentRule(rule.Id)!;
    }

    public ReplenishmentRuleDetailView Approve(long replenishmentRuleId, long actorUserId)
    {
        var rule = _dbContext.ReplenishmentRules.Single(entry => entry.Id == replenishmentRuleId);
        rule.Approve(actorUserId);

        var workflowInstance = _dbContext.WorkflowInstances
            .Include(entry => entry.Actions)
            .SingleOrDefault(entry => entry.DocumentType == "REPLENISHMENT_RULE" && entry.DocumentId == replenishmentRuleId);
        workflowInstance?.Approve(actorUserId, "Replenishment rule approved.");

        _dbContext.SaveChanges();
        return GetReplenishmentRule(rule.Id)!;
    }

    public ReplenishmentRuleDetailView Activate(long replenishmentRuleId, long actorUserId)
    {
        var rule = _dbContext.ReplenishmentRules.Single(entry => entry.Id == replenishmentRuleId);
        rule.Activate(actorUserId);
        _dbContext.SaveChanges();
        return GetReplenishmentRule(rule.Id)!;
    }

    public ReplenishmentEvaluationView Evaluate(ReplenishmentEvaluationCommand command)
    {
        var activeRules = _dbContext.ReplenishmentRules
            .AsNoTracking()
            .ToArray()
            .Where(rule => rule.AppliesTo(command.BranchId, command.AsOfDate, command.TargetWarehouseId, command.ItemId))
            .OrderByDescending(rule => rule.Priority)
            .ThenBy(rule => rule.RuleCode)
            .ToArray();

        var onHandByWarehouse = BuildOnHandByWarehouse(command.BranchId, command.AsOfDate);

        var lines = activeRules.Select(rule =>
        {
            var targetOnHand = onHandByWarehouse.GetValueOrDefault((rule.ItemId, rule.TargetWarehouseId), 0m);
            var targetShortfall = Math.Max(rule.TargetStockQuantity - targetOnHand, 0m);
            if (targetOnHand > rule.ReorderPointQuantity || targetShortfall <= 0m)
            {
                return new ReplenishmentEvaluationLineView(
                    rule.Id,
                    rule.RuleCode,
                    rule.RuleName,
                    rule.BranchId,
                    rule.TargetWarehouseId,
                    null,
                    rule.ItemId,
                    rule.UnitOfMeasureCode,
                    rule.ReorderPointQuantity,
                    rule.TargetSafetyStockQuantity,
                    rule.TargetStockQuantity,
                    targetOnHand,
                    targetShortfall,
                    null,
                    null,
                    0m,
                    "NO_ACTION",
                    "Target warehouse stock remains above the replenishment trigger.");
            }

            var sourceCandidates = onHandByWarehouse
                .Where(entry =>
                    entry.Key.ItemId == rule.ItemId
                    && entry.Key.WarehouseId != rule.TargetWarehouseId
                    && entry.Value > rule.SourceSafetyStockQuantity)
                .Select(entry => new
                {
                    WarehouseId = entry.Key.WarehouseId,
                    OnHandQuantity = entry.Value,
                    AvailableToTransferQuantity = entry.Value - rule.SourceSafetyStockQuantity,
                    IsPreferred = rule.PreferredSourceWarehouseId.HasValue && rule.PreferredSourceWarehouseId.Value == entry.Key.WarehouseId
                })
                .OrderByDescending(entry => entry.IsPreferred)
                .ThenByDescending(entry => entry.AvailableToTransferQuantity)
                .ThenBy(entry => entry.WarehouseId)
                .ToArray();

            var chosenSource = sourceCandidates.FirstOrDefault();
            if (chosenSource is null)
            {
                return new ReplenishmentEvaluationLineView(
                    rule.Id,
                    rule.RuleCode,
                    rule.RuleName,
                    rule.BranchId,
                    rule.TargetWarehouseId,
                    null,
                    rule.ItemId,
                    rule.UnitOfMeasureCode,
                    rule.ReorderPointQuantity,
                    rule.TargetSafetyStockQuantity,
                    rule.TargetStockQuantity,
                    targetOnHand,
                    targetShortfall,
                    null,
                    null,
                    0m,
                    "INSUFFICIENT_SOURCE_STOCK",
                    "No source warehouse has available surplus above the configured safety stock.");
            }

            var recommendedTransferQuantity = Math.Min(targetShortfall, chosenSource.AvailableToTransferQuantity);
            var recommendationStatusCode = recommendedTransferQuantity < targetShortfall
                ? "PARTIAL_REPLENISHMENT"
                : "REPLENISHMENT_REQUIRED";

            return new ReplenishmentEvaluationLineView(
                rule.Id,
                rule.RuleCode,
                rule.RuleName,
                rule.BranchId,
                rule.TargetWarehouseId,
                chosenSource.WarehouseId,
                rule.ItemId,
                rule.UnitOfMeasureCode,
                rule.ReorderPointQuantity,
                rule.TargetSafetyStockQuantity,
                rule.TargetStockQuantity,
                targetOnHand,
                targetShortfall,
                chosenSource.OnHandQuantity,
                chosenSource.AvailableToTransferQuantity,
                recommendedTransferQuantity,
                recommendationStatusCode,
                chosenSource.IsPreferred
                    ? "Preferred source warehouse selected for replenishment."
                    : "Best available source warehouse selected from live multi-warehouse stock.");
        }).ToArray();

        return new ReplenishmentEvaluationView(command.BranchId, command.AsOfDate, lines);
    }

    public ReorderPlanningView BuildReorderPlan(ReorderPlanningCommand command)
    {
        var evaluation = Evaluate(new ReplenishmentEvaluationCommand(
            command.BranchId,
            command.AsOfDate,
            command.TargetWarehouseId,
            command.ItemId));

        var lines = evaluation.Lines.Select(line =>
        {
            var isBelowReorderPoint = line.TargetOnHandQuantity <= line.ReorderPointQuantity;
            var isBelowSafetyStock = line.TargetOnHandQuantity < line.TargetSafetyStockQuantity;
            var recommendedReorderQuantity = Math.Max(line.TargetStockQuantity - line.TargetOnHandQuantity, 0m);

            var planningActionCode = "NO_ACTION";
            decimal recommendedTransferQuantity = 0m;
            decimal recommendedProcurementQuantity = 0m;
            string? notes = line.Notes;

            if (recommendedReorderQuantity == 0m)
            {
                planningActionCode = "NO_ACTION";
                notes = "Target warehouse is already at or above target stock.";
            }
            else if (!isBelowReorderPoint && isBelowSafetyStock)
            {
                planningActionCode = "MONITOR";
                notes = "Target warehouse has dropped below safety stock but not yet reached the reorder trigger.";
            }
            else if (isBelowReorderPoint)
            {
                if (line.SourceWarehouseId.HasValue && line.RecommendedTransferQuantity > 0m)
                {
                    planningActionCode = "REPLENISH_TRANSFER";
                    recommendedTransferQuantity = line.RecommendedTransferQuantity;
                    recommendedProcurementQuantity = Math.Max(recommendedReorderQuantity - recommendedTransferQuantity, 0m);
                    if (recommendedProcurementQuantity > 0m)
                    {
                        planningActionCode = "REPLENISH_TRANSFER_PLUS_PROCUREMENT";
                        notes = "Available inter-warehouse surplus only partially covers the reorder requirement.";
                    }
                    else
                    {
                        notes = line.Notes;
                    }
                }
                else
                {
                    planningActionCode = "REPLENISH_PROCUREMENT";
                    recommendedProcurementQuantity = recommendedReorderQuantity;
                    notes = "No source warehouse can cover the reorder requirement; procurement should be raised.";
                }
            }

            return new ReorderPlanningLineView(
                line.ReplenishmentRuleId,
                line.RuleCode,
                line.RuleName,
                line.BranchId,
                line.TargetWarehouseId,
                line.SourceWarehouseId,
                line.ItemId,
                line.UnitOfMeasureCode,
                line.ReorderPointQuantity,
                line.TargetSafetyStockQuantity,
                line.TargetStockQuantity,
                line.TargetOnHandQuantity,
                isBelowReorderPoint,
                isBelowSafetyStock,
                recommendedReorderQuantity,
                recommendedTransferQuantity,
                recommendedProcurementQuantity,
                planningActionCode,
                notes);
        }).ToArray();

        return new ReorderPlanningView(command.BranchId, command.AsOfDate, lines);
    }

    public ReorderPlanHandoffView CreateOperationalHandoff(ReorderPlanningCommand command, long actorUserId)
    {
        var plan = BuildReorderPlan(command);

        var transferDrafts = new List<ReorderPlanHandoffTransferDraftView>();
        var procurementDrafts = new List<ReorderPlanHandoffProcurementDraftView>();

        var transferGroups = plan.Lines
            .Where(line => line.RecommendedTransferQuantity > 0m && line.SourceWarehouseId.HasValue)
            .GroupBy(line => new { line.BranchId, FromWarehouseId = line.SourceWarehouseId!.Value, line.TargetWarehouseId })
            .ToArray();

        var nextTransferSequence = (_dbContext.WarehouseTransfers.Max(entry => (long?)entry.Id) ?? 0L) + 1L;
        foreach (var group in transferGroups)
        {
            var transfer = new WarehouseTransfer(
                transferNumber: $"TRF-{nextTransferSequence:D6}",
                branchId: group.Key.BranchId,
                fromWarehouseId: group.Key.FromWarehouseId,
                toWarehouseId: group.Key.TargetWarehouseId,
                transferDate: command.AsOfDate,
                reasonCode: "REPLENISHMENT_PLAN",
                notes: $"Generated from reorder planning handoff for {command.AsOfDate:yyyy-MM-dd}.");

            var lineNumber = 1;
            foreach (var line in group.OrderBy(entry => entry.RuleCode))
            {
                transfer.AddLine(
                    lineNumber++,
                    line.ItemId,
                    null,
                    null,
                    line.RecommendedTransferQuantity,
                    line.UnitOfMeasureCode,
                    null,
                    "AVAILABLE",
                    null,
                    null,
                    $"Generated from replenishment rule {line.RuleCode}.");
            }

            _dbContext.WarehouseTransfers.Add(transfer);
            _dbContext.SaveChanges();

            transferDrafts.Add(new ReorderPlanHandoffTransferDraftView(
                transfer.Id,
                transfer.TransferNumber,
                transfer.BranchId,
                transfer.FromWarehouseId,
                transfer.ToWarehouseId,
                transfer.Lines.Count,
                transfer.TotalTransferQuantity,
                group.Select(entry => entry.ReplenishmentRuleId).Distinct().OrderBy(id => id).ToArray()));

            nextTransferSequence += 1L;
        }

        var procurementLines = plan.Lines
            .Where(line => line.RecommendedProcurementQuantity > 0m)
            .ToArray();

        if (procurementLines.Length > 0)
        {
            var nextRequisitionSequence = (_dbContext.PurchaseRequisitions.Max(entry => (long?)entry.Id) ?? 0L) + 1L;
            var requisition = new PurchaseRequisition(
                requisitionNumber: $"PR-{nextRequisitionSequence:D6}",
                branchId: command.BranchId,
                requesterUserId: actorUserId,
                requisitionDate: command.AsOfDate,
                needByDate: command.AsOfDate.AddDays(7),
                purposeText: $"Generated from reorder planning handoff for {command.AsOfDate:yyyy-MM-dd}.",
                currencyCode: "EGP");

            var lineNumber = 1;
            foreach (var line in procurementLines.OrderBy(entry => entry.RuleCode))
            {
                requisition.AddLine(
                    lineNumber++,
                    line.ItemId,
                    line.RecommendedProcurementQuantity,
                    line.UnitOfMeasureCode,
                    null,
                    command.AsOfDate.AddDays(7),
                    $"Generated from replenishment rule {line.RuleCode}.");
            }

            _dbContext.PurchaseRequisitions.Add(requisition);
            _dbContext.SaveChanges();

            procurementDrafts.Add(new ReorderPlanHandoffProcurementDraftView(
                requisition.Id,
                requisition.RequisitionNumber,
                requisition.BranchId,
                requisition.RequesterUserId,
                requisition.Lines.Count,
                requisition.EstimatedTotalAmount,
                procurementLines.Select(entry => entry.ReplenishmentRuleId).Distinct().OrderBy(id => id).ToArray()));
        }

        return new ReorderPlanHandoffView(command.BranchId, command.AsOfDate, transferDrafts, procurementDrafts);
    }

    private Dictionary<(long ItemId, long WarehouseId), decimal> BuildOnHandByWarehouse(long branchId, DateOnly asOfDate)
    {
        var cutoff = asOfDate.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
        var movements = _dbContext.InventoryMovements
            .AsNoTracking()
            .Where(movement =>
                movement.BranchId == branchId
                && movement.MovedAtUtc <= cutoff
                && movement.StatusId == WorkflowStatusSeedIds.Posted
                && movement.StockStatus == "AVAILABLE")
            .ToArray();

        var balances = new Dictionary<(long ItemId, long WarehouseId), decimal>();
        foreach (var movement in movements)
        {
            if (movement.ToWarehouseId.HasValue)
            {
                var key = (movement.ItemId, movement.ToWarehouseId.Value);
                balances[key] = balances.GetValueOrDefault(key, 0m) + movement.QuantityMoved;
            }

            if (movement.FromWarehouseId.HasValue)
            {
                var key = (movement.ItemId, movement.FromWarehouseId.Value);
                balances[key] = balances.GetValueOrDefault(key, 0m) - movement.QuantityMoved;
            }
        }

        return balances;
    }

    private static ReplenishmentRuleDetailView Map(ReplenishmentRule rule) =>
        new(
            rule.Id,
            rule.RuleCode,
            rule.RuleName,
            rule.BranchId,
            rule.TargetWarehouseId,
            rule.PreferredSourceWarehouseId,
            rule.ItemId,
            rule.UnitOfMeasureCode,
            rule.ReorderPointQuantity,
            rule.TargetSafetyStockQuantity,
            rule.TargetStockQuantity,
            rule.SourceSafetyStockQuantity,
            rule.Priority,
            rule.EffectiveFromDate,
            rule.EffectiveToDate,
            WorkflowStatusSeedIds.ToCode(rule.StatusId),
            rule.IsActive,
            rule.Notes,
            rule.SubmittedAtUtc,
            rule.SubmittedByUserId,
            rule.ApprovedAtUtc,
            rule.ApprovedByUserId,
            rule.ActivatedAtUtc,
            rule.ActivatedByUserId);
}
