using CashLineERP.Contracts.Finance;

namespace CashLineERP.Application.Finance;

public sealed class CustomerRefundService : ICustomerRefundService
{
    private readonly ICustomerRefundRepository _repository;
    private readonly IPaymentGatewayExecutionService _paymentGatewayExecutionService;

    public CustomerRefundService(ICustomerRefundRepository repository, IPaymentGatewayExecutionService paymentGatewayExecutionService)
    {
        _repository = repository;
        _paymentGatewayExecutionService = paymentGatewayExecutionService;
    }

    public IReadOnlyCollection<CustomerRefundResponse> GetRefunds(long? customerId = null) =>
        _repository.GetRefunds(customerId).Select(Map).ToArray();

    public CustomerRefundResponse? GetRefund(long customerRefundId)
    {
        var refund = _repository.GetRefund(customerRefundId);
        return refund is null ? null : Map(refund);
    }

    public CustomerRefundResponse CreateRefund(CreateCustomerRefundRequest request) =>
        Map(_repository.Create(new CreateCustomerRefundCommand(
            request.CustomerId,
            request.SourceAccountsReceivableReceiptId,
            request.RefundDate,
            request.RefundMethod,
            request.CurrencyCode,
            request.AmountAmount,
            request.PaymentChannelConfigurationId,
            request.ReferenceNumber,
            request.Notes)));

    public CustomerRefundResponse SubmitRefund(long customerRefundId, long actorUserId) =>
        Map(_repository.Submit(customerRefundId, actorUserId));

    public CustomerRefundResponse ApproveRefund(long customerRefundId, long actorUserId) =>
        Map(_repository.Approve(customerRefundId, actorUserId));

    public CustomerRefundResponse ReturnRefund(long customerRefundId, long actorUserId, ReviewCustomerRefundRequest request) =>
        Map(_repository.Return(customerRefundId, actorUserId, request.ReviewNotes));

    public CustomerRefundResponse RejectRefund(long customerRefundId, long actorUserId, ReviewCustomerRefundRequest request) =>
        Map(_repository.Reject(customerRefundId, actorUserId, request.ReviewNotes));

    public CustomerRefundResponse PostRefund(long customerRefundId, long actorUserId) =>
        Map(_repository.Post(customerRefundId, actorUserId));

    public CustomerRefundOrchestrationResponse GetRefundOrchestration(long customerRefundId)
    {
        var refund = GetRefund(customerRefundId)
            ?? throw new InvalidOperationException("Customer refund was not found.");

        var executions = _paymentGatewayExecutionService.GetRefundExecutions(customerRefundId);
        var latestExecution = executions
            .OrderByDescending(item => item.CreatedAtUtc)
            .ThenByDescending(item => item.PaymentGatewayExecutionId)
            .FirstOrDefault();
        var providerContract = refund.PaymentChannelConfigurationId.HasValue
            ? _paymentGatewayExecutionService.GetCustomerRefundProviderContract(customerRefundId)
            : null;

        return new CustomerRefundOrchestrationResponse(
            refund,
            executions.Count,
            executions.Count(item => item.StatusCode == "QUEUED" || item.StatusCode == "RETRY"),
            executions.Count(item => item.StatusCode == "DISPATCHED" || item.StatusCode == "ACKNOWLEDGED"),
            executions.Count(item => item.StatusCode == "SETTLED"),
            executions.Count(item => item.StatusCode == "FAILED"),
            latestExecution?.PaymentGatewayExecutionId,
            latestExecution?.StatusCode,
            latestExecution?.ProviderStatusCode,
            latestExecution?.ProviderReference,
            providerContract);
    }

    private static CustomerRefundResponse Map(CustomerRefundDetailView refund) =>
        new(
            refund.CustomerRefundId,
            refund.RefundNumber,
            refund.CustomerId,
            refund.SourceAccountsReceivableReceiptId,
            refund.RefundDate,
            refund.RefundMethod,
            refund.CurrencyCode,
            refund.AmountAmount,
            refund.PaymentChannelConfigurationId,
            refund.StatusCode,
            refund.ReferenceNumber,
            refund.Notes,
            refund.SubmittedAtUtc,
            refund.SubmittedByUserId,
            refund.ApprovedAtUtc,
            refund.ApprovedByUserId,
            refund.ReturnedAtUtc,
            refund.ReturnedByUserId,
            refund.ReturnNotes,
            refund.RejectedAtUtc,
            refund.RejectedByUserId,
            refund.RejectionNotes,
            refund.PostedAtUtc,
            refund.PostedByUserId,
            refund.GeneralLedgerBatchId);
}
