import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import {
  NotificationChannel,
  TicketPriority,
  TicketStatus,
} from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { NotificationsService } from '../notifications/notifications.service';
import { AuthenticatedUser } from '../../common/decorators/current-user.decorator';
import {
  AddTicketMessageDto,
  AssignTicketDto,
  CreateTicketDto,
  UpdateTicketStatusDto,
} from './dto/ticket.dto';

/**
 * SLA defaults in hours per priority (business-configurable later).
 */
const SLA_HOURS: Record<TicketPriority, number> = {
  URGENT: 4,
  HIGH: 24,
  MEDIUM: 72,
  LOW: 168,
};

@Injectable()
export class TicketsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly notifications: NotificationsService,
  ) {}

  async create(user: AuthenticatedUser, dto: CreateTicketDto) {
    if (!user.tenantId) throw new BadRequestException('Tenant required');

    const complex = await this.prisma.complex.findFirst({
      where: { id: dto.complexId, tenantId: user.tenantId },
    });
    if (!complex) throw new NotFoundException('Complex not found');

    const priority = dto.priority ?? TicketPriority.MEDIUM;
    const slaDueAt = new Date(Date.now() + SLA_HOURS[priority] * 3600 * 1000);

    const ticket = await this.prisma.ticket.create({
      data: {
        tenantId: user.tenantId,
        complexId: dto.complexId,
        lotId: dto.lotId,
        authorId: user.userId,
        category: dto.category,
        priority,
        title: dto.title,
        description: dto.description,
        location: dto.location,
        slaDueAt,
      },
      include: { author: { select: { id: true, email: true, firstName: true, lastName: true } } },
    });

    // Notify syndic(s) in the tenant
    const syndics = await this.prisma.userRole.findMany({
      where: { tenantId: user.tenantId, role: { code: 'SYNDIC' } },
      include: { user: true },
    });
    for (const s of syndics) {
      await this.notifications.send({
        tenantId: user.tenantId,
        userId: s.user.id,
        channel: NotificationChannel.IN_APP,
        template: 'ticket_created',
        recipient: s.user.email,
        subject: `Nouveau ticket: ${ticket.title}`,
        body: `Ticket ${ticket.category}/${ticket.priority} créé par ${ticket.author.email}`,
        metadata: { ticketId: ticket.id },
      });
    }
    return ticket;
  }

  async list(user: AuthenticatedUser, filters: {
    status?: TicketStatus;
    complexId?: string;
    assignedToMe?: boolean;
    mine?: boolean;
    priority?: TicketPriority;
  }) {
    if (!user.tenantId && !user.isSuperAdmin) return [];
    const where: any = {};
    if (user.tenantId) where.tenantId = user.tenantId;
    if (filters.status) where.status = filters.status;
    if (filters.priority) where.priority = filters.priority;
    if (filters.complexId) where.complexId = filters.complexId;
    if (filters.assignedToMe) where.assigneeId = user.userId;
    if (filters.mine) where.authorId = user.userId;

    // If user is only COPROPRIETAIRE/LOCATAIRE, restrict to own tickets
    const privileged = user.roles.some((r) =>
      ['SUPERADMIN', 'SYNDIC', 'GARDIEN', 'PRESTATAIRE'].includes(r.code),
    );
    if (!privileged) where.authorId = user.userId;

    return this.prisma.ticket.findMany({
      where,
      orderBy: [{ priority: 'desc' }, { createdAt: 'desc' }],
      include: {
        author: { select: { id: true, email: true, firstName: true, lastName: true } },
        assignee: { select: { id: true, email: true } },
        lot: { select: { id: true, lotNumber: true } },
        _count: { select: { messages: true, attachments: true } },
      },
      take: 200,
    });
  }

  async findOne(user: AuthenticatedUser, id: string) {
    const ticket = await this.prisma.ticket.findUnique({
      where: { id },
      include: {
        author: { select: { id: true, email: true, firstName: true } },
        assignee: { select: { id: true, email: true } },
        messages: {
          orderBy: { createdAt: 'asc' },
          include: { author: { select: { id: true, email: true, firstName: true } } },
        },
        attachments: true,
        lot: { select: { id: true, lotNumber: true } },
      },
    });
    if (!ticket) throw new NotFoundException('Ticket not found');
    if (!user.isSuperAdmin && ticket.tenantId !== user.tenantId) {
      throw new ForbiddenException();
    }
    return ticket;
  }

  async addMessage(user: AuthenticatedUser, id: string, dto: AddTicketMessageDto) {
    const ticket = await this.findOne(user, id);
    const privileged = user.roles.some((r) =>
      ['SUPERADMIN', 'SYNDIC', 'GARDIEN', 'PRESTATAIRE'].includes(r.code),
    );
    if (dto.isInternal && !privileged) {
      throw new ForbiddenException('Internal notes reserved for staff');
    }
    return this.prisma.ticketMessage.create({
      data: {
        ticketId: ticket.id,
        authorId: user.userId,
        body: dto.body,
        isInternal: dto.isInternal ?? false,
      },
    });
  }

  async assign(user: AuthenticatedUser, id: string, dto: AssignTicketDto) {
    const ticket = await this.findOne(user, id);
    const assignee = await this.prisma.user.findUnique({
      where: { id: dto.assigneeId },
      include: { roles: { include: { role: true } } },
    });
    if (!assignee) throw new NotFoundException('Assignee not found');

    const validRole = assignee.roles.some(
      (r) =>
        r.tenantId === ticket.tenantId &&
        ['GARDIEN', 'PRESTATAIRE', 'SYNDIC'].includes(r.role.code),
    );
    if (!validRole) {
      throw new BadRequestException('Assignee must be Gardien, Prestataire or Syndic of the tenant');
    }

    const updated = await this.prisma.ticket.update({
      where: { id },
      data: {
        assigneeId: dto.assigneeId,
        status: ticket.status === 'NEW' ? TicketStatus.ACKNOWLEDGED : ticket.status,
        acknowledgedAt: ticket.acknowledgedAt ?? new Date(),
      },
    });

    await this.notifications.send({
      tenantId: ticket.tenantId,
      userId: dto.assigneeId,
      channel: NotificationChannel.IN_APP,
      template: 'ticket_assigned',
      recipient: assignee.email,
      subject: `Ticket assigné: ${ticket.title}`,
      body: `Le ticket ${ticket.id.slice(0, 8)} vous a été assigné`,
      metadata: { ticketId: ticket.id },
    });
    return updated;
  }

  async updateStatus(user: AuthenticatedUser, id: string, dto: UpdateTicketStatusDto) {
    const ticket = await this.findOne(user, id);
    const privileged = user.roles.some((r) =>
      ['SUPERADMIN', 'SYNDIC', 'GARDIEN', 'PRESTATAIRE'].includes(r.code),
    );
    if (!privileged) throw new ForbiddenException('Only staff can change status');

    const now = new Date();
    const data: any = { status: dto.status as TicketStatus };
    if (dto.status === 'ACKNOWLEDGED' && !ticket.acknowledgedAt) data.acknowledgedAt = now;
    if (dto.status === 'RESOLVED' && !ticket.resolvedAt) data.resolvedAt = now;
    if (dto.status === 'CLOSED' && !ticket.closedAt) data.closedAt = now;

    return this.prisma.ticket.update({ where: { id }, data });
  }
}
