import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "../../common/prisma/prisma.service";
import { defaultCurrency, validMediaUrls } from "../../common/service-fields";

type RequestBody = {
  serviceId?: unknown;
  title?: unknown;
  description?: unknown;
  budgetMin?: unknown;
  budgetMax?: unknown;
  location?: unknown;
  mediaUrls?: unknown;
  scheduledAt?: unknown;
};

@Injectable()
export class ServiceRequestsService {
  constructor(private readonly prisma: PrismaService) {}

  private text(value: unknown, field: string, min: number, max: number, optional = false) {
    if (optional && (value === undefined || value === null || value === "")) return null;
    if (typeof value !== "string") throw new BadRequestException(`invalid_${field}`);
    const result = value.trim();
    if (result.length < min || result.length > max) throw new BadRequestException(`invalid_${field}`);
    return result;
  }

  private money(value: unknown, field: string) {
    if (value === undefined || value === null || value === "") return null;
    const number = Number(value);
    if (!Number.isFinite(number) || number < 0 || number > 1_000_000_000) throw new BadRequestException(`invalid_${field}`);
    return number;
  }

  async create(customerId: string, body: RequestBody) {
    const serviceId = this.text(body.serviceId, "service", 1, 100)!;
    const title = this.text(body.title, "title", 5, 200)!;
    const description = this.text(body.description, "description", 10, 10_000)!;
    const location = this.text(body.location, "location", 2, 250, true);
    const budgetMin = this.money(body.budgetMin, "budget_min");
    const budgetMax = this.money(body.budgetMax, "budget_max");
    if (budgetMin !== null && budgetMax !== null && budgetMin > budgetMax) throw new BadRequestException("invalid_budget_range");
    const scheduledAt = body.scheduledAt ? new Date(String(body.scheduledAt)) : null;
    if (scheduledAt && (Number.isNaN(scheduledAt.getTime()) || scheduledAt.getTime() < Date.now() - 60_000)) throw new BadRequestException("invalid_schedule");
    const mediaUrls = validMediaUrls(body.mediaUrls, 6);
    const service = await this.prisma.service.findFirst({ where: { id: serviceId, isActive: true }, select: { id: true } });
    if (!service) throw new NotFoundException("service_not_found");
    const currency = await defaultCurrency(this.prisma);

    return this.prisma.$transaction(async tx => {
      const request = await tx.serviceRequest.create({
        data: {
          customerId,
          serviceId,
          title,
          description,
          budgetMin,
          budgetMax,
          currency,
          location,
          mediaUrls,
          scheduledAt,
          expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
          status: "PUBLISHED",
        },
        include: { service: { include: { category: true } }, _count: { select: { offers: true } } },
      });

      const matchingUsers = await tx.user.findMany({
        where: {
          id: { not: customerId },
          isActive: true,
          status: "ACTIVE",
          serviceAds: { some: { serviceId, status: "PUBLISHED" } },
        },
        select: { id: true },
        take: 100,
      });
      if (matchingUsers.length) {
        await tx.notification.createMany({
          data: matchingUsers.map(user => ({
            userId: user.id,
            type: "NEED_MATCH",
            title: "فرصة جديدة تناسب خدماتك",
            body: title,
            data: { requestId: request.id },
          })),
        });
      }
      return request;
    });
  }

  mine(customerId: string) {
    return this.prisma.serviceRequest.findMany({
      where: { customerId, isPrivate: false },
      include: {
        service: { include: { category: true } },
        offers: {
          include: { provider: { select: { id: true, fullName: true, avatarUrl: true, providerProfile: true } } },
          orderBy: { createdAt: "desc" },
        },
        _count: { select: { offers: true } },
      },
      orderBy: { createdAt: "desc" },
    });
  }

  opportunities(actorId: string, filters: { serviceId?: string; location?: string }) {
    const location = filters.location?.trim();
    return this.prisma.serviceRequest.findMany({
      where: {
        customerId: { not: actorId },
        isPrivate: false,
        status: "PUBLISHED",
        OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }],
        ...(filters.serviceId ? { serviceId: filters.serviceId } : {}),
        ...(location ? { location: { contains: location, mode: "insensitive" } } : {}),
      },
      include: {
        customer: { select: { id: true, fullName: true, avatarUrl: true } },
        service: { include: { category: true } },
        offers: { where: { providerId: actorId }, select: { id: true, status: true } },
        _count: { select: { offers: true } },
      },
      orderBy: { createdAt: "desc" },
      take: 50,
    });
  }

  async detail(id: string, actorId: string) {
    const request = await this.prisma.serviceRequest.findUnique({
      where: { id },
      include: {
        customer: { select: { id: true, fullName: true, avatarUrl: true } },
        service: { include: { category: true } },
        offers: {
          include: { provider: { select: { id: true, fullName: true, avatarUrl: true, providerProfile: true } } },
          orderBy: { createdAt: "desc" },
        },
        stages: { orderBy: { sortOrder: "asc" } },
        _count: { select: { offers: true } },
      },
    });
    if (!request) throw new NotFoundException("request_not_found");
    const owner = request.customerId === actorId;
    const actorOffer = request.offers.find(offer => offer.providerId === actorId);
    const visible = owner || Boolean(actorOffer) || (!request.isPrivate && request.status === "PUBLISHED");
    if (!visible) throw new ForbiddenException("request_access_denied");
    return { ...request, offers: owner ? request.offers : actorOffer ? [actorOffer] : [] };
  }

  async cancel(id: string, customerId: string) {
    const request = await this.prisma.serviceRequest.findUnique({ where: { id }, include: { offers: { where: { status: { in: ["PENDING", "ACCEPTED"] } }, select: { providerId: true } } } });
    if (!request) throw new NotFoundException("request_not_found");
    if (request.customerId !== customerId) throw new ForbiddenException("request_owner_only");
    if (!["PUBLISHED", "ASSIGNED"].includes(request.status)) throw new BadRequestException("invalid_status_transition");
    return this.prisma.$transaction(async tx => {
      const updated = await tx.serviceRequest.update({ where: { id }, data: { status: "CANCELLED" } });
      if (request.offers.length) {
        await tx.notification.createMany({ data: request.offers.map(offer => ({ userId: offer.providerId, type: "NEED_CANCELLED", title: "تم إلغاء الاحتياج", body: request.title, data: { requestId: id } })) });
      }
      return updated;
    });
  }
}
