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

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class RfqEvaluationRepository : IRfqEvaluationRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<RfqEvaluationDetailView> GetEvaluations(long? rfqId = null)
    {
        var query = _dbContext.RfqEvaluations
            .AsNoTracking()
            .Include(evaluation => evaluation.Lines)
            .AsQueryable();

        if (rfqId.HasValue)
        {
            query = query.Where(evaluation => evaluation.RequestForQuotationId == rfqId.Value);
        }

        return query
            .OrderByDescending(evaluation => evaluation.EvaluationDate)
            .ThenByDescending(evaluation => evaluation.Id)
            .ToArray()
            .Select(MapEvaluation)
            .ToArray();
    }

    public RfqEvaluationDetailView? GetEvaluation(long evaluationId)
    {
        var evaluation = _dbContext.RfqEvaluations
            .AsNoTracking()
            .Include(existingEvaluation => existingEvaluation.Lines)
            .SingleOrDefault(existingEvaluation => existingEvaluation.Id == evaluationId);

        return evaluation is null ? null : MapEvaluation(evaluation);
    }

    public RfqEvaluationDetailView Create(CreateRfqEvaluationCommand command)
    {
        var nextSequence = (_dbContext.RfqEvaluations.Max(evaluation => (long?)evaluation.Id) ?? 0L) + 1L;
        var evaluation = new RfqEvaluation(
            requestForQuotationId: command.RequestForQuotationId,
            evaluationNumber: $"EVAL-{nextSequence:D6}",
            evaluationDate: command.EvaluationDate,
            notes: command.Notes);

        foreach (var line in command.Lines.OrderBy(line => line.RankNumber))
        {
            evaluation.AddLine(
                line.QuotationId,
                line.SupplierId,
                line.PriceScore,
                line.CommercialScore,
                line.DeliveryScore,
                line.QualityScore,
                line.RankNumber,
                line.IsRecommended,
                line.Notes);
        }

        _dbContext.RfqEvaluations.Add(evaluation);
        _dbContext.SaveChanges();

        return GetEvaluation(evaluation.Id)!;
    }

    public RfqEvaluationDetailView FinalizeRecommendation(long evaluationId, long actorUserId)
    {
        var evaluation = _dbContext.RfqEvaluations
            .Include(existingEvaluation => existingEvaluation.Lines)
            .Single(existingEvaluation => existingEvaluation.Id == evaluationId);

        evaluation.FinalizeRecommendation(actorUserId);
        _dbContext.SaveChanges();
        return GetEvaluation(evaluation.Id)!;
    }

    public PurchaseOrderDetailView AwardPurchaseOrder(long evaluationId, long actorUserId, DateOnly purchaseOrderDate, long defaultWarehouseId)
    {
        var evaluation = _dbContext.RfqEvaluations
            .Single(existingEvaluation => existingEvaluation.Id == evaluationId);

        if (evaluation.ApprovedQuotationId is null)
        {
            throw new InvalidOperationException("Evaluation does not have an approved quotation.");
        }

        var quotation = _dbContext.SupplierQuotations
            .Include(existingQuotation => existingQuotation.Lines)
            .Single(existingQuotation => existingQuotation.Id == evaluation.ApprovedQuotationId.Value);

        quotation.SelectForAward(actorUserId);

        var rfq = _dbContext.RequestForQuotations.Single(existingRfq => existingRfq.Id == quotation.RequestForQuotationId);
        var nextSequence = (_dbContext.PurchaseOrders.Max(po => (long?)po.Id) ?? 0L) + 1L;
        var purchaseOrder = new PurchaseOrder(
            purchaseOrderNumber: $"PO-{nextSequence:D6}",
            branchId: rfq.BranchId,
            supplierId: quotation.SupplierId,
            purchaseRequisitionId: rfq.PurchaseRequisitionId,
            purchaseOrderDate: purchaseOrderDate,
            currencyCode: quotation.CurrencyCode,
            exchangeRate: quotation.FxRate ?? 1m);

        var lineNumber = 1;
        foreach (var line in quotation.Lines.OrderBy(existingLine => existingLine.LineNumber))
        {
            purchaseOrder.AddLine(
                lineNumber++,
                line.ItemId ?? 0L,
                defaultWarehouseId,
                line.QuotedQuantity,
                line.UnitOfMeasureCode,
                line.UnitPrice,
                line.TaxCode);
        }

        purchaseOrder.Approve(actorUserId);
        _dbContext.PurchaseOrders.Add(purchaseOrder);
        _dbContext.SaveChanges();

        return MapPurchaseOrder(purchaseOrder);
    }

    private static RfqEvaluationDetailView MapEvaluation(RfqEvaluation evaluation) =>
        new(
            evaluation.Id,
            evaluation.RequestForQuotationId,
            evaluation.EvaluationNumber,
            evaluation.EvaluationDate,
            WorkflowStatusSeedIds.ToCode(evaluation.StatusId),
            evaluation.RecommendedSupplierId,
            evaluation.ApprovedQuotationId,
            evaluation.Notes,
            evaluation.Lines.OrderBy(line => line.RankNumber).Select(line => new RfqEvaluationLineView(
                line.Id,
                line.QuotationId,
                line.SupplierId,
                line.PriceScore,
                line.CommercialScore,
                line.DeliveryScore,
                line.QualityScore,
                line.TotalScore,
                line.RankNumber,
                line.IsRecommended,
                line.Notes)).ToArray());

    private static PurchaseOrderDetailView MapPurchaseOrder(PurchaseOrder purchaseOrder) =>
        new(
            purchaseOrder.Id,
            purchaseOrder.PurchaseOrderNumber,
            purchaseOrder.BranchId,
            purchaseOrder.SupplierId,
            purchaseOrder.PurchaseRequisitionId,
            purchaseOrder.PurchaseOrderDate,
            purchaseOrder.CurrencyCode,
            purchaseOrder.ExchangeRate,
            WorkflowStatusSeedIds.ToCode(purchaseOrder.StatusId),
            purchaseOrder.SubtotalAmount,
            purchaseOrder.TaxAmount,
            purchaseOrder.TotalAmount,
            purchaseOrder.Lines.OrderBy(line => line.LineNumber).Select(line => new PurchaseOrderLineView(
                line.Id,
                line.LineNumber,
                line.ItemId,
                line.WarehouseId,
                line.OrderedQuantity,
                line.ReceivedQuantity,
                line.UnitOfMeasureCode,
                line.UnitPrice,
                line.TaxCode,
                line.LineNetAmount,
                line.LineTaxAmount,
                line.LineTotalAmount)).ToArray());
}
