using CashLineERP.Application.IslamicFinance.Disbursements;
using CashLineERP.Application.IslamicFinance;
using CashLineERP.Domain.IslamicFinance.Disbursements;
using CashLineERP.SharedKernel.Documents;
using CashLineERP.SharedKernel.IslamicFinance;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories.IslamicFinance;

public sealed class IslamicFinanceMurabahaDisbursementRepository : IIslamicFinanceMurabahaDisbursementRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<IslamicFinanceMurabahaDisbursementDetailView> GetDisbursements(
        long? murabahaCaseId = null,
        string? statusCode = null,
        string? targetType = null)
    {
        var normalizedStatusCode = string.IsNullOrWhiteSpace(statusCode)
            ? null
            : IslamicFinanceWorkflowCatalog.NormalizeStatusCode(statusCode);
        var normalizedTargetType = string.IsNullOrWhiteSpace(targetType)
            ? null
            : IslamicFinanceDisbursementCatalog.NormalizeTargetType(targetType);

        return _dbContext.Set<IslamicFinanceMurabahaDisbursement>()
            .AsNoTracking()
            .Where(entry => !murabahaCaseId.HasValue || entry.MurabahaCaseId == murabahaCaseId.Value)
            .Where(entry => normalizedStatusCode == null || entry.StatusCode == normalizedStatusCode)
            .Where(entry => normalizedTargetType == null || entry.TargetType == normalizedTargetType)
            .OrderBy(entry => entry.DisbursementNumber)
            .Select(Map)
            .ToArray();
    }

    public IslamicFinanceMurabahaDisbursementDetailView? GetDisbursement(long disbursementId)
    {
        var entry = _dbContext.Set<IslamicFinanceMurabahaDisbursement>()
            .AsNoTracking()
            .SingleOrDefault(item => item.Id == disbursementId);

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

    public IslamicFinanceMurabahaDisbursementDetailView Create(CreateIslamicFinanceMurabahaDisbursementCommand command)
    {
        var murabahaCase = _dbContext.IslamicFinanceMurabahaCases
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == command.MurabahaCaseId);

        if (murabahaCase is null)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement requires a valid Murabaha case.");
        }

        if (!string.Equals(murabahaCase.StatusCode, IslamicFinanceWorkflowCatalog.Approved, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(murabahaCase.StatusCode, IslamicFinanceWorkflowCatalog.Active, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement can only be created from an approved or active Murabaha case.");
        }

        var request = _dbContext.IslamicFinanceRequests
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == murabahaCase.FinancingRequestId);

        if (request is null)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement requires a valid financing request.");
        }

        if (!string.Equals(request.FundingSourceMode, IslamicFinanceProductCatalog.ToCode(IslamicFinanceFundingSourceMode.Partner), StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement currently supports partner-funded execution only.");
        }

        var hasGeneratedContract = _dbContext.DocumentLinks
            .AsNoTracking()
            .Any(document =>
                document.ModuleCode == IslamicFinanceDocumentCatalog.ModuleCode
                && document.EntityName == IslamicFinanceDocumentCatalog.MurabahaCaseEntityName
                && document.EntityId == murabahaCase.Id
                && document.DocumentType == IslamicFinanceDocumentCatalog.MurabahaContractDocumentType
                && document.LifecycleStatusCode == DocumentLifecycleStatusCode.Active);

        if (!hasGeneratedContract)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement requires an active generated Murabaha contract.");
        }

        var hasSchedule = _dbContext.IslamicFinanceProfitSchedules
            .AsNoTracking()
            .Any(entry => entry.MurabahaCaseId == murabahaCase.Id);

        if (!hasSchedule)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement requires a generated profit schedule.");
        }

        var normalizedDisbursementNumber = command.DisbursementNumber.Trim().ToUpperInvariant();
        if (_dbContext.Set<IslamicFinanceMurabahaDisbursement>().AsNoTracking().Any(entry => entry.DisbursementNumber == normalizedDisbursementNumber))
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement number already exists.");
        }

        var supplierPartyId = command.TargetType == IslamicFinanceDisbursementTargetType.SupplierPayment
            ? command.SupplierPartyId ?? murabahaCase.SupplierPartyId
            : command.SupplierPartyId;

        if (supplierPartyId.HasValue && !_dbContext.BusinessParties.AsNoTracking().Any(entry => entry.Id == supplierPartyId.Value))
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement supplier party reference is not valid.");
        }

        var nonCancelledReservedAmount = _dbContext.Set<IslamicFinanceMurabahaDisbursement>()
            .AsNoTracking()
            .Where(entry => entry.MurabahaCaseId == murabahaCase.Id)
            .Where(entry => entry.StatusCode != IslamicFinanceWorkflowCatalog.Rejected)
            .Where(entry => entry.StatusCode != IslamicFinanceWorkflowCatalog.Returned)
            .Select(entry => entry.InstructedAmount)
            .AsEnumerable()
            .Sum();

        if (nonCancelledReservedAmount + command.InstructedAmount > murabahaCase.PurchaseCostAmount)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha disbursement cannot exceed the Murabaha purchase cost envelope.");
        }

        var entry = new IslamicFinanceMurabahaDisbursement(
            normalizedDisbursementNumber,
            murabahaCase.Id,
            murabahaCase.CustomerPartyId,
            murabahaCase.PartnerLenderId,
            supplierPartyId,
            IslamicFinanceFundingSourceMode.Partner,
            command.TargetType,
            murabahaCase.TransactionCurrencyCode,
            command.InstructedAmount,
            command.PlannedExecutionDate);

        _dbContext.Add(entry);
        _dbContext.SaveChanges();
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.SettlementCreated,
            entry.CreatedByUserId);
        _dbContext.SaveChanges();
        return GetDisbursement(entry.Id)!;
    }

    public IslamicFinanceMurabahaDisbursementDetailView Submit(long disbursementId, long actorUserId)
    {
        var entry = GetTracked(disbursementId);
        entry.Submit(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.SettlementSubmitted,
            actorUserId);
        _dbContext.SaveChanges();
        return GetDisbursement(entry.Id)!;
    }

    public IslamicFinanceMurabahaDisbursementDetailView Approve(long disbursementId, long actorUserId)
    {
        var entry = GetTracked(disbursementId);
        entry.Approve(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.SettlementApproved,
            actorUserId);
        _dbContext.SaveChanges();
        return GetDisbursement(entry.Id)!;
    }

    public IslamicFinanceMurabahaDisbursementDetailView Return(long disbursementId, long actorUserId)
    {
        var entry = GetTracked(disbursementId);
        entry.Return(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.SettlementReturned,
            actorUserId);
        _dbContext.SaveChanges();
        return GetDisbursement(entry.Id)!;
    }

    public IslamicFinanceMurabahaDisbursementDetailView Reject(long disbursementId, long actorUserId)
    {
        var entry = GetTracked(disbursementId);
        entry.Reject(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.SettlementRejected,
            actorUserId);
        _dbContext.SaveChanges();
        return GetDisbursement(entry.Id)!;
    }

    public IslamicFinanceMurabahaDisbursementDetailView Execute(long disbursementId, ExecuteIslamicFinanceMurabahaDisbursementCommand command, long actorUserId)
    {
        var entry = GetTracked(disbursementId);
        var executedAtUtc = command.ExecutedAtUtc ?? DateTime.UtcNow;
        entry.Execute(
            actorUserId,
            command.ExecutionReferenceNumber,
            executedAtUtc,
            command.ExecutedAmount,
            command.ExecutionNotes);

        var murabahaCase = _dbContext.IslamicFinanceMurabahaCases.Single(caseEntry => caseEntry.Id == entry.MurabahaCaseId);
        if (string.Equals(murabahaCase.StatusCode, IslamicFinanceWorkflowCatalog.Approved, StringComparison.OrdinalIgnoreCase))
        {
            murabahaCase.Activate(actorUserId);
        }

        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.SettlementExecuted,
            actorUserId);
        _dbContext.SaveChanges();
        return GetDisbursement(entry.Id)!;
    }

    public IslamicFinanceMurabahaDisbursementDetailView Suspend(long disbursementId, string reasonCode, long actorUserId)
    {
        var entry = GetTracked(disbursementId);
        entry.Suspend(actorUserId, reasonCode);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.SettlementSuspended,
            actorUserId);
        _dbContext.SaveChanges();
        return GetDisbursement(entry.Id)!;
    }

    public IslamicFinanceMurabahaDisbursementDetailView Close(long disbursementId, long actorUserId)
    {
        var entry = GetTracked(disbursementId);
        entry.Close(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.SettlementClosed,
            actorUserId);
        _dbContext.SaveChanges();
        return GetDisbursement(entry.Id)!;
    }

    public IReadOnlyCollection<IslamicFinanceLifecycleHistoryEntryView> GetDisbursementHistory(long disbursementId)
    {
        var disbursement = _dbContext.Set<IslamicFinanceMurabahaDisbursement>()
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == disbursementId);

        if (disbursement is null)
        {
            return Array.Empty<IslamicFinanceLifecycleHistoryEntryView>();
        }

        var events = new List<IslamicFinanceLifecycleHistoryEntryView>
        {
            new(
                disbursement.Id,
                "DISBURSEMENT_CREATED",
                "DISBURSEMENT",
                disbursement.Id,
                disbursement.CreatedByUserId,
                disbursement.DisbursementNumber,
                disbursement.CreatedAtUtc)
        };

        AddIfPresent(events, disbursement.Id, "DISBURSEMENT_SUBMITTED", "DISBURSEMENT", disbursement.Id, disbursement.SubmittedByUserId, null, disbursement.SubmittedAtUtc);
        AddIfPresent(events, disbursement.Id, "DISBURSEMENT_APPROVED", "DISBURSEMENT", disbursement.Id, disbursement.ApprovedByUserId, null, disbursement.ApprovedAtUtc);
        AddIfPresent(events, disbursement.Id, "DISBURSEMENT_EXECUTED", "DISBURSEMENT", disbursement.Id, disbursement.ExecutedByUserId, disbursement.ExecutionReferenceNumber, disbursement.ExecutedAtUtc);
        AddIfPresent(events, disbursement.Id, "DISBURSEMENT_SUSPENDED", "DISBURSEMENT", disbursement.Id, disbursement.SuspendedByUserId, disbursement.SuspensionReasonCode, disbursement.SuspendedAtUtc);

        if (string.Equals(disbursement.StatusCode, IslamicFinanceWorkflowCatalog.Returned, StringComparison.OrdinalIgnoreCase) && disbursement.UpdatedAtUtc.HasValue)
        {
            events.Add(new IslamicFinanceLifecycleHistoryEntryView(
                disbursement.Id,
                "DISBURSEMENT_RETURNED",
                "DISBURSEMENT",
                disbursement.Id,
                disbursement.UpdatedByUserId,
                null,
                disbursement.UpdatedAtUtc.Value));
        }

        if (string.Equals(disbursement.StatusCode, IslamicFinanceWorkflowCatalog.Rejected, StringComparison.OrdinalIgnoreCase) && disbursement.UpdatedAtUtc.HasValue)
        {
            events.Add(new IslamicFinanceLifecycleHistoryEntryView(
                disbursement.Id,
                "DISBURSEMENT_REJECTED",
                "DISBURSEMENT",
                disbursement.Id,
                disbursement.UpdatedByUserId,
                null,
                disbursement.UpdatedAtUtc.Value));
        }

        if (string.Equals(disbursement.StatusCode, IslamicFinanceWorkflowCatalog.Closed, StringComparison.OrdinalIgnoreCase) && disbursement.UpdatedAtUtc.HasValue)
        {
            events.Add(new IslamicFinanceLifecycleHistoryEntryView(
                disbursement.Id,
                "DISBURSEMENT_CLOSED",
                "DISBURSEMENT",
                disbursement.Id,
                disbursement.UpdatedByUserId,
                null,
                disbursement.UpdatedAtUtc.Value));
        }

        var settlementLinks = _dbContext.Set<Domain.IslamicFinance.Settlements.IslamicFinanceSettlementLine>()
            .AsNoTracking()
            .Where(entry => entry.MurabahaDisbursementId == disbursementId)
            .OrderBy(entry => entry.CreatedAtUtc)
            .ToArray();

        if (settlementLinks.Length > 0)
        {
            var settlementIdList = settlementLinks
                .Select(entry => entry.IslamicFinanceSettlementHeaderId)
                .Distinct()
                .ToList();
            var settlementsById = _dbContext.IslamicFinanceSettlementHeaders
                .AsNoTracking()
                .Where(entry => settlementIdList.Contains(entry.Id))
                .ToArray()
                .ToDictionary(entry => entry.Id);

            foreach (var line in settlementLinks)
            {
                events.Add(new IslamicFinanceLifecycleHistoryEntryView(
                    disbursement.Id,
                    "SETTLEMENT_LINKED",
                    "SETTLEMENT",
                    line.IslamicFinanceSettlementHeaderId,
                    line.CreatedByUserId,
                    settlementsById.TryGetValue(line.IslamicFinanceSettlementHeaderId, out var settlement) ? settlement.SettlementNumber : null,
                    line.CreatedAtUtc));

                if (line.UpdatedAtUtc.HasValue)
                {
                    events.Add(new IslamicFinanceLifecycleHistoryEntryView(
                        disbursement.Id,
                        "SETTLEMENT_LINE_SETTLED",
                        "SETTLEMENT",
                        line.IslamicFinanceSettlementHeaderId,
                        line.UpdatedByUserId,
                        settlementsById.TryGetValue(line.IslamicFinanceSettlementHeaderId, out settlement) ? settlement.SettlementNumber : null,
                        line.UpdatedAtUtc.Value));
                }
            }
        }

        return events.OrderBy(entry => entry.OccurredAtUtc).ThenBy(entry => entry.EventCode).ToArray();
    }

    public IslamicFinanceMurabahaDisbursementAuditView? GetDisbursementAudit(long disbursementId)
    {
        var disbursement = GetDisbursement(disbursementId);
        if (disbursement is null)
        {
            return null;
        }

        return new IslamicFinanceMurabahaDisbursementAuditView(
            disbursement,
            GetDisbursementHistory(disbursementId));
    }

    private IslamicFinanceMurabahaDisbursement GetTracked(long disbursementId) =>
        _dbContext.Set<IslamicFinanceMurabahaDisbursement>().Single(entry => entry.Id == disbursementId);

    private static IslamicFinanceMurabahaDisbursementDetailView Map(IslamicFinanceMurabahaDisbursement entry) =>
        new(
            entry.Id,
            entry.DisbursementNumber,
            entry.MurabahaCaseId,
            entry.CustomerPartyId,
            entry.PartnerLenderId,
            entry.SupplierPartyId,
            IslamicFinanceProductCatalog.NormalizeFundingSourceMode(entry.FundingSourceMode),
            IslamicFinanceDisbursementCatalog.NormalizeTargetType(entry.TargetType),
            entry.TransactionCurrencyCode,
            entry.InstructedAmount,
            entry.ExecutedAmount,
            entry.Amount.OutstandingAmount,
            entry.PlannedExecutionDate,
            IslamicFinanceWorkflowCatalog.NormalizeStatusCode(entry.StatusCode),
            entry.WorkflowInstanceId,
            entry.CreatedAtUtc,
            entry.CreatedByUserId,
            entry.SubmittedAtUtc,
            entry.SubmittedByUserId,
            entry.ApprovedAtUtc,
            entry.ApprovedByUserId,
            entry.ExecutedAtUtc,
            entry.ExecutedByUserId,
            entry.ExecutionReferenceNumber,
            entry.ExecutionNotes,
            entry.SuspendedAtUtc,
            entry.SuspendedByUserId,
            entry.SuspensionReasonCode,
            entry.UpdatedAtUtc,
            entry.UpdatedByUserId);

    private static void AddIfPresent(
        ICollection<IslamicFinanceLifecycleHistoryEntryView> events,
        long entityId,
        string eventCode,
        string sourceType,
        long? sourceId,
        long? actorUserId,
        string? notes,
        DateTime? occurredAtUtc)
    {
        if (!occurredAtUtc.HasValue)
        {
            return;
        }

        events.Add(new IslamicFinanceLifecycleHistoryEntryView(
            entityId,
            eventCode,
            sourceType,
            sourceId,
            actorUserId,
            notes,
            occurredAtUtc.Value));
    }
}
