import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import * as crypto from 'crypto';
import {
  AnnouncementAudience,
  ParkingReportStatus,
  VisitorPassStatus,
} from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { AuthenticatedUser } from '../../common/decorators/current-user.decorator';

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

  private async assertComplexAccess(user: AuthenticatedUser, complexId: string) {
    if (user.isSuperAdmin) return;
    const c = await this.prisma.complex.findFirst({
      where: { id: complexId, tenantId: user.tenantId ?? undefined },
    });
    if (!c) throw new ForbiddenException();
  }

  // ========== VISITOR PASSES ==========

  async createVisitorPass(
    user: AuthenticatedUser,
    dto: {
      complexId: string;
      visitorName: string;
      visitorPhone?: string;
      purpose?: string;
      validFrom?: Date;
      validUntil: Date;
    },
  ) {
    // Resident must be resident of this complex OR staff
    const isStaff = user.roles.some((r) =>
      ['SUPERADMIN', 'SYNDIC', 'GARDIEN'].includes(r.code),
    );
    if (!isStaff) {
      const isResident = await this.prisma.resident.findFirst({
        where: { userId: user.userId, lot: { complexId: dto.complexId }, endDate: null },
      });
      if (!isResident) throw new ForbiddenException('Not a resident');
    } else {
      await this.assertComplexAccess(user, dto.complexId);
    }
    if (dto.validUntil <= new Date()) {
      throw new BadRequestException('validUntil must be in the future');
    }
    const qrToken = crypto.randomBytes(20).toString('base64url');
    return this.prisma.visitorPass.create({
      data: {
        residentId: user.userId,
        complexId: dto.complexId,
        visitorName: dto.visitorName,
        visitorPhone: dto.visitorPhone,
        purpose: dto.purpose,
        qrToken,
        validFrom: dto.validFrom ?? new Date(),
        validUntil: dto.validUntil,
      },
    });
  }

  async listVisitorPasses(user: AuthenticatedUser, complexId?: string) {
    return this.prisma.visitorPass.findMany({
      where: {
        ...(complexId && { complexId }),
        ...(!user.roles.some((r) => ['SUPERADMIN', 'SYNDIC', 'GARDIEN'].includes(r.code)) && {
          residentId: user.userId,
        }),
      },
      orderBy: { createdAt: 'desc' },
      take: 200,
    });
  }

  async scanVisitorPass(qrToken: string) {
    const pass = await this.prisma.visitorPass.findUnique({ where: { qrToken } });
    if (!pass) return { granted: false, reason: 'NOT_FOUND' };
    const now = new Date();
    if (pass.status === VisitorPassStatus.REVOKED) {
      return { granted: false, reason: 'REVOKED' };
    }
    if (pass.status === VisitorPassStatus.USED) {
      return { granted: false, reason: 'ALREADY_USED' };
    }
    if (pass.status === VisitorPassStatus.EXPIRED) {
      return { granted: false, reason: 'EXPIRED' };
    }
    if (pass.validUntil < now) {
      await this.prisma.visitorPass.update({
        where: { id: pass.id },
        data: { status: VisitorPassStatus.EXPIRED },
      });
      return { granted: false, reason: 'EXPIRED' };
    }
    if (pass.validFrom > now) {
      return { granted: false, reason: 'NOT_YET_VALID' };
    }
    await this.prisma.visitorPass.update({
      where: { id: pass.id },
      data: { status: VisitorPassStatus.USED, usedAt: now },
    });
    return {
      granted: true,
      pass: {
        id: pass.id,
        visitorName: pass.visitorName,
        purpose: pass.purpose,
        residentId: pass.residentId,
      },
    };
  }

  async revokeVisitorPass(user: AuthenticatedUser, id: string) {
    const pass = await this.prisma.visitorPass.findUnique({ where: { id } });
    if (!pass) throw new NotFoundException();
    if (pass.residentId !== user.userId && !user.isSuperAdmin) {
      const isStaff = user.roles.some((r) => ['SYNDIC', 'GARDIEN'].includes(r.code));
      if (!isStaff) throw new ForbiddenException();
    }
    return this.prisma.visitorPass.update({
      where: { id },
      data: { status: VisitorPassStatus.REVOKED },
    });
  }

  // ========== PARKING REPORTS ==========

  async createParkingReport(
    user: AuthenticatedUser,
    dto: { complexId: string; plateNumber: string; spot?: string; photoUrl?: string; description?: string },
  ) {
    return this.prisma.parkingReport.create({
      data: {
        complexId: dto.complexId,
        reporterId: user.userId,
        plateNumber: dto.plateNumber.toUpperCase(),
        spot: dto.spot,
        photoUrl: dto.photoUrl,
        description: dto.description,
      },
    });
  }

  async listParkingReports(user: AuthenticatedUser, complexId?: string) {
    await (complexId ? this.assertComplexAccess(user, complexId) : Promise.resolve());
    return this.prisma.parkingReport.findMany({
      where: {
        ...(complexId && { complexId }),
        ...(!user.isSuperAdmin &&
          user.tenantId && { complex: { tenantId: user.tenantId } }),
      },
      include: {
        reporter: { select: { id: true, firstName: true, lastName: true } },
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  /**
   * Returns lot/owner candidates for a given plate — useful to identify owner of a badly-parked car.
   */
  async resolvePlate(user: AuthenticatedUser, plateNumber: string) {
    const plate = plateNumber.toUpperCase();
    let complexIds: string[] | undefined;
    if (!user.isSuperAdmin && user.tenantId) {
      const complexes = await this.prisma.complex.findMany({
        where: { tenantId: user.tenantId },
        select: { id: true },
      });
      complexIds = complexes.map((c) => c.id);
    }
    const vehicles = await this.prisma.vehicle.findMany({
      where: {
        plateNumber: plate,
        ...(complexIds ? { lot: { complexId: { in: complexIds } } } : {}),
      },
      include: {
        lot: true,
        resident: { include: { user: { select: { email: true, firstName: true, lastName: true, phone: true } } } },
      },
    });
    return { plate, candidates: vehicles };
  }

  async updateParkingStatus(user: AuthenticatedUser, id: string, status: ParkingReportStatus) {
    const r = await this.prisma.parkingReport.findUnique({ where: { id } });
    if (!r) throw new NotFoundException();
    await this.assertComplexAccess(user, r.complexId);
    return this.prisma.parkingReport.update({
      where: { id },
      data: { status, resolvedAt: status === ParkingReportStatus.RESOLVED ? new Date() : null },
    });
  }

  // ========== ANNOUNCEMENTS ==========

  async createAnnouncement(
    user: AuthenticatedUser,
    dto: {
      complexId: string;
      title: string;
      body: string;
      audience?: AnnouncementAudience;
      expiresAt?: Date;
      pinned?: boolean;
    },
  ) {
    await this.assertComplexAccess(user, dto.complexId);
    return this.prisma.announcement.create({
      data: {
        complexId: dto.complexId,
        authorId: user.userId,
        title: dto.title,
        body: dto.body,
        audience: dto.audience ?? AnnouncementAudience.ALL,
        expiresAt: dto.expiresAt,
        pinned: dto.pinned ?? false,
      },
    });
  }

  async listAnnouncements(user: AuthenticatedUser, complexId: string) {
    const now = new Date();
    const audienceFilter = user.roles.some((r) => r.code === 'LOCATAIRE')
      ? { audience: { in: [AnnouncementAudience.ALL, AnnouncementAudience.TENANTS] } }
      : user.roles.some((r) => r.code === 'COPROPRIETAIRE')
      ? { audience: { in: [AnnouncementAudience.ALL, AnnouncementAudience.OWNERS] } }
      : {};
    return this.prisma.announcement.findMany({
      where: {
        complexId,
        OR: [{ expiresAt: null }, { expiresAt: { gte: now } }],
        ...audienceFilter,
      },
      orderBy: [{ pinned: 'desc' }, { publishedAt: 'desc' }],
      include: { author: { select: { id: true, firstName: true, lastName: true } } },
    });
  }

  async deleteAnnouncement(user: AuthenticatedUser, id: string) {
    const a = await this.prisma.announcement.findUnique({ where: { id } });
    if (!a) throw new NotFoundException();
    await this.assertComplexAccess(user, a.complexId);
    await this.prisma.announcement.delete({ where: { id } });
    return { ok: true };
  }

  // ========== MESSAGING ==========

  async createThread(
    user: AuthenticatedUser,
    dto: { complexId: string; subject: string; participantIds: string[] },
  ) {
    await this.assertComplexAccess(user, dto.complexId);
    const participants = Array.from(new Set([...dto.participantIds, user.userId]));
    return this.prisma.messageThread.create({
      data: {
        complexId: dto.complexId,
        subject: dto.subject,
        participants: { create: participants.map((userId) => ({ userId })) },
      },
      include: { participants: true },
    });
  }

  async postMessage(user: AuthenticatedUser, threadId: string, body: string) {
    const thread = await this.prisma.messageThread.findUnique({
      where: { id: threadId },
      include: { participants: true },
    });
    if (!thread) throw new NotFoundException();
    const isParticipant = thread.participants.some((p) => p.userId === user.userId);
    if (!isParticipant && !user.isSuperAdmin) throw new ForbiddenException();
    await this.prisma.messageThread.update({
      where: { id: threadId },
      data: { updatedAt: new Date() },
    });
    return this.prisma.message.create({
      data: { threadId, senderId: user.userId, body },
    });
  }

  async listThreads(user: AuthenticatedUser, complexId?: string) {
    return this.prisma.messageThread.findMany({
      where: {
        ...(complexId && { complexId }),
        ...(!user.isSuperAdmin && { participants: { some: { userId: user.userId } } }),
      },
      orderBy: { updatedAt: 'desc' },
      include: {
        participants: { include: { user: { select: { id: true, firstName: true, lastName: true, email: true } } } },
        messages: { orderBy: { sentAt: 'desc' }, take: 1 },
      },
    });
  }

  async listMessages(user: AuthenticatedUser, threadId: string) {
    const thread = await this.prisma.messageThread.findUnique({
      where: { id: threadId },
      include: { participants: true },
    });
    if (!thread) throw new NotFoundException();
    const isParticipant = thread.participants.some((p) => p.userId === user.userId);
    if (!isParticipant && !user.isSuperAdmin) throw new ForbiddenException();
    return this.prisma.message.findMany({
      where: { threadId },
      orderBy: { sentAt: 'asc' },
      include: {
        sender: { select: { id: true, firstName: true, lastName: true } },
      },
    });
  }
}
