using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Application.Abstractions.Payment;
using Domain.Entities.Common.ValueObject;
using Domain.Entities.Payments.Danal;
using Domain.Entities.Payments.Danal.ValueObject;
using Microsoft.EntityFrameworkCore;

namespace Application.Features.Api.Payment.Confirm;

internal sealed class Handler(
    IAppDbContext db,
    IDanalPayService danalPay
) : IQueryHandler<Command, Response>
{
    public async Task<Response> Handle(Command r, CancellationToken ct)
    {
        // 주문 조회
        var order = await db.PaymentOrder.FirstOrDefaultAsync(o => o.OrderID == r.OrderID, ct);
        if (order is null)
        {
            return new Response(false, 0, "주문을 찾을 수 없습니다.", null);
        }

        // 이미 결제 완료된 주문인 경우 중복 승인 방지
        if (order.Status == Domain.Entities.Payments.ValueObject.PaymentStatus.Paid)
        {
            return new Response(true, order.PointAmount, "이미 충전이 완료된 주문입니다.", order.PaidAt);
        }

        // 회원 검증
        if (order.MemberID != r.MemberID)
        {
            order.MarkFailed("회원 불일치");
            await db.SaveChangesAsync(ct);
            return new Response(false, 0, "주문 정보가 일치하지 않습니다.", null);
        }

        // 승인 요청 기록 저장
        var confirm = DanalConfirm.CreateRequest(r.MemberID, order.ID, order.PaymentMethod, r.TransactionID, r.OrderID, order.Amount, order.MerchantID);

        await db.DanalConfirm.AddAsync(confirm, ct);
        await db.SaveChangesAsync(ct);

        try
        {
            // 다날 승인 API 호출
            var result = await danalPay.ConfirmAsync(r.Method, r.TransactionID, r.OrderID, order.Amount, ct);

            if (!result.Success)
            {
                confirm.SetResponse(result.Code ?? "", result.Message ?? "승인 실패");
                order.MarkFailed(result.Message ?? "승인 실패");

                await db.DanalLog.AddAsync(DanalLog.Create(r.MemberID, DanalLogType.Failed, result.Code ?? "FAIL", result.Message ?? "승인 실패", r.TransactionID, r.OrderID), ct);
                await db.SaveChangesAsync(ct);

                return new Response(false, 0, result.Message ?? "결제 승인에 실패했습니다.", null);
            }

            // 응답 저장
            confirm.SetResponse(
                result.Code ?? "", result.Message ?? "",
                result.OrderName, result.TotalAmount, result.DiscountAmount, result.UserName,
                result.TransDate, result.TransTime,
                result.CardCode, result.CardName, result.CardNo,
                result.InstallmentMonths, result.ApproveNo,
                result.ApprovalDateTime, result.AuthKey,
                result.AccountNumber, result.BankCode, result.UserId, result.UserEmail,
                result.BankName, result.ExpireDate, result.ExpireTime,
                result.VirtualAccountNumber, result.UseCashReceipt
            );

            // 주문 상태 갱신
            order.MarkPaid(r.TransactionID);

            // 지갑에 포인트 충전
            var wallet = await db.Wallet.Include(w => w.Balances).Include(w => w.Transactions).AsSplitQuery().FirstOrDefaultAsync(w => w.MemberID == order.MemberID, ct);
            if (wallet is null)
            {
                return new Response(false, 0, "지갑 정보를 찾을 수 없습니다.", null);
            }

            wallet.CreditPgCharge(Money.KRW(order.PointAmount), $"포인트 충전 ({order.PaymentMethod})", order.OrderID);

            await db.SaveChangesAsync(ct);

            return new Response(true, order.PointAmount, "충전이 완료되었습니다.", order.PaidAt);
        }
        catch (Exception ex)
        {
            await db.DanalLog.AddAsync(DanalLog.Create(r.MemberID, DanalLogType.Error, "EXCEPTION", ex.Message, r.TransactionID, r.OrderID), ct);
            await db.SaveChangesAsync(ct);

            return new Response(false, 0, "결제 처리 중 오류가 발생했습니다.", null);
        }
    }
}