import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { NotificationChannel, NotificationStatus, Prisma, ResidentRequestStatus, UserRole } from '@prisma/client';
import { AuditService } from '../audit/audit.service';
import { generateCode } from '../common/code-generator';
import { resolveEstateScope } from '../common/estate-scope';
import { RequestUser } from '../common/request-user';
import { PrismaService } from '../database/prisma.service';
import { CreateResidentRequestDto, RespondResidentRequestDto } from './dto/request.dto';

const residentRequestCategories = [
  { label: 'Security Risk', category: 'SECURITY', metadataKey: 'SECURITY_RISK' },
  { label: 'Street Light Issue', category: 'INFRASTRUCTURE', metadataKey: 'STREET_LIGHT_ISSUE' },
  { label: 'Environment Issue', category: 'ESTATE_SERVICES', metadataKey: 'ENVIRONMENT_ISSUE' },
  { label: 'Debris/Waste', category: 'ESTATE_SERVICES', metadataKey: 'DEBRIS_WASTE' },
  { label: 'Noise Complaint', category: 'ESTATE_SERVICES', metadataKey: 'NOISE_COMPLAINT' },
  { label: 'Maintenance', category: 'INFRASTRUCTURE', metadataKey: 'MAINTENANCE' },
  { label: 'Water Issue', category: 'INFRASTRUCTURE', metadataKey: 'WATER_ISSUE' },
  { label: 'Power/Electricity Issue', category: 'INFRASTRUCTURE', metadataKey: 'POWER_ELECTRICITY_ISSUE' },
  { label: 'Security Support', category: 'SECURITY', metadataKey: 'SECURITY_SUPPORT' },
  { label: 'Infrastructure', category: 'INFRASTRUCTURE', metadataKey: 'INFRASTRUCTURE' },
  { label: 'Other', category: 'SUPPORT', metadataKey: 'OTHER' }
] as const;

const residentRequestPriorities = [
  { label: 'Low', value: 'LOW' },
  { label: 'Normal', value: 'NORMAL' },
  { label: 'High', value: 'HIGH' },
  { label: 'Urgent', value: 'URGENT' }
] as const;

@Injectable()
export class RequestsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly audit: AuditService
  ) {}

  async findAll(user: RequestUser, estateId?: string, residentId?: string, status?: ResidentRequestStatus) {
    const scopedEstateId = resolveEstateScope(user, estateId);
    const resident = user.role === UserRole.RESIDENT ? await this.getResidentForUser(user.id, scopedEstateId) : null;
    return this.prisma.residentRequest.findMany({
      where: {
        estateId: scopedEstateId,
        residentId: resident ? resident.id : residentId,
        status
      },
      include: {
        resident: true,
        messages: { orderBy: { createdAt: 'asc' }, include: { author: { select: { id: true, name: true, role: true } } } }
      },
      orderBy: { createdAt: 'desc' },
      take: 100
    });
  }

  getOptions() {
    return {
      categories: residentRequestCategories,
      priorities: residentRequestPriorities,
      fields: ['title', 'category', 'priority', 'description', 'photoUrl', 'location'],
      photoUploadOptional: true
    };
  }

  async create(dto: CreateResidentRequestDto, user: RequestUser) {
    const estateId = resolveEstateScope(user, dto.estateId);
    const resident =
      user.role === UserRole.RESIDENT
        ? await this.getResidentForUser(user.id, estateId)
        : await this.prisma.resident.findFirst({ where: { id: dto.residentId, estateId } });
    if (!resident) {
      throw new NotFoundException('Resident not found');
    }

    const request = await this.prisma.residentRequest.create({
      data: {
        requestCode: `REQ-${generateCode(8)}`,
        estateId,
        residentId: resident.id,
        category: dto.category,
        priority: dto.priority,
        title: dto.title.trim(),
        description: dto.description.trim(),
        metadata: this.toJson({
          ...(dto.metadata ?? {}),
          categoryLabel: dto.metadata?.categoryLabel ?? this.categoryLabel(dto.category),
          location: dto.metadata?.location ?? null,
          photoUrl: dto.metadata?.photoUrl ?? null
        })
      },
      include: { resident: true }
    });

    await this.notifyEstateSecurity(estateId, request.id, request.title);
    await this.audit.record({
      estateId,
      actorType: 'USER',
      actorId: user.id,
      action: 'RESIDENT_REQUEST_CREATED',
      metadata: { requestId: request.id, category: request.category, priority: request.priority }
    });

    return request;
  }

  async respond(id: string, dto: RespondResidentRequestDto, user: RequestUser) {
    const existing = await this.prisma.residentRequest.findUnique({
      where: { id },
      include: { resident: true }
    });
    if (!existing) {
      throw new NotFoundException('Request not found');
    }
    resolveEstateScope(user, existing.estateId);
    if (user.role === UserRole.RESIDENT) {
      throw new ForbiddenException('Residents cannot respond to requests as security');
    }

    const updated = await this.prisma.$transaction(async (tx) => {
      const request = await tx.residentRequest.update({
        where: { id },
        data: {
          status: dto.status,
          assignedToId: dto.assignedToId,
          resolvedAt: dto.status === ResidentRequestStatus.RESOLVED ? new Date() : undefined,
          closedAt: dto.status === ResidentRequestStatus.CLOSED ? new Date() : undefined
        },
        include: { resident: true }
      });
      await tx.residentRequestMessage.create({
        data: {
          requestId: id,
          authorId: user.id,
          message: dto.message.trim(),
          metadata: this.toJson(dto.metadata)
        }
      });
      await tx.appNotification.create({
        data: {
          estateId: request.estateId,
          userId: request.resident.userId,
          channel: NotificationChannel.IN_APP,
          status: NotificationStatus.PENDING,
          title: `Request update: ${request.title}`,
          body: dto.message.trim(),
          data: { requestId: request.id, status: request.status, deepLink: '/requests' }
        }
      });
      return request;
    });

    await this.audit.record({
      estateId: existing.estateId,
      actorType: 'USER',
      actorId: user.id,
      action: 'RESIDENT_REQUEST_RESPONDED',
      metadata: { requestId: id, status: dto.status }
    });

    return updated;
  }

  private async notifyEstateSecurity(estateId: string, requestId: string, title: string) {
    const recipients = await this.prisma.user.findMany({
      where: {
        estateId,
        status: 'ACTIVE',
        role: { in: [UserRole.ESTATE_ADMIN, UserRole.SECURITY_SUPERVISOR, UserRole.GUARD] }
      },
      select: { id: true }
    });

    await this.prisma.appNotification.createMany({
      data: recipients.map((recipient) => ({
        estateId,
        userId: recipient.id,
        channel: NotificationChannel.IN_APP,
        status: NotificationStatus.PENDING,
        title: 'New resident request',
        body: title,
        data: { requestId, deepLink: '/requests' }
      }))
    });
  }

  private categoryLabel(category: string) {
    return residentRequestCategories.find((item) => item.category === category)?.label ?? category;
  }

  private async getResidentForUser(userId: string, estateId: string) {
    return this.prisma.resident.findFirst({ where: { userId, estateId } });
  }

  private toJson(value: unknown) {
    if (value === undefined) {
      return undefined;
    }
    return JSON.parse(JSON.stringify(value)) as Prisma.InputJsonValue;
  }
}
