import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import {
  AnnouncementCategory,
  AnnouncementPriority,
  AnnouncementStatus,
  NotificationChannel,
  NotificationStatus,
  Prisma,
  UserRole
} from '@prisma/client';
import { AuditService } from '../audit/audit.service';
import { resolveEstateScope } from '../common/estate-scope';
import { getNumericPlanLimit, startOfCurrentMonth, startOfCurrentWeek } from '../common/plan-limits';
import { RequestUser } from '../common/request-user';
import { PrismaService } from '../database/prisma.service';
import { CreateAnnouncementDto } from './dto/announcement.dto';

const blockedCampaignTerms = [
  'advert',
  'buy now',
  'campaign',
  'election',
  'vote for',
  'political',
  'promo',
  'discount sale',
  'sponsored',
  'external business'
];

const announcementCategories = [
  { label: 'Security', category: AnnouncementCategory.SECURITY },
  { label: 'Maintenance', category: AnnouncementCategory.MAINTENANCE },
  { label: 'Estate News', category: AnnouncementCategory.NEWS },
  { label: 'Events', category: AnnouncementCategory.EVENTS },
  { label: 'Utility Notice', category: AnnouncementCategory.NOTICES },
  { label: 'Parking', category: AnnouncementCategory.NOTICES, metadataKey: 'PARKING' },
  { label: 'Visitor Policy', category: AnnouncementCategory.NOTICES, metadataKey: 'VISITOR_POLICY' },
  { label: 'Emergency', category: AnnouncementCategory.EMERGENCY }
] as const;

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

  findAll(user: RequestUser, estateId?: string) {
    const scopedEstateId = resolveEstateScope(user, estateId);
    return this.prisma.estateAnnouncement.findMany({
      where: {
        estateId: scopedEstateId,
        status: AnnouncementStatus.PUBLISHED,
        OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }]
      },
      orderBy: { publishedAt: 'desc' },
      take: 100,
      include: { author: { select: { id: true, name: true, role: true } } }
    });
  }

  findOne(user: RequestUser, id: string) {
    const estateId = resolveEstateScope(user, undefined);
    return this.prisma.estateAnnouncement
      .findFirst({
      where: {
        id,
        estateId,
        status: AnnouncementStatus.PUBLISHED,
        OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }]
      },
      include: {
        author: { select: { id: true, name: true, role: true } },
        notifications: {
          where: { userId: user.id },
          select: { id: true, status: true, readAt: true },
          take: 1
        }
      }
      })
      .then((announcement) => {
        if (!announcement) {
          throw new NotFoundException('Announcement not found');
        }
        return announcement;
      });
  }

  getOptions() {
    return {
      categories: announcementCategories,
      priorities: Object.values(AnnouncementPriority),
      contentControls: {
        adsAllowed: false,
        titleMaxLength: 80,
        bodyMaxLength: 700,
        flaggedKeywordApproval: true,
        auditLogs: true
      }
    };
  }

  async create(dto: CreateAnnouncementDto, user: RequestUser) {
    const estateId = resolveEstateScope(user, dto.estateId);
    this.assertRoleCanSend(user.role, dto);
    const blocked = this.findBlockedTerm(`${dto.title} ${dto.body}`);
    if (blocked) {
      await this.audit.record({
        estateId,
        actorType: 'USER',
        actorId: user.id,
        action: 'ANNOUNCEMENT_BLOCKED_FOR_REVIEW',
        metadata: { term: blocked, category: dto.category, title: dto.title }
      });
      throw new BadRequestException('Announcement appears promotional or unrelated and has been blocked for review');
    }
    await this.assertPlanLimit(estateId, dto);

    const targetRoles = (dto.targetRoles?.length ? dto.targetRoles : [UserRole.RESIDENT]).map(String);
    const deliveryChannels = dto.deliveryChannels?.length ? dto.deliveryChannels : [NotificationChannel.IN_APP, NotificationChannel.PUSH];

    const announcement = await this.prisma.$transaction(async (tx) => {
      const created = await tx.estateAnnouncement.create({
        data: {
          estateId,
          authorId: user.id,
          title: dto.title.trim(),
          body: dto.body.trim(),
          category: dto.category,
          priority: dto.priority ?? AnnouncementPriority.NORMAL,
          status: AnnouncementStatus.PUBLISHED,
          targetRoles,
          targetUserIds: dto.targetUserIds ?? [],
          deliveryChannels,
          publishedAt: new Date(),
          expiresAt: dto.expiresAt,
          metadata: this.toJson({
            ...(dto.metadata ?? {}),
            emergencyDelivery: dto.priority === AnnouncementPriority.EMERGENCY || dto.category === AnnouncementCategory.EMERGENCY
          })
        }
      });

      const recipientOr: Prisma.UserWhereInput[] = [{ role: { in: targetRoles as UserRole[] } }];
      if (dto.targetUserIds?.length) {
        recipientOr.push({ id: { in: dto.targetUserIds } });
      }

      const users = await tx.user.findMany({
        where: {
          estateId,
          status: 'ACTIVE',
          OR: recipientOr
        },
        select: { id: true, email: true }
      });

      await tx.appNotification.createMany({
        data: users.flatMap((recipient) =>
          deliveryChannels.map((channel) => ({
            estateId,
            userId: recipient.id,
            announcementId: created.id,
            channel,
            status: NotificationStatus.PENDING,
            title: created.title,
            body: created.body,
            data: {
              category: created.category,
              priority: created.priority,
              deepLink: '/announcements',
              emailAvailable: Boolean(recipient.email)
            }
          }))
        )
      });

      return created;
    });

    await this.audit.record({
      estateId,
      actorType: 'USER',
      actorId: user.id,
      action: 'ESTATE_ANNOUNCEMENT_SENT',
      metadata: {
        announcementId: announcement.id,
        category: announcement.category,
        priority: announcement.priority,
        channels: deliveryChannels
      }
    });

    return announcement;
  }

  private assertRoleCanSend(role: UserRole, dto: CreateAnnouncementDto) {
    if (role === UserRole.ESTATE_ADMIN || role === UserRole.SUPER_ADMIN) {
      return;
    }

    if (role === UserRole.SECURITY_SUPERVISOR) {
      const allowed: AnnouncementCategory[] = [
        AnnouncementCategory.SECURITY,
        AnnouncementCategory.EMERGENCY,
        AnnouncementCategory.NOTICES,
        AnnouncementCategory.MAINTENANCE
      ];
      if (!allowed.includes(dto.category)) {
        throw new ForbiddenException('Supervisors can only send security, gate, maintenance, notice, and emergency messages');
      }
      return;
    }

    if (role === UserRole.GUARD && (dto.category === AnnouncementCategory.EMERGENCY || dto.priority === AnnouncementPriority.EMERGENCY)) {
      return;
    }

    throw new ForbiddenException('This role cannot send estate announcements');
  }

  private async assertPlanLimit(estateId: string, dto: CreateAnnouncementDto) {
    const estate = await this.prisma.estate.findUnique({
      where: { id: estateId },
      select: { subscriptionPlan: { select: { limits: true } } }
    });
    const limits = estate?.subscriptionPlan?.limits;
    const isEmergency = dto.category === AnnouncementCategory.EMERGENCY || dto.priority === AnnouncementPriority.EMERGENCY;
    const limitKey = isEmergency ? 'sosAlertsPerMonth' : 'announcementsPerWeek';
    const periodStart = isEmergency ? startOfCurrentMonth() : startOfCurrentWeek();
    const limit = getNumericPlanLimit(limits, limitKey);
    const monthlyAnnouncementLimit = !isEmergency ? getNumericPlanLimit(limits, 'announcementsPerMonth') : null;

    if (limit !== null) {
      const used = await this.prisma.estateAnnouncement.count({
        where: {
          estateId,
          createdAt: { gte: periodStart },
          ...(isEmergency
            ? { OR: [{ category: AnnouncementCategory.EMERGENCY }, { priority: AnnouncementPriority.EMERGENCY }] }
            : {})
        }
      });
      if (used >= limit) {
        throw new BadRequestException('Announcement limit for this plan has been reached');
      }
    }

    if (monthlyAnnouncementLimit !== null) {
      const used = await this.prisma.estateAnnouncement.count({
        where: { estateId, createdAt: { gte: startOfCurrentMonth() } }
      });
      if (used >= monthlyAnnouncementLimit) {
        throw new BadRequestException('Monthly announcement limit for this plan has been reached');
      }
    }
  }

  private findBlockedTerm(value: string) {
    const normalized = value.toLowerCase();
    return blockedCampaignTerms.find((term) => normalized.includes(term));
  }

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