import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Between } from 'typeorm';
import { User } from '../users/entities/user.entity';
import { Team } from '../teams/entities/team.entity';
import { LeaveRequest, LeaveStatus } from '../leaves/entities/leave-request.entity';
import { PublicHoliday } from '../holidays/entities/public-holiday.entity';
import dayjs from 'dayjs';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';

dayjs.extend(isSameOrAfter);
dayjs.extend(isSameOrBefore);

export interface PlanningDay {
  date: string;
  is_holiday: boolean;
  holiday_label?: string;
  is_weekend: boolean;
  absences: {
    user_id: number;
    user_name: string;
    leave_type: string;
    leave_color: string;
    days_count: number;
  }[];
  presence_count: number;
  absence_count: number;
}

export interface TeamPlanning {
  team_id: number;
  team_name: string;
  min_presence: number;
  members: {
    id: number;
    matricule: string;
    first_name: string;
    last_name: string;
    work_schedule: string;
  }[];
  days: PlanningDay[];
  period: { start: string; end: string; view: string };
}

export interface GlobalPlanningMember {
  id: number;
  matricule: string;
  first_name: string;
  last_name: string;
  team_id: number;
  team_name: string;
  work_schedule: string;
  absences: {
    date_start: string;
    date_end: string;
    leave_type: string;
    leave_color: string;
    days_count: number;
  }[];
}

export interface GlobalPlanning {
  period: { start: string; end: string; view: string };
  holidays: { date: string; label: string }[];
  members: GlobalPlanningMember[];
  stats: {
    total_members: number;
    absence_rate_by_team: {
      team_id: number;
      team_name: string;
      total_members: number;
      absent_today: number;
      rate: number;
    }[];
  };
}

@Injectable()
export class PlanningService {
  constructor(
    @InjectRepository(User)
    private readonly userRepo: Repository<User>,

    @InjectRepository(Team)
    private readonly teamRepo: Repository<Team>,

    @InjectRepository(LeaveRequest)
    private readonly leaveRequestRepo: Repository<LeaveRequest>,

    @InjectRepository(PublicHoliday)
    private readonly holidayRepo: Repository<PublicHoliday>,
  ) {}

  // ─── Planning équipe (mensuel ou hebdomadaire) ───────────────────────────────
  async getTeamPlanning(
    teamId: number,
    view: 'month' | 'week',
    date: string,
  ): Promise<TeamPlanning> {
    const team = await this.teamRepo.findOne({
      where: { id: teamId },
      relations: ['agency'],
    });
    if (!team) throw new NotFoundException('Équipe introuvable');

    // Calculer la période selon la vue
    const { startDate, endDate } = this.getPeriodBounds(view, date);

    // Membres de l'équipe
    const members = await this.userRepo.find({
      where: { team: { id: teamId }, is_active: true },
      order: { last_name: 'ASC' },
    });

    // Congés approuvés sur la période
    const leaveRequests = await this.leaveRequestRepo.find({
      where: {
        status: LeaveStatus.APPROVED,
        user: { team: { id: teamId } },
      },
      relations: ['user', 'leave_type'],
    });

    // Filtrer les congés qui chevauchent la période
    const filteredLeaves = leaveRequests.filter(lr => {
      const leaveStart = dayjs(lr.start_date);
      const leaveEnd   = dayjs(lr.end_date);
      return leaveStart.isBefore(dayjs(endDate).add(1, 'day'))
          && leaveEnd.isAfter(dayjs(startDate).subtract(1, 'day'));
    });

    // Jours fériés sur la période
    const holidays = await this.holidayRepo.find({
      where: { date: Between(new Date(startDate), new Date(endDate)) },
    });
    const holidayMap = new Map(
      holidays.map(h => [dayjs(h.date).format('YYYY-MM-DD'), h.label])
    );

    // Construire les jours
    const days = this.buildDays(
      startDate,
      endDate,
      members,
      filteredLeaves,
      holidayMap,
      team.min_presence,
    );

    return {
      team_id:      team.id,
      team_name:    team.name,
      min_presence: team.min_presence,
      members: members.map(m => ({
        id:            m.id,
        matricule:     m.matricule,
        first_name:    m.first_name,
        last_name:     m.last_name,
        work_schedule: m.work_schedule,
      })),
      days,
      period: { start: startDate, end: endDate, view },
    };
  }

