import { BadRequestException, ForbiddenException, Injectable, NotFoundException, ServiceUnavailableException } from "@nestjs/common";
import { MessageState } from "../../../prisma/generated/client";
import { PrismaService } from "../../common/prisma/prisma.service";
import { ConversationsGateway } from "./conversations.gateway";

function asciiDigits(value: unknown) {
  return String(value ?? "")
    .replace(/[٠-٩]/g, digit => String("٠١٢٣٤٥٦٧٨٩".indexOf(digit)))
    .replace(/[۰-۹]/g, digit => String("۰۱۲۳۴۵۶۷۸۹".indexOf(digit)));
}

function appointmentDate(value: unknown) {
  const match = /^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})$/.exec(asciiDigits(value).trim());
  if (!match) return null;
  const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
  const parsed = new Date(Date.UTC(year, month - 1, day));
  if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) return null;
  return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}

function appointmentTime(value: unknown) {
  const normalized = asciiDigits(value).trim().toLowerCase().replace(/\s+/g, "");
  const match = /^(\d{1,2}):(\d{2})(?::\d{2})?(am|pm|ص|م)?$/.exec(normalized);
  if (!match) return null;
  let hour = Number(match[1]);
  const minute = Number(match[2]);
  const period = match[3];
  if (minute > 59 || hour > (period ? 12 : 23) || hour < (period ? 1 : 0)) return null;
  if ((period === "pm" || period === "م") && hour < 12) hour += 12;
  if ((period === "am" || period === "ص") && hour === 12) hour = 0;
  return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
}

