using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.SupportAlerts;
using Baya.Application.Features.Refunds.Commands.CreateRefund;
using Baya.Application.Features.Refunds.Commands.WriteOffClawback;
using Baya.Domain.Entities.Payments;
using Baya.Domain.Entities.Refunds;
using Microsoft.EntityFrameworkCore;
using NSubstitute;

namespace Baya.Test.Foundation.Refunds;

/// <summary>
/// The admin bad-debt path (previously untested — refinement-phase-6 §6.5): writing off a <c>pending</c> nurse
/// clawback posts a balanced <c>DEBIT bad_debt / CREDIT nurse_clawback_receivable</c> group and is guarded
/// (404 unknown / 409 non-pending / idempotent-ish once resolved).
/// </summary>
public class ClawbackWriteOffTests
{
    private static readonly DateTimeOffset Now = new(2026, 8, 10, 10, 0, 0, TimeSpan.Zero);

    private static async Task<(long ClawbackId, long BookingId)> SeedPendingClawbackAsync(RefundsTestHost host)
    {
        var (bookingId, _) = host.SeedCapturedBooking(gross: 10_000_000, commission: 1_500_000);
        var create = new CreateRefundCommandHandler(
            host.UnitOfWork, host.Lock(), host.Config(), host.Clock(Now), host.AsAdmin(),
            host.Card(), host.Bnpl(), host.PayoutStatus(paid: true),
            Substitute.For<ISupportAlertService>(), Substitute.For<INotificationDispatcher>(), host.Senders());

        var refund = await create.Handle(
            new CreateRefundCommand(bookingId, null, RefundPercentage: 1m, null, null, "customer_request", null, null, null),
            CancellationToken.None);
        Assert.True(refund.IsSuccess);
        Assert.NotNull(refund.Result.ClawbackId);
        return (refund.Result.ClawbackId!.Value, bookingId);
    }

    [Fact]
    public async Task Write_off_posts_a_balanced_bad_debt_group_and_resolves_the_clawback()
    {
        using var host = new RefundsTestHost();
        var (clawbackId, bookingId) = await SeedPendingClawbackAsync(host);

        var handler = new WriteOffClawbackCommandHandler(host.UnitOfWork, host.Clock(Now));
        var result = await handler.Handle(new WriteOffClawbackCommand(clawbackId, "uncollectable_after_dispute"), CancellationToken.None);

        Assert.True(result.IsSuccess);

        var clawback = host.Db.Set<NurseClawback>().AsNoTracking().Single(c => c.Id == clawbackId);
        Assert.Equal(ClawbackStatus.WrittenOff, clawback.Status);

        // The correction group: DEBIT bad_debt == CREDIT nurse_clawback_receivable, both for the clawback amount.
        var group = host.LedgerFor(bookingId).Where(l => l.SourceRefType == LedgerSourceRefType.Clawback).ToList();
        Assert.Equal(2, group.Count);
        Assert.Equal(8_500_000, group.Single(l => l.AccountType == LedgerAccountType.BadDebt && l.Direction == LedgerDirection.Debit).AmountIrr);
        Assert.Equal(8_500_000, group.Single(l => l.AccountType == LedgerAccountType.NurseClawbackReceivable && l.Direction == LedgerDirection.Credit).AmountIrr);
        Assert.Equal(group.Where(l => l.Direction == LedgerDirection.Debit).Sum(l => l.AmountIrr),
                     group.Where(l => l.Direction == LedgerDirection.Credit).Sum(l => l.AmountIrr));
    }

    [Fact]
    public async Task Write_off_of_an_unknown_clawback_is_not_found()
    {
        using var host = new RefundsTestHost();
        var handler = new WriteOffClawbackCommandHandler(host.UnitOfWork, host.Clock(Now));

        var result = await handler.Handle(new WriteOffClawbackCommand(999_999, "x"), CancellationToken.None);

        Assert.False(result.IsSuccess);
        Assert.True(result.IsNotFound);
    }

    [Fact]
    public async Task Second_write_off_of_the_same_clawback_is_a_conflict()
    {
        using var host = new RefundsTestHost();
        var (clawbackId, _) = await SeedPendingClawbackAsync(host);
        var handler = new WriteOffClawbackCommandHandler(host.UnitOfWork, host.Clock(Now));

        var first = await handler.Handle(new WriteOffClawbackCommand(clawbackId, "uncollectable"), CancellationToken.None);
        var second = await handler.Handle(new WriteOffClawbackCommand(clawbackId, "again"), CancellationToken.None);

        Assert.True(first.IsSuccess);
        Assert.False(second.IsSuccess);
        Assert.True(second.IsConflict);
    }
}
