using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Domain.Entities.Common.ValueObject;
using Domain.Entities.Donations;
using Domain.Entities.Donations.ValueObject;
using Microsoft.EntityFrameworkCore;

namespace Application.Features.Api.Donation.Send;

internal sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Response>
{
    public async Task<Response> Handle(Command request, CancellationToken ct)
    {
        // 0. ChannelSID → ChannelID 변환 (ChannelSID가 제공된 경우)
        if (request.ChannelID == 0 && !string.IsNullOrEmpty(request.ChannelSID))
        {
            var resolved = await db.Channel.AsNoTracking()
                .Where(c => c.SID == request.ChannelSID)
                .Select(c => c.ID)
                .FirstOrDefaultAsync(ct);

            if (resolved == 0) throw new KeyNotFoundException("채널을 찾을 수 없습니다.");
            request = request with { ChannelID = resolved };
        }

        // 1. 채널 조회 + 수수료율
        var channel = await db.Channel
            .AsNoTracking()
            .Where(c => c.ID == request.ChannelID && c.IsActive)
            .Select(c => new { c.ID, c.MemberID, c.PlatformFeeRate })
            .FirstOrDefaultAsync(ct);

        if (channel is null)
        {
            throw new KeyNotFoundException("채널을 찾을 수 없습니다.");
        }

        // 2. 자기 자신에게 후원 불가
        if (request.SponsorMemberID == channel.MemberID && !request.IsTest)
        {
            throw new InvalidOperationException("본인 채널에 후원할 수 없습니다.");
        }

        // 3. 후원 수락 상태 확인
        var meta = await db.DonationMeta
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.ChannelID == request.ChannelID, ct);

        if (meta is not null && !meta.IsAccepting)
        {
            throw new InvalidOperationException("현재 후원을 받지 않는 채널입니다.");
        }

        var minAmount = meta?.MinAmount ?? DonationConstants.MinAmount;
        if (request.Amount < minAmount)
        {
            throw new InvalidOperationException($"최소 후원 금액은 {minAmount}원입니다.");
        }

        if (request.Amount > DonationConstants.MaxAmount)
        {
            throw new InvalidOperationException($"최대 후원 금액은 {DonationConstants.MaxAmount:N0}원입니다.");
        }

        // 4. 크루 세션 검증 (크루 후원인 경우)
        if (request.CrewSessionID.HasValue)
        {
            var session = await db.CrewSession
                .AsNoTracking()
                .FirstOrDefaultAsync(s => s.ID == request.CrewSessionID.Value && s.Status == CrewSessionStatus.Active, ct);

            if (session is null)
            {
                throw new InvalidOperationException("활성화된 크루 후원 방송이 아닙니다.");
            }
        }

        // 5. 후원자 지갑 조회 + 잔액 차감
        var sponsorWallet = await db.Wallet
            .Include(w => w.Balances)
            .FirstOrDefaultAsync(w => w.MemberID == request.SponsorMemberID, ct);

        if (sponsorWallet is null)
        {
            throw new KeyNotFoundException("지갑을 찾을 수 없습니다.");
        }

        var spendAmount = Money.KRW(request.Amount);
        var totalAvailable = sponsorWallet.GetTotalAvailable();

        if (totalAvailable.Value < spendAmount.Value)
        {
            throw new InvalidOperationException("잔액이 부족합니다.");
        }

        // 6. Donation 생성 (수수료 스냅샷)
        var donation = Domain.Entities.Donations.Donation.Create(
            sponsorMemberID: request.SponsorMemberID,
            receiverMemberID: channel.MemberID,
            channelID: channel.ID,
            amount: request.Amount,
            feeRate: channel.PlatformFeeRate,
            message: request.Message,
            sendName: request.SendName,
            crewSessionID: request.CrewSessionID,
            crewMemberID: request.CrewMemberID,
            isTest: request.IsTest
        );

        db.Donation.Add(donation);

        // 7. 후원자 지갑 차감
        if (!request.IsTest)
        {
            sponsorWallet.Spend(spendAmount, "DONATION_OUT", donation.ID.ToString());
        }

        // 8. 수신자 지갑에 입금 (NetAmount)
        if (!request.IsTest)
        {
            var receiverWallet = await db.Wallet
                .Include(w => w.Balances)
                .FirstOrDefaultAsync(w => w.MemberID == channel.MemberID, ct);

            if (receiverWallet is not null)
            {
                receiverWallet.CreditDonationIn(Money.KRW(donation.NetAmount), "DONATION_IN", donation.ID.ToString());
            }
        }

        // SaveChanges로 Donation ID 확보
        await db.SaveChangesAsync(ct);

        // 9. DonationAlert 생성
        var alert = Domain.Entities.Donations.DonationAlert.Create(
            donationID: donation.ID,
            sponsorMemberID: request.SponsorMemberID,
            receiverMemberID: channel.MemberID
        );

        db.DonationAlert.Add(alert);

        // 10. 크루 후원인 경우 집계 갱신
        if (request.CrewSessionID.HasValue && request.CrewMemberID.HasValue)
        {
            var summary = await db.CrewDonationSummary
                .FirstOrDefaultAsync(s => s.CrewSessionID == request.CrewSessionID.Value
                                       && s.CrewMemberID == request.CrewMemberID.Value, ct);

            if (summary is null)
            {
                summary = CrewDonationSummary.Create(request.CrewSessionID.Value, request.CrewMemberID.Value);
                db.CrewDonationSummary.Add(summary);
            }

            summary.AddDonation(request.Amount);

            // 세션 전체 합계 갱신
            var session = await db.CrewSession.FindAsync([request.CrewSessionID.Value], ct);
            session?.AddDonation(request.Amount);

            // 기여도 재계산
            if (session is not null && session.TotalAmount > 0)
            {
                var allSummaries = await db.CrewDonationSummary
                    .Where(s => s.CrewSessionID == session.ID)
                    .OrderByDescending(s => s.TotalAmount)
                    .ToListAsync(ct);

                var rank = 1;
                foreach (var s in allSummaries)
                {
                    var rate = (decimal)s.TotalAmount / session.TotalAmount * 100;
                    s.UpdateContribution(rate, rank++);
                }
            }
        }

        await db.SaveChangesAsync(ct);

        // SignalR Push는 Endpoint 레이어 또는 별도 서비스에서 처리
        return new Response(donation.ID, alert.CorrelationID, donation.Amount, donation.FeeAmount, donation.NetAmount);
    }
}