import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../../prisma/prisma.service';
import { BookingEventsService } from '../core/booking-events.service';
import { ClientMembershipsService } from './client-memberships.service';
import { CreatePaymentDto, RefundPaymentDto, PaymentCallbackDto } from '../dto/payment.dto';
import { Decimal } from '@prisma/client/runtime/library';

const PAYMENT_INCLUDE = {
  record: {
    select: {
      id: true,
      number: true,
      clientName: true,
      clientPhone: true,
      serviceId: true,
      membershipId: true,
    },
  },
};

@Injectable()
export class PaymentService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly events: BookingEventsService,
    private readonly clientMemberships: ClientMembershipsService,
  ) {}

  // ─── List payments for a record ────────────────────────

  async findByRecord(recordId: string) {
    return this.prisma.bookingPayment.findMany({
      where: { recordId },
      include: PAYMENT_INCLUDE,
      orderBy: { createdAt: 'desc' },
    });
  }

  // ─── List payments for a club ──────────────────────────

  async findAll(
    clubId: string,
    query: { cursor?: string; limit?: number; from?: string; to?: string },
  ) {
    const limit = Math.min(query.limit ?? 50, 100);

    const where: Record<string, any> = { clubId };
    if (query.from || query.to) {
      where.createdAt = {};
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      if (query.from) where.createdAt.gte = new Date(query.from);
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      if (query.to) where.createdAt.lte = new Date(query.to + 'T23:59:59');
    }

    const items = await this.prisma.bookingPayment.findMany({
      where,
      include: PAYMENT_INCLUDE,
      take: limit + 1,
      ...(query.cursor ? { cursor: { id: query.cursor }, skip: 1 } : {}),
      orderBy: { createdAt: 'desc' },
    });

    const hasMore = items.length > limit;
    if (hasMore) items.pop();

    return {
      items,
      nextCursor: hasMore ? items[items.length - 1]?.id : null,
      hasMore,
    };
  }

  // ─── Create payment ────────────────────────────────────

  async create(clubId: string, dto: CreatePaymentDto, userId?: string) {
    const record = await this.prisma.bookingRecord.findUniqueOrThrow({
      where: { id: dto.recordId },
    });

    if (record.clubId !== clubId) {
      throw new NotFoundException('Record not found');
    }

    // Check for overpayment
    const existingPayments = await this.prisma.bookingPayment.aggregate({
      where: { recordId: dto.recordId, status: { in: ['PENDING', 'COMPLETED'] } },
      _sum: { amount: true },
    });

    const paid = Number(existingPayments._sum.amount ?? 0);
    const requested = Number(dto.amount);
    const price = Number(record.price);

    if (paid + requested > price) {
      throw new BadRequestException(
        `Overpayment: record price is ${price}, already paid ${paid}, requested ${requested}`,
      );
    }

    const payment = await this.prisma.bookingPayment.create({
      data: {
        clubId,
        recordId: dto.recordId,
        amount: dto.amount,
        method: dto.method,
        status: dto.method === 'CASH' ? 'COMPLETED' : 'PENDING',
        externalId: dto.externalId,
      },
      include: PAYMENT_INCLUDE,
    });

    // CASH and MEMBERSHIP payments are completed immediately
    if (dto.method === 'CASH') {
      await this.onPaymentCompleted(payment, userId);
    }

    // MEMBERSHIP payment — deduct visit from membership
    if (dto.method === 'MEMBERSHIP' && record.membershipId) {
      await this.clientMemberships.deductVisit(record.membershipId);

      await this.prisma.bookingPayment.update({
        where: { id: payment.id },
        data: { status: 'COMPLETED' },
      });

      await this.onPaymentCompleted(payment, userId);
    }

    return payment;
  }

  // ─── Process payment callback (online payments) ────────

  async processCallback(dto: PaymentCallbackDto) {
    const payment = await this.prisma.bookingPayment.findFirst({
      where: { externalId: dto.externalId },
      include: PAYMENT_INCLUDE,
    });

    if (!payment) {
      throw new NotFoundException(`Payment with externalId ${dto.externalId} not found`);
    }

    if (payment.status !== 'PENDING') {
      throw new BadRequestException(`Payment is already ${payment.status}`);
    }

    const newStatus = dto.status === 'success' ? 'COMPLETED' : 'FAILED';

    const updated = await this.prisma.bookingPayment.update({
      where: { id: payment.id },
      data: {
        status: newStatus,
        metadata: dto.metadata ?? undefined,
      },
      include: PAYMENT_INCLUDE,
    });

    if (newStatus === 'COMPLETED') {
      await this.onPaymentCompleted(updated);
    }

    return updated;
  }

  // ─── Refund ────────────────────────────────────────────

  async refund(paymentId: string, dto: RefundPaymentDto, _userId?: string) {
    const payment = await this.prisma.bookingPayment.findUnique({
      where: { id: paymentId },
      include: PAYMENT_INCLUDE,
    });

    if (!payment) throw new NotFoundException(`Payment ${paymentId} not found`);
    if (payment.status !== 'COMPLETED') {
      throw new BadRequestException('Only completed payments can be refunded');
    }

    const refundAmount = dto.amount ? new Decimal(dto.amount) : payment.amount;

    if (refundAmount.gt(payment.amount)) {
      throw new BadRequestException('Refund amount exceeds payment amount');
    }

    const updated = await this.prisma.bookingPayment.update({
      where: { id: paymentId },
      data: {
        status: 'REFUNDED',
        metadata: {
          ...((payment.metadata as Record<string, any>) ?? {}),
          refundAmount: refundAmount.toString(),
          refundReason: dto.reason,
          refundedAt: new Date().toISOString(),
        },
      },
      include: PAYMENT_INCLUDE,
    });

    // Update record paidAmount
    await this.prisma.bookingRecord.update({
      where: { id: payment.recordId },
      data: { paidAmount: { decrement: refundAmount } },
    });

    return updated;
  }

  // ─── Internal: called when payment confirmed ───────────

  private async onPaymentCompleted(
    payment: {
      id: string;
      clubId: string;
      recordId: string;
      amount: any;
    },
    userId?: string,
  ) {
    // Update record paidAmount
    await this.prisma.bookingRecord.update({
      where: { id: payment.recordId },
      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
      data: { paidAmount: { increment: new Decimal(payment.amount) } },
    });

    await this.events.emit('booking.payment.completed', {
      clubId: payment.clubId,
      data: { payment },
      userId,
    });
  }
}