  // ─── Construire les jours du planning ───────────────────────────────────────
  private buildDays(
    startDate: string,
    endDate: string,
    members: User[],
    leaveRequests: LeaveRequest[],
    holidayMap: Map<string, string>,
    minPresence: number,
  ): PlanningDay[] {
    const days: PlanningDay[] = [];
    let current = dayjs(startDate);
    const end   = dayjs(endDate);

    while (current.isBefore(end.add(1, 'day'))) {
      const dateStr    = current.format('YYYY-MM-DD');
      const dayOfWeek  = current.day(); // 0=Dim, 6=Sam
      const is_weekend = dayOfWeek === 0 || dayOfWeek === 6;
      const is_holiday = holidayMap.has(dateStr);

      // Trouver les absences de ce jour
      const absences = leaveRequests
        .filter(lr => {
          const start = dayjs(lr.start_date);
          const end   = dayjs(lr.end_date);
          return !current.isBefore(start) && !current.isAfter(end);
        })
        .map(lr => ({
          user_id:     lr.user.id,
          user_name:   `${lr.user.first_name} ${lr.user.last_name}`,
          leave_type:  lr.leave_type.label,
          leave_color: lr.leave_type.color_hex,
          days_count:  lr.days_count,
        }));

      const absence_count  = absences.length;
      const presence_count = Math.max(members.length - absence_count, 0);

      days.push({
        date: dateStr,
        is_holiday,
        holiday_label: holidayMap.get(dateStr),
        is_weekend,
        absences,
        presence_count,
        absence_count,
      });

      current = current.add(1, 'day');
    }

    return days;
  }

  // ─── Calculer les bornes de la période ──────────────────────────────────────
  private getPeriodBounds(
    view: 'month' | 'week',
    date: string,
  ): { startDate: string; endDate: string } {
    const d = dayjs(date);

    if (view === 'month') {
      return {
        startDate: d.startOf('month').format('YYYY-MM-DD'),
        endDate:   d.endOf('month').format('YYYY-MM-DD'),
      };
    }

    // Vue semaine : lundi → dimanche
    return {
      startDate: d.startOf('week').add(1, 'day').format('YYYY-MM-DD'),
      endDate:   d.endOf('week').add(1, 'day').format('YYYY-MM-DD'),
    };
  }

  // ─── Vérifier le seuil minimum de présence ──────────────────────────────────
  async checkPresenceThreshold(
    teamId: number,
    startDate: string,
    endDate: string,
  ): Promise<{ ok: boolean; conflicts: { date: string; presence: number; minimum: number }[] }> {
    const planning = await this.getTeamPlanning(teamId, 'month', startDate);
    const conflicts = planning.days
      .filter(d => {
        if (d.is_weekend || d.is_holiday) return false;
        return d.presence_count < planning.min_presence;
      })
      .map(d => ({
        date:     d.date,
        presence: d.presence_count,
        minimum:  planning.min_presence,
      }));

    return { ok: conflicts.length === 0, conflicts };
  }

  // ─── Planning global (tous les salariés) ─────────────────────────────────────
  async getGlobalPlanning(
    view: 'month' | 'week',
    date: string,
    teamId?: number,
    search?: string,
    page: number = 1,
    limit: number = 20,
  ): Promise<GlobalPlanning & { total: number; page: number; limit: number }> {
    const { startDate, endDate } = this.getPeriodBounds(view, date);

    // Requête utilisateurs avec filtres
    const userQuery = this.userRepo.createQueryBuilder('user')
      .leftJoinAndSelect('user.team', 'team')
      .leftJoinAndSelect('user.agency', 'agency')
      .where('user.is_active = :active', { active: true });

    if (teamId) {
      userQuery.andWhere('user.team_id = :teamId', { teamId });
    }

    if (search) {
      userQuery.andWhere(
        '(user.first_name LIKE :search OR user.last_name LIKE :search OR user.matricule LIKE :search)',
        { search: `%${search}%` },
      );
    }

    userQuery.orderBy('team.name', 'ASC').addOrderBy('user.last_name', 'ASC');

    const total   = await userQuery.getCount();
    const allUsers = await userQuery.getMany();

    // Pagination
    const paginatedUsers = allUsers.slice((page - 1) * limit, page * limit);

    // Congés approuvés sur la période
    const leaveRequests = await this.leaveRequestRepo
      .createQueryBuilder('lr')
      .leftJoinAndSelect('lr.user', 'user')
      .leftJoinAndSelect('lr.leave_type', 'leave_type')
      .where('lr.status = :status', { status: LeaveStatus.APPROVED })
      .andWhere('lr.start_date <= :endDate', { endDate })
      .andWhere('lr.end_date >= :startDate', { startDate })
      .getMany();

    // Jours fériés sur la période
    const holidays = await this.holidayRepo.find({
      where: { date: Between(new Date(startDate), new Date(endDate)) },
      order: { date: 'ASC' },
    });

    // Construire les membres avec leurs absences
    const members: GlobalPlanningMember[] = paginatedUsers.map(user => {
      const userLeaves = leaveRequests.filter(lr => lr.user.id === user.id);

      return {
        id:            user.id,
        matricule:     user.matricule,
        first_name:    user.first_name,
        last_name:     user.last_name,
        team_id:       user.team?.id,
        team_name:     user.team?.name ?? 'Sans équipe',
        work_schedule: user.work_schedule,
        absences: userLeaves.map(lr => ({
          date_start:  dayjs(lr.start_date).format('YYYY-MM-DD'),
          date_end:    dayjs(lr.end_date).format('YYYY-MM-DD'),
          leave_type:  lr.leave_type.label,
          leave_color: lr.leave_type.color_hex,
          days_count:  lr.days_count,
        })),
      };
    });

    // Stats par équipe (sur tous les users, pas seulement la page)
    const teams = await this.teamRepo.find({ where: { ...(teamId ? { id: teamId } : {}) } });
    const today = dayjs().format('YYYY-MM-DD');

    const absence_rate_by_team = await Promise.all(
      teams.map(async team => {
        const teamMembers = allUsers.filter(u => u.team?.id === team.id);
        const absentToday = leaveRequests.filter(lr =>
          lr.user.team?.id === team.id &&
          dayjs(today).isSameOrAfter(dayjs(lr.start_date)) &&
          dayjs(today).isSameOrBefore(dayjs(lr.end_date)),
        ).length;

        return {
          team_id:       team.id,
          team_name:     team.name,
          total_members: teamMembers.length,
          absent_today:  absentToday,
          rate: teamMembers.length > 0
            ? Math.round((absentToday / teamMembers.length) * 100)
            : 0,
        };
      }),
    );

    return {
      period:   { start: startDate, end: endDate, view },
      holidays: holidays.map(h => ({
        date:  dayjs(h.date).format('YYYY-MM-DD'),
        label: h.label,
      })),
      members,
      stats: {
        total_members: total,
        absence_rate_by_team,
      },
      total,
      page,
      limit,
    };
  }

