import {
  Injectable, NotFoundException, ForbiddenException
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Announcement, AnnouncementTarget } from './entities/announcement.entity';
import { Notification, NotificationType } from '../notifications/entities/notification.entity';
import { User, Role } from '../users/entities/user.entity';
import { CreateAnnouncementDto } from './dto/create-announcement.dto';
import { UpdateAnnouncementDto } from './dto/update-announcement.dto';
import { MailService } from '../notifications/mail.service';

@Injectable()
export class AnnouncementsService {
  constructor(
    @InjectRepository(Announcement)
    private readonly announcementRepo: Repository<Announcement>,

    @InjectRepository(Notification)
    private readonly notificationRepo: Repository<Notification>,

    @InjectRepository(User)
    private readonly userRepo: Repository<User>,

    private readonly mailService: MailService,
  ) {}

  // ─── Lister les annonces visibles par l'utilisateur ─────────────────────────
  async findAll(user: User): Promise<Announcement[]> {
    const query = this.announcementRepo
      .createQueryBuilder('a')
      .leftJoinAndSelect('a.author', 'author')
      .where('a.is_published = true')
      .andWhere(
        '(a.target = :all OR (a.target = :agency AND a.target_id = :agencyId) OR (a.target = :team AND a.target_id = :teamId))',
        {
          all:      AnnouncementTarget.ALL,
          agency:   AnnouncementTarget.AGENCY,
          agencyId: user.agency?.id ?? 0,
          team:     AnnouncementTarget.TEAM,
          teamId:   user.team?.id ?? 0,
        }
      )
      .orderBy('a.published_at', 'DESC');

    return query.getMany();
  }

  // ─── Lister toutes les annonces (admin) ──────────────────────────────────────
  async findAllAdmin(): Promise<Announcement[]> {
    return this.announcementRepo.find({
      relations: ['author'],
      order: { created_at: 'DESC' },
    });
  }

  // ─── Détail d'une annonce ────────────────────────────────────────────────────
  async findOne(id: number): Promise<Announcement> {
    const announcement = await this.announcementRepo.findOne({
      where: { id },
      relations: ['author'],
    });
    if (!announcement) throw new NotFoundException('Annonce introuvable');
    return announcement;
  }

  // ─── Créer une annonce ───────────────────────────────────────────────────────
  async create(dto: CreateAnnouncementDto, author: User): Promise<Announcement> {
    const announcement = this.announcementRepo.create({
      title:        dto.title,
      content:      dto.content,
      target:       dto.target,
      target_id:    dto.target_id,
      is_published: dto.is_published ?? false,
      published_at: dto.published_at ? new Date(dto.published_at) : undefined,
      author,
    });

    const saved = await this.announcementRepo.save(announcement);

    // Publier immédiatement si demandé
    if (saved.is_published) {
      await this.publishAndNotify(saved);
    }

    return saved;
  }

  // ─── Mettre à jour une annonce ───────────────────────────────────────────────
  async update(
    id: number,
    dto: UpdateAnnouncementDto,
    user: User,
  ): Promise<Announcement> {
    const announcement = await this.findOne(id);

    // Seul l'auteur ou un directeur peut modifier
    if (announcement.author.id !== user.id && user.role !== Role.DIRECTOR) {
      throw new ForbiddenException('Vous ne pouvez pas modifier cette annonce');
    }

    const wasPublished = announcement.is_published;

    Object.assign(announcement, {
      title:        dto.title        ?? announcement.title,
      content:      dto.content      ?? announcement.content,
      target:       dto.target       ?? announcement.target,
      target_id:    dto.target_id    ?? announcement.target_id,
      is_published: dto.is_published ?? announcement.is_published,
      published_at: dto.published_at
        ? new Date(dto.published_at)
        : announcement.published_at,
    });

    const saved = await this.announcementRepo.save(announcement);

    // Notifier si vient d'être publiée
    if (!wasPublished && saved.is_published) {
      await this.publishAndNotify(saved);
    }

    return saved;
  }

  // ─── Publier une annonce ─────────────────────────────────────────────────────
  async publish(id: number, user: User): Promise<Announcement> {
    return this.update(id, { is_published: true }, user);
  }

  // ─── Supprimer une annonce ───────────────────────────────────────────────────
  async remove(id: number, user: User): Promise<{ message: string }> {
    const announcement = await this.findOne(id);

    if (announcement.author.id !== user.id && user.role !== Role.DIRECTOR) {
      throw new ForbiddenException('Vous ne pouvez pas supprimer cette annonce');
    }

    await this.announcementRepo.remove(announcement);
    return { message: `Annonce "${announcement.title}" supprimée` };
  }

  // ─── Publier + notifier les destinataires ────────────────────────────────────
  private async publishAndNotify(announcement: Announcement): Promise<void> {
    // Mettre à jour published_at si pas défini
    if (!announcement.published_at) {
      announcement.published_at = new Date();
      await this.announcementRepo.save(announcement);
    }

    // Récupérer les destinataires selon le ciblage
    const recipients = await this.getRecipients(announcement);

    // Créer les notifications in-app
    const notifications = recipients.map(user =>
      this.notificationRepo.create({
        user,
        type:    NotificationType.ANNOUNCEMENT,
        message: `Nouvelle annonce : ${announcement.title}`,
        link:    `/announcements/${announcement.id}`,
      })
    );
    await this.notificationRepo.save(notifications);

    // Envoyer les emails
    for (const user of recipients) {
      await this.mailService.sendAnnouncementEmail(user.email, announcement);
    }
  }

  // ─── Récupérer les destinataires ─────────────────────────────────────────────
  private async getRecipients(announcement: Announcement): Promise<User[]> {
    const query = this.userRepo
      .createQueryBuilder('user')
      .where('user.is_active = true');

    switch (announcement.target) {
      case AnnouncementTarget.ALL:
        break;
      case AnnouncementTarget.AGENCY:
        query.andWhere('user.agency_id = :id', { id: announcement.target_id });
        break;
      case AnnouncementTarget.TEAM:
        query.andWhere('user.team_id = :id', { id: announcement.target_id });
        break;
    }

    return query.getMany();
  }
}