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

namespace CashLineERP.Infrastructure.Persistence.Repositories.IslamicFinance;

public sealed class IslamicFinanceSettlementRepository : IIslamicFinanceSettlementRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<IslamicFinanceSettlementDetailView> GetSettlements(long? murabahaCaseId = null, string? statusCode = null)
    {
        var normalizedStatusCode = string.IsNullOrWhiteSpace(statusCode)
            ? null
            : IslamicFinanceWorkflowCatalog.NormalizeStatusCode(statusCode);

        var headers = _dbContext.Set<IslamicFinanceSettlementHeader>()
            .AsNoTracking()
            .Where(entry => !murabahaCaseId.HasValue || entry.MurabahaCaseId == murabahaCaseId.Value)
            .Where(entry => normalizedStatusCode == null || entry.StatusCode == normalizedStatusCode)
            .OrderByDescending(entry => entry.SettlementDate)
            .ThenBy(entry => entry.SettlementNumber)
            .ToArray();

        return headers.Select(Map).ToArray();
    }

    public IslamicFinanceSettlementDetailView? GetSettlement(long settlementHeaderId)
    {
        var header = _dbContext.Set<IslamicFinanceSettlementHeader>()
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == settlementHeaderId);

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

    public IslamicFinanceSettlementDetailView Create(CreateIslamicFinanceSettlementCommand command)
    {
        ValidateCreateCommand(command);

        var murabahaCase = _dbContext.IslamicFinanceMurabahaCases
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == command.MurabahaCaseId)
            ?? throw new InvalidOperationException("Islamic Finance settlement requires a valid Murabaha case.");

        if (!string.Equals(murabahaCase.StatusCode, IslamicFinanceWorkflowCatalog.Active, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Islamic Finance settlement currently requires an active Murabaha case.");
        }

        var financingRequest = _dbContext.IslamicFinanceRequests
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == murabahaCase.FinancingRequestId)
            ?? throw new InvalidOperationException("Islamic Finance settlement requires a valid financing request.");

        var normalizedSettlementNumber = command.SettlementNumber.Trim().ToUpperInvariant();
        if (_dbContext.Set<IslamicFinanceSettlementHeader>().AsNoTracking().Any(entry => entry.SettlementNumber == normalizedSettlementNumber))
        {
            throw new InvalidOperationException("Islamic Finance settlement number already exists.");
        }

        var disbursementIds = command.Lines.Select(entry => entry.MurabahaDisbursementId).Distinct().ToArray();
        var disbursementIdList = disbursementIds.ToList();
        var disbursements = _dbContext.IslamicFinanceMurabahaDisbursements
            .Where(entry => disbursementIdList.Contains(entry.Id))
            .ToArray();

        if (disbursements.Length != disbursementIds.Length)
        {
            throw new InvalidOperationException("Islamic Finance settlement references an unknown Murabaha disbursement.");
        }

        var disbursementsById = disbursements.ToDictionary(entry => entry.Id);
        foreach (var line in command.Lines)
        {
            var disbursement = disbursementsById[line.MurabahaDisbursementId];
            if (disbursement.MurabahaCaseId != murabahaCase.Id)
            {
                throw new InvalidOperationException("Islamic Finance settlement lines must belong to the referenced Murabaha case.");
            }

            if (!string.Equals(disbursement.StatusCode, IslamicFinanceWorkflowCatalog.Active, StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("Islamic Finance settlement currently requires active executed Murabaha disbursements.");
            }

            if (disbursement.ExecutedAmount <= 0m)
            {
                throw new InvalidOperationException("Islamic Finance settlement requires executed Murabaha disbursements.");
            }

            if (!string.Equals(disbursement.TransactionCurrencyCode, command.SettlementCurrencyCode.Trim().ToUpperInvariant(), StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("Islamic Finance settlement currency must match the linked Murabaha disbursement currency.");
            }

            if (line.SettledAmount != disbursement.ExecutedAmount)
            {
                throw new InvalidOperationException("Current Islamic Finance settlement linkage baseline requires settlement amount to match the executed disbursement amount.");
            }
        }

        var alreadyLinkedDisbursementIds = _dbContext.Set<IslamicFinanceSettlementLine>()
            .AsNoTracking()
            .Where(entry => disbursementIdList.Contains(entry.MurabahaDisbursementId))
            .Select(entry => entry.MurabahaDisbursementId)
            .Distinct()
            .ToArray();

        if (alreadyLinkedDisbursementIds.Length > 0)
        {
            throw new InvalidOperationException($"Islamic Finance settlement cannot relink Murabaha disbursement IDs already linked to settlement: {string.Join(", ", alreadyLinkedDisbursementIds.Order())}.");
        }

        var totalSettledAmount = command.Lines.Sum(entry => entry.SettledAmount);
        var header = new IslamicFinanceSettlementHeader(
            normalizedSettlementNumber,
            murabahaCase.Id,
            financingRequest.Id,
            murabahaCase.PartnerLenderId,
            IslamicFinanceFundingSourceMode.Partner,
            command.SettlementDate,
            command.SettlementCurrencyCode,
            totalSettledAmount);

        _dbContext.Add(header);
        _dbContext.SaveChanges();

        var lines = command.Lines.Select(line => new IslamicFinanceSettlementLine(
            header.Id,
            line.MurabahaDisbursementId,
            line.SettledAmount)).ToArray();
        _dbContext.AddRange(lines);

        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            header,
            lines.Select(entry => entry.MurabahaDisbursementId).ToArray(),
            IslamicFinanceIntegrationEventCatalog.SettlementCreated,
            header.CreatedByUserId);

        _dbContext.SaveChanges();
        return GetSettlement(header.Id)!;
    }

    public IslamicFinanceSettlementDetailView Submit(long settlementHeaderId, long actorUserId)
    {
        var header = GetTracked(settlementHeaderId);
        header.Submit(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            header,
            GetSettlementDisbursementIds(header.Id),
            IslamicFinanceIntegrationEventCatalog.SettlementSubmitted,
            actorUserId);
        _dbContext.SaveChanges();
        return GetSettlement(header.Id)!;
    }

    public IslamicFinanceSettlementDetailView Approve(long settlementHeaderId, long actorUserId)
    {
        var header = GetTracked(settlementHeaderId);
        header.Approve(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            header,
            GetSettlementDisbursementIds(header.Id),
            IslamicFinanceIntegrationEventCatalog.SettlementApproved,
            actorUserId);
        _dbContext.SaveChanges();
        return GetSettlement(header.Id)!;
    }

    public IslamicFinanceSettlementDetailView Close(long settlementHeaderId, long actorUserId)
    {
        var header = GetTracked(settlementHeaderId);
        var lines = _dbContext.Set<IslamicFinanceSettlementLine>()
            .Where(entry => entry.IslamicFinanceSettlementHeaderId == header.Id)
            .ToArray();

        var disbursementIds = lines.Select(entry => entry.MurabahaDisbursementId).Distinct().ToArray();
        var disbursementIdList = disbursementIds.ToList();
        var disbursements = _dbContext.IslamicFinanceMurabahaDisbursements
            .Where(entry => disbursementIdList.Contains(entry.Id))
            .ToArray()
            .ToDictionary(entry => entry.Id);

        foreach (var line in lines)
        {
            line.MarkSettled(actorUserId);
            disbursements[line.MurabahaDisbursementId].Close(actorUserId);
        }

        header.Close(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueSettlementEvent(
            _dbContext,
            header,
            disbursementIds,
            IslamicFinanceIntegrationEventCatalog.SettlementClosed,
            actorUserId);
        _dbContext.SaveChanges();
        return GetSettlement(header.Id)!;
    }

    public IReadOnlyCollection<IslamicFinanceLifecycleHistoryEntryView> GetSettlementHistory(long settlementHeaderId)
    {
        var settlement = _dbContext.Set<IslamicFinanceSettlementHeader>()
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == settlementHeaderId);

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

        var events = new List<IslamicFinanceLifecycleHistoryEntryView>
        {
            new(
                settlement.Id,
                "SETTLEMENT_CREATED",
                "SETTLEMENT",
                settlement.Id,
                settlement.CreatedByUserId,
                settlement.SettlementNumber,
                settlement.CreatedAtUtc)
        };

        var lines = _dbContext.Set<IslamicFinanceSettlementLine>()
            .AsNoTracking()
            .Where(entry => entry.IslamicFinanceSettlementHeaderId == settlementHeaderId)
            .OrderBy(entry => entry.CreatedAtUtc)
            .ToArray();

        foreach (var line in lines)
        {
            events.Add(new IslamicFinanceLifecycleHistoryEntryView(
                settlement.Id,
                "SETTLEMENT_LINE_LINKED",
                "SETTLEMENT_LINE",
                line.Id,
                line.CreatedByUserId,
                $"Disbursement {line.MurabahaDisbursementId}",
                line.CreatedAtUtc));

            if (line.UpdatedAtUtc.HasValue)
            {
                events.Add(new IslamicFinanceLifecycleHistoryEntryView(
                    settlement.Id,
                    "SETTLEMENT_LINE_SETTLED",
                    "SETTLEMENT_LINE",
                    line.Id,
                    line.UpdatedByUserId,
                    $"Disbursement {line.MurabahaDisbursementId}",
                    line.UpdatedAtUtc.Value));
            }
        }

        AddIfPresent(events, settlement.Id, "SETTLEMENT_SUBMITTED", "SETTLEMENT", settlement.Id, settlement.SubmittedByUserId, null, settlement.SubmittedAtUtc);
        AddIfPresent(events, settlement.Id, "SETTLEMENT_APPROVED", "SETTLEMENT", settlement.Id, settlement.ApprovedByUserId, null, settlement.ApprovedAtUtc);
        AddIfPresent(events, settlement.Id, "SETTLEMENT_CLOSED", "SETTLEMENT", settlement.Id, settlement.ClosedByUserId, null, settlement.ClosedAtUtc);

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

    public IslamicFinanceSettlementAuditView? GetSettlementAudit(long settlementHeaderId)
    {
        var settlement = GetSettlement(settlementHeaderId);
        if (settlement is null)
        {
            return null;
        }

        return new IslamicFinanceSettlementAuditView(
            settlement,
            GetSettlementHistory(settlementHeaderId));
    }

    private IslamicFinanceSettlementHeader GetTracked(long settlementHeaderId) =>
        _dbContext.Set<IslamicFinanceSettlementHeader>().Single(entry => entry.Id == settlementHeaderId);

    private long[] GetSettlementDisbursementIds(long settlementHeaderId) =>
        _dbContext.Set<IslamicFinanceSettlementLine>()
            .AsNoTracking()
            .Where(entry => entry.IslamicFinanceSettlementHeaderId == settlementHeaderId)
            .OrderBy(entry => entry.Id)
            .Select(entry => entry.MurabahaDisbursementId)
            .ToArray();

    private IslamicFinanceSettlementDetailView Map(IslamicFinanceSettlementHeader header)
    {
        var lines = _dbContext.Set<IslamicFinanceSettlementLine>()
            .AsNoTracking()
            .Where(entry => entry.IslamicFinanceSettlementHeaderId == header.Id)
            .OrderBy(entry => entry.Id)
            .ToArray();

        var disbursementsById = _dbContext.IslamicFinanceMurabahaDisbursements
            .AsNoTracking()
            .Where(entry => lines.Select(line => line.MurabahaDisbursementId).Contains(entry.Id))
            .ToArray()
            .ToDictionary(entry => entry.Id);

        return new IslamicFinanceSettlementDetailView(
            header.Id,
            header.SettlementNumber,
            header.MurabahaCaseId,
            header.FinancingRequestId,
            header.PartnerLenderId,
            header.FundingSourceMode,
            header.SettlementDate,
            header.SettlementCurrencyCode,
            header.TotalSettledAmount,
            header.StatusCode,
            header.WorkflowInstanceId,
            header.CreatedAtUtc,
            header.CreatedByUserId,
            header.SubmittedAtUtc,
            header.SubmittedByUserId,
            header.ApprovedAtUtc,
            header.ApprovedByUserId,
            header.ClosedAtUtc,
            header.ClosedByUserId,
            header.UpdatedAtUtc,
            header.UpdatedByUserId,
            lines.Select(entry => new IslamicFinanceSettlementLineView(
                entry.Id,
                entry.IslamicFinanceSettlementHeaderId,
                entry.MurabahaDisbursementId,
                disbursementsById[entry.MurabahaDisbursementId].DisbursementNumber,
                entry.SettledAmount,
                entry.StatusCode,
                entry.CreatedAtUtc,
                entry.CreatedByUserId,
                entry.UpdatedAtUtc,
                entry.UpdatedByUserId)).ToArray());
    }

    private static void ValidateCreateCommand(CreateIslamicFinanceSettlementCommand command)
    {
        if (string.IsNullOrWhiteSpace(command.SettlementNumber))
        {
            throw new InvalidOperationException("Islamic Finance settlement number is required.");
        }

        if (command.MurabahaCaseId <= 0)
        {
            throw new InvalidOperationException("Islamic Finance settlement requires a valid Murabaha case identifier.");
        }

        if (string.IsNullOrWhiteSpace(command.SettlementCurrencyCode))
        {
            throw new InvalidOperationException("Islamic Finance settlement currency code is required.");
        }

        if (command.Lines.Count == 0)
        {
            throw new InvalidOperationException("Islamic Finance settlement must contain at least one linked disbursement line.");
        }

        var duplicateDisbursementIds = command.Lines
            .GroupBy(entry => entry.MurabahaDisbursementId)
            .Where(group => group.Count() > 1)
            .Select(group => group.Key)
            .OrderBy(entry => entry)
            .ToArray();

        if (duplicateDisbursementIds.Length > 0)
        {
            throw new InvalidOperationException($"Islamic Finance settlement cannot contain duplicate Murabaha disbursements: {string.Join(", ", duplicateDisbursementIds)}.");
        }
    }

    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));
    }
}
