import { BadRequestException, Injectable } from "@nestjs/common";
import { PrismaService } from "../../prisma/prisma.service";
import { BookingRequestStatus, BookingStatus, OfferState } from "@prisma/client";
import { computeMinDepositPercent } from "../../common/utils/risk";
import { percentOf, round2 } from "../../common/utils/money";
import { PushService } from "../push/push.service";

@Injectable()
export class OffersService {
  constructor(private prisma: PrismaService, private push: PushService) {}

  async listProviderOffers(providerUserId: string) {
    const provider = await this.prisma.provider.findFirst({ where: { userId: providerUserId } });
    if (!provider) throw new BadRequestException("Not a provider");

    return this.prisma.bookingOffer.findMany({
      where: { providerId: provider.id, state: OfferState.offered, expiresAt: { gt: new Date() } },
      include: { request: true },
      orderBy: { offeredAt: "desc" },
    });
  }

  async declineOffer(providerUserId: string, offerId: string, dto: { reason?: string }) {
    const provider = await this.prisma.provider.findFirst({ where: { userId: providerUserId } });
    if (!provider) throw new BadRequestException("Not a provider");

    const offer = await this.prisma.bookingOffer.findFirst({ where: { id: offerId, providerId: provider.id } });
    if (!offer) throw new BadRequestException("Offer not found");
    if (offer.state !== OfferState.offered) throw new BadRequestException("Offer not available");

    await this.prisma.bookingOffer.update({
      where: { id: offerId },
      data: { state: OfferState.declined, respondedAt: new Date(), declineReason: dto.reason ?? null },
    });

    return { ok: true };
  }

  async acceptOffer(providerUserId: string, offerId: string, dto: any) {
    const provider = await this.prisma.provider.findFirst({ where: { userId: providerUserId } });
    if (!provider) throw new BadRequestException("Not a provider");

    const depositWindowMin = Number(process.env.DEPOSIT_WINDOW_MINUTES ?? 15);
    const baseDepositPercent = Number(process.env.DEPOSIT_RATE_PERCENT ?? 20);
    const platformFeePercent = Number(process.env.PLATFORM_FEE_PERCENT ?? 10);

    const riskUpliftEnabled = String(process.env.RISK_UPLIFT_ENABLED ?? "true") === "true";
    const noShowThreshold = Number(process.env.RISK_UPLIFT_NO_SHOW_THRESHOLD ?? 2);
    const cancelThreshold = Number(process.env.RISK_UPLIFT_CANCEL_THRESHOLD ?? 3);
    const upliftMinPercent = Number(process.env.RISK_UPLIFT_MIN_DEPOSIT_PERCENT ?? 30);

    const now = new Date();
    const depositDeadlineAt = new Date(now.getTime() + depositWindowMin * 60 * 1000);

    return this.prisma.$transaction(async (tx) => {
      const offer = await tx.bookingOffer.findFirst({
        where: { id: offerId, providerId: provider.id },
      });
      if (!offer) throw new BadRequestException("Offer not found");
      if (offer.state !== OfferState.offered) throw new BadRequestException("Offer not available");
      if (offer.expiresAt <= now) throw new BadRequestException("Offer expired");

      const req = await tx.bookingRequest.findUnique({
        where: { id: offer.bookingRequestId },
      });
      if (!req) throw new BadRequestException("Request missing");
      if (req.status !== BookingRequestStatus.open) throw new BadRequestException("Request not open");

      await tx.bookingOffer.update({
        where: { id: offer.id },
        data: { state: OfferState.accepted, respondedAt: now },
      });

      await tx.bookingOffer.updateMany({
        where: { bookingRequestId: req.id, state: OfferState.offered, NOT: { id: offer.id } },
        data: { state: OfferState.cancelled },
      });

      const scheduledFor = req.isAsap
        ? new Date(now.getTime() + 45 * 60 * 1000)
        : (req.desiredTime ?? new Date(now.getTime() + 2 * 60 * 60 * 1000));

      let price = 0;
      if (req.serviceId) {
        const svc = await tx.providerService.findUnique({ where: { id: req.serviceId } });
        if (!svc) throw new BadRequestException("Service not found");
        price = Number(svc.priceAmount);
      } else {
        price = 300; // MVP category estimate fallback
      }

      const clientNoShows = await tx.booking.count({
        where: { clientId: req.clientId, status: BookingStatus.no_show },
      });
      const lateCancels = 0;

      const minDepositPercent = computeMinDepositPercent({
        basePercent: baseDepositPercent,
        riskUpliftEnabled,
        stats: { noShows: clientNoShows, lateCancels },
        noShowThreshold,
        cancelThreshold,
        upliftMinPercent,
      });

      const recommendedDeposit = round2(percentOf(price, baseDepositPercent));
      const minDeposit = round2(percentOf(price, minDepositPercent));
      const maxDeposit = round2(price);
      const platformFee = round2(percentOf(price, platformFeePercent));

      await tx.bookingRequest.update({
        where: { id: req.id },
        data: {
          status: BookingRequestStatus.awaiting_deposit,
          matchedProviderId: provider.id,
          matchedOfferId: offer.id,
          updatedAt: now,
        },
      });

      const endAt = new Date(scheduledFor.getTime() + 2 * 60 * 60 * 1000);
      await tx.bookingTimeLock.create({
        data: {
          providerId: provider.id,
          bookingRequestId: req.id,
          startAt: scheduledFor,
          endAt,
          expiresAt: depositDeadlineAt,
        },
      });

      const booking = await tx.booking.create({
        data: {
          bookingRequestId: req.id,
          clientId: req.clientId,
          providerId: provider.id,
          serviceId: req.serviceId,
          serviceMode: req.serviceMode,
          scheduledFor,
          locationLat: req.clientLat,
          locationLng: req.clientLng,
          addressText: req.addressText,
          status: BookingStatus.pending_deposit,
          priceAmount: price as any,
          currency: "BWP",
          depositRatePercent: baseDepositPercent as any,
          recommendedDepositAmount: recommendedDeposit as any,
          minDepositAmount: minDeposit as any,
          maxDepositAmount: maxDeposit as any,
          platformFeeAmount: platformFee as any,
          depositDeadlineAt,
        },
      });

      await tx.bookingStatusEvent.create({
        data: {
          bookingId: booking.id,
          status: "pending_deposit",
          actorUserId: providerUserId,
          meta: { offerId: offer.id, providerNote: dto?.providerNote ?? null },
        },
      });

      // Push client: deposit required
      await this.push.sendToUser(req.clientId, {
        title: "Provider accepted 🎉",
        body: "Pay deposit to confirm your booking.",
        data: { type: "DEPOSIT_REQUIRED", bookingId: booking.id },
      });

      return {
        bookingId: booking.id,
        status: booking.status,
        deposit: {
          recommended: recommendedDeposit,
          min: minDeposit,
          max: maxDeposit,
          deadlineAt: depositDeadlineAt,
        },
      };
    });
  }
}
