import {
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Repository } from 'typeorm';
import type { AuthUser } from '../../common/types/auth-user.type';
import { ActivityLogsService } from '../activity-logs/activity-logs.service';
import { ActivityAction } from '../activity-logs/entities/activity-log.entity';
import { CreateNotificationDto } from './dto/create-notification.dto';
import { UpdateNotificationDto } from './dto/update-notification.dto';
import { Notification } from './entities/notification.entity';

@Injectable()
export class NotificationsService {
  constructor(
    @InjectRepository(Notification)
    private readonly notificationRepository: Repository<Notification>,

    private readonly activityLogsService: ActivityLogsService,
  ) {}

  private ensureCanAccessNotification(
    currentUser: AuthUser,
    notification: Notification,
  ) {
    if (currentUser.role === 'super_admin') {
      return;
    }

    if (!currentUser.agencyId) {
      throw new ForbiddenException('Agency access required');
    }

    if (
      notification.targetAgencyId !== null &&
      notification.targetAgencyId !== currentUser.agencyId
    ) {
      throw new ForbiddenException('Access denied for this notification');
    }
  }

  private async createAuditLog(
    action: ActivityAction,
    title: string,
    description: string,
    notification: Notification,
    severity = 'info',
  ) {
    await this.activityLogsService.create({
      module: 'notifications',
      action,
      title,
      description,
      entityType: 'notification',
      entityId: notification.id,
      actor: 'System',
      severity,
      targetAgencyId: notification.targetAgencyId ?? undefined,
    });
  }

  async create(data: CreateNotificationDto) {
    const notification = this.notificationRepository.create(data);
    const savedNotification =
      await this.notificationRepository.save(notification);

    await this.createAuditLog(
      ActivityAction.CREATED,
      'Notification créée',
      `Notification #${savedNotification.id} créée`,
      savedNotification,
      'info',
    );

    return savedNotification;
  }

  createForUser(data: CreateNotificationDto, currentUser: AuthUser) {
    if (currentUser.role !== 'super_admin') {
      if (!currentUser.agencyId) {
        throw new ForbiddenException('Agency access required');
      }

      data.targetAgencyId = currentUser.agencyId;
    }

    return this.create(data);
  }

  findAll() {
    return this.notificationRepository.find({
      order: {
        createdAt: 'DESC',
      },
    });
  }

  findAllForUser(currentUser: AuthUser) {
    if (currentUser.role === 'super_admin') {
      return this.findAll();
    }

    if (!currentUser.agencyId) {
      return [];
    }

    return this.notificationRepository.find({
      where: [
        { targetAgencyId: currentUser.agencyId },
        { targetAgencyId: IsNull() },
      ],
      order: {
        createdAt: 'DESC',
      },
    });
  }

  unread() {
    return this.notificationRepository.find({
      where: {
        isRead: false,
      },
      order: {
        createdAt: 'DESC',
      },
    });
  }

  unreadForUser(currentUser: AuthUser) {
    if (currentUser.role === 'super_admin') {
      return this.unread();
    }

    if (!currentUser.agencyId) {
      return [];
    }

    return this.notificationRepository.find({
      where: [
        {
          isRead: false,
          targetAgencyId: currentUser.agencyId,
        },
        {
          isRead: false,
          targetAgencyId: IsNull(),
        },
      ],
      order: {
        createdAt: 'DESC',
      },
    });
  }

  async findOne(id: number) {
    const notification = await this.notificationRepository.findOne({
      where: { id },
    });

    if (!notification) {
      throw new NotFoundException('Notification not found');
    }

    return notification;
  }

  async findOneForUser(id: number, currentUser: AuthUser) {
    const notification = await this.findOne(id);
    this.ensureCanAccessNotification(currentUser, notification);
    return notification;
  }

  async update(id: number, data: UpdateNotificationDto) {
    const notification = await this.findOne(id);

    Object.assign(notification, data);

    const savedNotification =
      await this.notificationRepository.save(notification);

    await this.createAuditLog(
      ActivityAction.UPDATED,
      'Notification modifiée',
      `Notification #${savedNotification.id} modifiée`,
      savedNotification,
      'info',
    );

    return savedNotification;
  }

  async updateForUser(
    id: number,
    data: UpdateNotificationDto,
    currentUser: AuthUser,
  ) {
    const notification = await this.findOne(id);
    this.ensureCanAccessNotification(currentUser, notification);

    if (currentUser.role !== 'super_admin') {
      delete data.targetAgencyId;
      delete data.targetRole;
    }

    return this.update(id, data);
  }

  async markRead(id: number) {
    const notification = await this.findOne(id);

    notification.isRead = true;

    const savedNotification =
      await this.notificationRepository.save(notification);

    await this.createAuditLog(
      ActivityAction.UPDATED,
      'Notification lue',
      `Notification #${savedNotification.id} marquée comme lue`,
      savedNotification,
      'success',
    );

    return savedNotification;
  }

  async markReadForUser(id: number, currentUser: AuthUser) {
    const notification = await this.findOne(id);
    this.ensureCanAccessNotification(currentUser, notification);
    return this.markRead(id);
  }

  async markUnread(id: number) {
    const notification = await this.findOne(id);

    notification.isRead = false;

    const savedNotification =
      await this.notificationRepository.save(notification);

    await this.createAuditLog(
      ActivityAction.UPDATED,
      'Notification non lue',
      `Notification #${savedNotification.id} marquée comme non lue`,
      savedNotification,
      'info',
    );

    return savedNotification;
  }

  async markUnreadForUser(id: number, currentUser: AuthUser) {
    const notification = await this.findOne(id);
    this.ensureCanAccessNotification(currentUser, notification);
    return this.markUnread(id);
  }

  async remove(id: number) {
    const notification = await this.findOne(id);

    await this.createAuditLog(
      ActivityAction.DELETED,
      'Notification supprimée',
      `Notification #${notification.id} supprimée`,
      notification,
      'warning',
    );

    return this.notificationRepository.softRemove(notification);
  }

  async removeForUser(id: number, currentUser: AuthUser) {
    const notification = await this.findOne(id);
    this.ensureCanAccessNotification(currentUser, notification);

    await this.createAuditLog(
      ActivityAction.DELETED,
      'Notification supprimée',
      `Notification #${notification.id} supprimée`,
      notification,
      'warning',
    );

    return this.notificationRepository.softRemove(notification);
  }
}