using CashLineERP.Application.Procurement;
using CashLineERP.Contracts.Procurement;
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 PurchaseReturnRepository : IPurchaseReturnRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<PurchaseReturnDetailView> GetPurchaseReturns() =>
        _dbContext.Set<PurchaseReturn>()
            .AsNoTracking()
            .Include(purchaseReturn => purchaseReturn.Lines)
            .OrderByDescending(purchaseReturn => purchaseReturn.ReturnDate)
            .ThenByDescending(purchaseReturn => purchaseReturn.Id)
            .ToArray()
            .Select(Map)
            .ToArray();

    public PurchaseReturnDetailView? GetPurchaseReturn(long purchaseReturnId)
    {
        var purchaseReturn = _dbContext.Set<PurchaseReturn>()
            .AsNoTracking()
            .Include(existingReturn => existingReturn.Lines)
            .SingleOrDefault(existingReturn => existingReturn.Id == purchaseReturnId);

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

    public PurchaseReturnDetailView Create(CreatePurchaseReturnCommand command)
    {
        var goodsReceipt = _dbContext.GoodsReceipts
            .Include(existingReceipt => existingReceipt.Lines)
            .SingleOrDefault(existingReceipt => existingReceipt.Id == command.GoodsReceiptId);

        if (goodsReceipt is null)
        {
            throw new InvalidOperationException("Purchase return requires a valid goods receipt.");
        }

        if (goodsReceipt.PurchaseOrderId != command.PurchaseOrderId)
        {
            throw new InvalidOperationException("Purchase return purchase order must match the source goods receipt.");
        }

        if (goodsReceipt.SupplierId != command.SupplierId)
        {
            throw new InvalidOperationException("Purchase return supplier must match the source goods receipt supplier.");
        }

        var purchaseOrder = _dbContext.PurchaseOrders
            .Include(existingOrder => existingOrder.Lines)
            .Single(existingOrder => existingOrder.Id == command.PurchaseOrderId);

        var nextSequence = (_dbContext.Set<PurchaseReturn>().Max(purchaseReturn => (long?)purchaseReturn.Id) ?? 0L) + 1L;
        var purchaseReturn = new PurchaseReturn(
            purchaseReturnNumber: $"PRTN-{nextSequence:D6}",
            branchId: command.BranchId,
            supplierId: command.SupplierId,
            purchaseOrderId: command.PurchaseOrderId,
            goodsReceiptId: command.GoodsReceiptId,
            returnDate: command.ReturnDate,
            returnWarehouseId: command.ReturnWarehouseId,
            reasonCode: command.ReasonCode,
            notes: command.Notes);

        var lineNumber = 1;
        foreach (var line in command.Lines)
        {
            if (line.QuantityReturned <= 0m)
            {
                throw new InvalidOperationException("Purchase return quantity must be greater than zero.");
            }

            var goodsReceiptLine = goodsReceipt.Lines.SingleOrDefault(existingLine => existingLine.Id == line.GoodsReceiptLineId);
            if (goodsReceiptLine is null)
            {
                throw new InvalidOperationException("Purchase return line must reference a valid goods receipt line.");
            }

            if (goodsReceiptLine.PurchaseOrderLineId != line.PurchaseOrderLineId || goodsReceiptLine.ItemId != line.ItemId)
            {
                throw new InvalidOperationException("Purchase return line must match the source goods receipt line and purchase order line.");
            }

            if (goodsReceiptLine.WarehouseId != command.ReturnWarehouseId)
            {
                throw new InvalidOperationException("Purchase return warehouse must match the source goods receipt warehouse.");
            }

            if (line.QuantityReturned > goodsReceiptLine.QuantityAccepted)
            {
                throw new InvalidOperationException("Purchase return quantity cannot exceed accepted receipt quantity.");
            }

            var purchaseOrderLine = purchaseOrder.Lines.Single(existingLine => existingLine.Id == line.PurchaseOrderLineId);
            if (purchaseOrderLine.ItemId != line.ItemId)
            {
                throw new InvalidOperationException("Purchase return line item must match the purchase order line.");
            }

            purchaseReturn.AddLine(
                lineNumber++,
                line.GoodsReceiptLineId,
                line.PurchaseOrderLineId,
                line.ItemId,
                line.QuantityReturned,
                line.WarehouseLocationId,
                line.BatchNumber,
                line.SerialNumber,
                line.Notes);
        }

        _dbContext.Add(purchaseReturn);
        _dbContext.SaveChanges();
        return GetPurchaseReturn(purchaseReturn.Id)!;
    }

    public PurchaseReturnDetailView Submit(long purchaseReturnId, long actorUserId)
    {
        var purchaseReturn = _dbContext.Set<PurchaseReturn>()
            .Include(existingReturn => existingReturn.Lines)
            .Single(existingReturn => existingReturn.Id == purchaseReturnId);

        purchaseReturn.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "PROCUREMENT",
            documentType: "PURCHASE_RETURN",
            documentId: purchaseReturn.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Purchase return submitted for review.");

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetPurchaseReturn(purchaseReturn.Id)!;
    }

    public PurchaseReturnDetailView Post(long purchaseReturnId, long actorUserId)
    {
        var purchaseReturn = _dbContext.Set<PurchaseReturn>()
            .Include(existingReturn => existingReturn.Lines)
            .Single(existingReturn => existingReturn.Id == purchaseReturnId);

        if (purchaseReturn.StatusId == WorkflowStatusSeedIds.Posted)
        {
            return GetPurchaseReturn(purchaseReturn.Id)!;
        }

        purchaseReturn.Post(actorUserId);

        var nextSequence = (_dbContext.InventoryMovements.Max(movement => (long?)movement.Id) ?? 0L) + 1L;
        foreach (var line in purchaseReturn.Lines.OrderBy(line => line.LineNumber))
        {
            var movement = new InventoryMovement(
                movementNumber: $"IM-{nextSequence + line.LineNumber - 1:D6}",
                movementType: "PURCHASE_RETURN_ISSUE",
                branchId: purchaseReturn.BranchId,
                itemId: line.ItemId,
                fromWarehouseId: purchaseReturn.ReturnWarehouseId,
                fromWarehouseLocationId: line.WarehouseLocationId,
                toWarehouseId: null,
                toWarehouseLocationId: null,
                batchNumber: line.BatchNumber,
                serialNumber: line.SerialNumber,
                stockStatus: "AVAILABLE",
                quantityMoved: line.QuantityReturned,
                unitCost: null,
                movedAtUtc: purchaseReturn.ReturnDate.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc));

            _dbContext.InventoryMovements.Add(movement);
        }

        _dbContext.SaveChanges();
        return GetPurchaseReturn(purchaseReturn.Id)!;
    }

    public PurchaseReturnDetailView IssueDebitNote(long purchaseReturnId, IssuePurchaseReturnDebitNoteRequest request)
    {
        var purchaseReturn = _dbContext.Set<PurchaseReturn>()
            .Include(existingReturn => existingReturn.Lines)
            .Single(existingReturn => existingReturn.Id == purchaseReturnId);

        if (purchaseReturn.StatusId != WorkflowStatusSeedIds.Posted)
        {
            throw new InvalidOperationException("Debit note issuance requires a posted purchase return.");
        }

        if (purchaseReturn.DebitNoteBillId.HasValue)
        {
            return GetPurchaseReturn(purchaseReturn.Id)!;
        }

        var purchaseOrder = _dbContext.PurchaseOrders
            .Include(existingOrder => existingOrder.Lines)
            .Single(existingOrder => existingOrder.Id == purchaseReturn.PurchaseOrderId);

        var nextSequence = (_dbContext.AccountsPayableBills.Max(bill => (long?)bill.Id) ?? 0L) + 1L;
        var debitNote = new AccountsPayableBill(
            billNumber: $"BILL-{nextSequence:D6}",
            supplierId: purchaseReturn.SupplierId,
            purchaseOrderId: purchaseReturn.PurchaseOrderId,
            goodsReceiptId: purchaseReturn.GoodsReceiptId,
            purchaseReturnId: purchaseReturn.Id,
            billType: "DEBIT_NOTE",
            billDate: request.BillDate,
            dueDate: request.DueDate,
            currencyCode: purchaseOrder.CurrencyCode);

        var lineNumber = 1;
        foreach (var line in purchaseReturn.Lines.OrderBy(line => line.LineNumber))
        {
            var purchaseOrderLine = purchaseOrder.Lines.Single(existingLine => existingLine.Id == line.PurchaseOrderLineId);
            debitNote.AddLine(
                lineNumber++,
                purchaseOrderLineId: purchaseOrderLine.Id,
                itemId: line.ItemId,
                quantityBilled: line.QuantityReturned,
                unitOfMeasureCode: purchaseOrderLine.UnitOfMeasureCode,
                unitPrice: purchaseOrderLine.UnitPrice,
                taxCode: purchaseOrderLine.TaxCode);
        }

        _dbContext.AccountsPayableBills.Add(debitNote);
        _dbContext.SaveChanges();

        purchaseReturn.LinkDebitNote(request.ActorUserId, debitNote.Id);
        _dbContext.SaveChanges();
        return GetPurchaseReturn(purchaseReturn.Id)!;
    }

    private static PurchaseReturnDetailView Map(PurchaseReturn purchaseReturn) =>
        new(
            purchaseReturn.Id,
            purchaseReturn.PurchaseReturnNumber,
            purchaseReturn.BranchId,
            purchaseReturn.SupplierId,
            purchaseReturn.PurchaseOrderId,
            purchaseReturn.GoodsReceiptId,
            purchaseReturn.ReturnDate,
            purchaseReturn.ReturnWarehouseId,
            purchaseReturn.ReasonCode,
            purchaseReturn.Notes,
            WorkflowStatusSeedIds.ToCode(purchaseReturn.StatusId),
            purchaseReturn.TotalReturnedQuantity,
            purchaseReturn.DebitNoteBillId,
            purchaseReturn.DebitNoteIssuedAtUtc,
            purchaseReturn.Lines.OrderBy(line => line.LineNumber).Select(line => new PurchaseReturnLineView(
                line.Id,
                line.LineNumber,
                line.GoodsReceiptLineId,
                line.PurchaseOrderLineId,
                line.ItemId,
                line.QuantityReturned,
                line.WarehouseLocationId,
                line.BatchNumber,
                line.SerialNumber,
                line.Notes)).ToArray());
}
