using Application.Abstractions.Hub;
using Application.Abstractions.Notification;
using Domain.Entities.Notifications.ValueObject;
using Infrastructure.Hubs;
using Web.Api.Common;
using MediatR;
using Microsoft.AspNetCore.SignalR;

namespace Web.Api.Endpoints.Donation;

/// <summary>크루 방송 종료 — 정산 + 크루원에게 결과 쪽지/알림</summary>
internal sealed class CrewSessionEnd : IEndpoint
{
    public void MapEndpoint(IEndpointRouteBuilder app)
    {
        app.MapPost("api/crew/session/end", async (
            Application.Features.Api.Crew.EndSession.Command body,
            ISender sender,
            IHubContext<AppHub, IAppHubClient> appHub,
            INotificationService notificationService,
            Application.Abstractions.Data.IAppDbContext db,
            CancellationToken ct
        ) => {
            // 종료 전 세션 정보 미리 조회
            var session = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
                .FirstOrDefaultAsync(
                    db.CrewSession.Where(s => s.ID == body.CrewSessionID), ct
                );

            if (session is null)
            {
                return CustomResults.Problem(SharedKernel.Results.Result.Failure(SharedKernel.Results.Error.NotFound("Session.NotFound", "세션을 찾을 수 없습니다.")));
            }

            var crew = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
                .FirstOrDefaultAsync(
                    Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
                        .AsNoTracking(db.Crew)
                        .Where(c => c.ID == session.CrewID), ct
                );

            // 세션 종료 처리
            await sender.Send(body, ct);

            // 정산 결과 조회
            var summaries = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
                .ToListAsync(
                    Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
                        .AsNoTracking(db.CrewDonationSummary)
                        .Where(s => s.CrewSessionID == body.CrewSessionID)
                        .Join(db.CrewMember, s => s.CrewMemberID, m => m.ID, (s, m) => new {
                            m.MemberID, m.Nickname, s.TotalAmount, s.DonationCount,
                            s.ContributionRate, s.Rank
                        })
                        .OrderBy(x => x.Rank), ct
                );

            // 각 크루원에게 정산 알림 + 쪽지
            foreach (var summary in summaries)
            {
                var noteTitle = $"[{crew?.Name}] 크루 방송 정산 결과";
                var noteContent = $"방송: {session.Title}\n" +
                    $"순위: {summary.Rank}위\n" +
                    $"받은 후원: {summary.TotalAmount:N0}원 ({summary.DonationCount}건)\n" +
                    $"기여율: {summary.ContributionRate:F1}%\n" +
                    $"전체 후원: {session.TotalAmount:N0}원";

                // 시스템 쪽지 발송
                var note = Domain.Entities.Notes.Note.CreateSystem(
                    summary.MemberID, noteTitle, noteContent,
                    "CrewSession", body.CrewSessionID
                );
                db.Note.Add(note);

                // 알림 발송
                await notificationService.SendAsync(
                    summary.MemberID,
                    NotificationType.CrewEnded,
                    "크루 방송 종료",
                    $"'{session.Title}' 방송이 종료되었습니다. {summary.Rank}위, {summary.TotalAmount:N0}원",
                    null, "CrewSession", body.CrewSessionID, null, ct
                );
            }

            await db.SaveChangesAsync(ct);

            // SignalR 브로드캐스트
            if (crew is not null)
            {
                var channel = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
                    .FirstOrDefaultAsync(
                        Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
                            .AsNoTracking(db.Channel)
                            .Where(c => c.ID == crew.ChannelID), ct
                    );

                if (channel is not null)
                {
                    await appHub.Clients.Group($"channel:{channel.SID}").ReceiveCrewEnded(new
                    {
                        CrewSessionID = body.CrewSessionID,
                        Title = session.Title,
                        TotalAmount = session.TotalAmount,
                        Summaries = summaries.Select(s => new {
                            s.Nickname, s.Rank, s.TotalAmount, s.ContributionRate
                        })
                    });
                }
            }

            return ApiResponse.Ok();
        })
        .WithTags("Crew")
        .RequireAuthorization();
    }
}