@Injectable()
export class ConversationsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly gateway: ConversationsGateway,
  ) {}

  private async assertChatEnabled() {
    const setting = await this.prisma.appSetting.findUnique({ where: { key: "chatEnabled" }, select: { value: true } });
    if (setting?.value === false) throw new ServiceUnavailableException("chat_disabled");
  }

  list(userId: string) {
    return this.prisma.conversation.findMany({
      where: {
        OR: [{ customerId: userId }, { providerId: userId }],
      },
      include: {
        customer: { select: { id: true, fullName: true, avatarUrl: true } },
        provider: { select: { id: true, fullName: true, avatarUrl: true } },
        messages: {
          orderBy: { createdAt: "desc" },
          take: 1,
        },
      },
      orderBy: { updatedAt: "desc" },
    });
  }

  async open(userId: string, peerId: string, requestId?: string, adId?: string) {
    await this.assertChatEnabled();
    if (userId === peerId) throw new BadRequestException("invalid_peer");
    const peer = await this.prisma.user.findUnique({ where: { id: peerId }, select: { id: true, isActive: true, status: true } });
    if (!peer?.isActive || ["SUSPENDED", "BANNED", "DELETED"].includes(peer.status)) throw new NotFoundException("peer_not_found");
    let customerId = userId;
    let providerId = peerId;
    if (adId) {
      const ad = await this.prisma.serviceAd.findFirst({ where: { id: adId, status: "PUBLISHED" }, select: { providerId: true } });
      if (!ad || ad.providerId !== peerId) throw new NotFoundException("ad_not_found");
      providerId = ad.providerId;
      customerId = providerId === userId ? peerId : userId;
    } else if (requestId) {
      const request = await this.prisma.serviceRequest.findUnique({ where: { id: requestId }, select: { customerId: true } });
      if (!request || ![userId, peerId].includes(request.customerId)) throw new ForbiddenException("request_owner_mismatch");
      customerId = request.customerId;
      providerId = customerId === userId ? peerId : userId;
    }
    const existing = await this.prisma.conversation.findFirst({
      where: {
        customerId,
        providerId,
        ...(adId ? { adId } : requestId ? { requestId } : { requestId: null, adId: null }),
      },
    });
    if (existing) return existing;
    return this.prisma.conversation.create({
      data: {
        customerId,
        providerId,
        requestId: requestId ?? null,
        adId: adId ?? null,
      },
    });
  }

  private async assertMember(conversationId: string, userId: string) {
    const conversation = await this.prisma.conversation.findUnique({ where: { id: conversationId } });
    if (!conversation) throw new NotFoundException("conversation_not_found");
    if (conversation.customerId !== userId && conversation.providerId !== userId) throw new ForbiddenException("conversation_member_only");
    return conversation;
  }

  async detail(id: string, userId: string) {
    await this.assertMember(id, userId);
    return this.prisma.conversation.findUnique({ where: { id }, include: { customer: { select: { id: true, fullName: true, avatarUrl: true } }, provider: { select: { id: true, fullName: true, avatarUrl: true } }, ad: true, request: { include: { offers: true } } } });
  }

  async sendMessage(userId: string, conversationId: string, body: { content?: string; type?: string; metadata?: Record<string, unknown> }) {
    await this.assertChatEnabled();
    await this.assertMember(conversationId, userId);
    const type = body.type ?? "TEXT";
    if (!["TEXT", "APPOINTMENT", "INQUIRY"].includes(type)) throw new BadRequestException("unsupported_message_type");
    const content = body.content?.trim() || (type === "APPOINTMENT" ? "اقتراح موعد" : "");
    if (!content) throw new BadRequestException("message_required");
    if (content.length > 5000) throw new BadRequestException("message_too_long");
    if (type === "APPOINTMENT" && (!body.metadata?.date || !body.metadata?.time)) throw new BadRequestException("appointment_date_time_required");
    const normalizedDate = type === "APPOINTMENT" ? appointmentDate(body.metadata?.date) : null;
    const normalizedTime = type === "APPOINTMENT" ? appointmentTime(body.metadata?.time) : null;
    const normalizedEndTime = type === "APPOINTMENT" && body.metadata?.endTime ? appointmentTime(body.metadata.endTime) : null;
    if (type === "APPOINTMENT" && (!normalizedDate || !normalizedTime)) throw new BadRequestException("invalid_appointment");
    if (type === "APPOINTMENT" && body.metadata?.endTime && (!normalizedEndTime || normalizedEndTime <= normalizedTime!)) throw new BadRequestException("invalid_appointment_range");

    const message = await this.prisma.message.create({
      data: {
        conversationId,
        senderId: userId,
        content,
        type,
        metadata: type === "APPOINTMENT" ? { date: normalizedDate!, time: normalizedTime!, endTime: normalizedEndTime ?? "", status: "PENDING" } : undefined,
        state: MessageState.SENT,
      },
      include: {
        sender: { select: { id: true, fullName: true, avatarUrl: true } },
      },
    });

    await this.prisma.conversation.update({
      where: { id: conversationId },
      data: { updatedAt: new Date() },
    });

    // Broadcast to all connected clients in the room
    this.gateway.broadcastMessage(conversationId, message);

    return message;
  }

  async messages(conversationId: string, userId: string) {
    await this.assertMember(conversationId, userId);
    const messages = await this.prisma.message.findMany({
      where: { conversationId },
      orderBy: { createdAt: "desc" },
      take: 500,
      include: {
        sender: { select: { id: true, fullName: true, avatarUrl: true } },
      },
    });
    const ids = messages.filter(m => m.type === "OFFER").map(m => (m.metadata as { offerId?: string })?.offerId).filter(Boolean) as string[];
    const offers = await this.prisma.offer.findMany({ where: { id: { in: ids } } });
    return messages.reverse().map(message => ({ ...message, ...(message.type === "OFFER" ? { offer: offers.find(o => o.id === (message.metadata as { offerId?: string })?.offerId) } : {}) }));
  }

  async respondAppointment(conversationId: string, messageId: string, userId: string, accepted: boolean) {
    await this.assertChatEnabled();
    await this.assertMember(conversationId, userId);
    const original = await this.prisma.message.findFirst({ where: { id: messageId, conversationId, type: "APPOINTMENT" } });
    if (!original) throw new NotFoundException("appointment_not_found");
    if (original.senderId === userId) throw new BadRequestException("appointment_sender_cannot_respond");
    if (typeof accepted !== "boolean") throw new BadRequestException("invalid_response");
    const metadata = original.metadata as Record<string, string>;
    const result = await this.prisma.message.updateMany({ where: { id: messageId, metadata: { path: ["status"], equals: "PENDING" } }, data: { metadata: { ...metadata, status: accepted ? "ACCEPTED" : "REJECTED" } } });
    if (!result.count) throw new BadRequestException("appointment_already_answered");
    return this.sendMessage(userId, conversationId, { content: accepted ? "تم قبول الموعد" : "تم رفض الموعد", type: "TEXT" });
  }
}