  // ─── Vérifier si une nouvelle demande crée un conflit ───────────────────────
  async checkNewRequestConflict(
    userId: number,
    startDate: string,
    endDate: string,
  ): Promise<{
    has_conflict: boolean;
    conflicts: { date: string; presence: number; minimum: number }[];
    warning_message?: string;
  }> {
    // Récupérer l'équipe du collaborateur
    const user = await this.userRepo.findOne({
      where: { id: userId },
      relations: ['team'],
    });

    if (!user?.team) {
      return { has_conflict: false, conflicts: [] };
    }

    const team = await this.teamRepo.findOne({
      where: { id: user.team.id },
    });

    if (!team) return { has_conflict: false, conflicts: [] };

    // Membres actifs de l'équipe
    const members = await this.userRepo.find({
      where: { team: { id: team.id }, is_active: true },
    });

    // Congés approuvés + en attente de l'équipe sur la période
    const existingLeaves = await this.leaveRequestRepo
      .createQueryBuilder('lr')
      .leftJoinAndSelect('lr.user', 'user')
      .leftJoinAndSelect('lr.leave_type', 'leave_type')
      .where('lr.status IN (:...statuses)', {
        statuses: [
          LeaveStatus.APPROVED,
          LeaveStatus.PENDING_N1,
          LeaveStatus.PENDING_N2,
          LeaveStatus.PENDING_N3,
        ],
      })
      .andWhere('user.team_id = :teamId', { teamId: team.id })
      .andWhere('lr.user_id != :userId', { userId }) // exclure le demandeur
      .andWhere('lr.start_date <= :endDate', { endDate })
      .andWhere('lr.end_date >= :startDate', { startDate })
      .getMany();

    // Jours fériés sur la période
    const holidays = await this.holidayRepo.find({
      where: {
        date: Between(new Date(startDate), new Date(endDate)),
      },
    });
    const holidayDates = new Set(
      holidays.map(h => dayjs(h.date).format('YYYY-MM-DD'))
    );

    // Vérifier chaque jour de la période demandée
    const conflicts: { date: string; presence: number; minimum: number }[] = [];
    let current = dayjs(startDate);
    const end   = dayjs(endDate);

    while (current.isBefore(end.add(1, 'day'))) {
      const dateStr   = current.format('YYYY-MM-DD');
      const dayOfWeek = current.day();

      // Ignorer week-ends et jours fériés
      if (dayOfWeek === 0 || dayOfWeek === 6 || holidayDates.has(dateStr)) {
        current = current.add(1, 'day');
        continue;
      }

      // Compter les absents ce jour (sans le demandeur)
      const absentCount = existingLeaves.filter(lr =>
        !dayjs(dateStr).isBefore(dayjs(lr.start_date)) &&
        !dayjs(dateStr).isAfter(dayjs(lr.end_date))
      ).length;

      // Présents si la nouvelle demande est ajoutée
      const presenceIfApproved = members.length - absentCount - 1;

      if (presenceIfApproved < team.min_presence) {
        conflicts.push({
          date:     dateStr,
          presence: presenceIfApproved,
          minimum:  team.min_presence,
        });
      }

      current = current.add(1, 'day');
    }

    const has_conflict = conflicts.length > 0;

    return {
      has_conflict,
      conflicts,
      warning_message: has_conflict
        ? `Cette demande crée un conflit de présence sur ${conflicts.length} jour(s) : seuil minimum de ${team.min_presence} personne(s) non respecté.`
        : undefined,
    };
  }
}