import { DiaryDate, Member, Participant, TrainingGroup } from '../models/index.js';
import { Op } from 'sequelize';

function getIsoWeekKey(dateLike) {
  const date = new Date(dateLike);
  if (Number.isNaN(date.getTime())) {
    return null;
  }

  const utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
  const dayNum = utcDate.getUTCDay() || 7;
  utcDate.setUTCDate(utcDate.getUTCDate() + 4 - dayNum);
  const yearStart = new Date(Date.UTC(utcDate.getUTCFullYear(), 0, 1));
  const weekNum = Math.ceil((((utcDate - yearStart) / 86400000) + 1) / 7);
  return `${utcDate.getUTCFullYear()}-${String(weekNum).padStart(2, '0')}`;
}

function countMissedTrainingWeeks(trainingDates, lastTrainingDate) {
  const lastTrainingTs = lastTrainingDate ? new Date(lastTrainingDate).getTime() : null;
  const weeks = new Set();

  for (const trainingDate of trainingDates) {
    const ts = new Date(trainingDate).getTime();
    if (Number.isNaN(ts)) {
      continue;
    }
    if (lastTrainingTs !== null && ts <= lastTrainingTs) {
      continue;
    }
    const key = getIsoWeekKey(trainingDate);
    if (key) {
      weeks.add(key);
    }
  }

  return weeks.size;
}

function getMonthKey(dateLike) {
  const date = new Date(dateLike);
  if (Number.isNaN(date.getTime())) {
    return null;
  }

  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
}

function buildLastMonthsTemplate(monthCount = 12) {
  const months = [];
  const cursor = new Date();
  cursor.setDate(1);

  for (let i = monthCount - 1; i >= 0; i -= 1) {
    const monthDate = new Date(cursor.getFullYear(), cursor.getMonth() - i, 1);
    months.push({
      key: getMonthKey(monthDate),
      year: monthDate.getFullYear(),
      month: monthDate.getMonth(),
      label: `${String(monthDate.getMonth() + 1).padStart(2, '0')}.${monthDate.getFullYear()}`,
      trainingCount: 0,
      participantCount: 0,
      averageParticipants: 0,
    });
  }

  return months;
}

class TrainingStatsService {
  async getTrainingStats(clubIdRaw) {
    const clubId = parseInt(clubIdRaw, 10);
    if (!Number.isFinite(clubId)) {
      const err = new Error('Ungültige clubId');
      err.status = 400;
      throw err;
    }

    const now = new Date();
    const twelveMonthsAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
    const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate());

    const members = await Member.findAll({
      where: { active: true, clubId },
      include: [{
        model: TrainingGroup,
        as: 'trainingGroups',
        attributes: ['id', 'name'],
        through: { attributes: [] },
        required: false
      }]
    });

    const trainingsCount12Months = await DiaryDate.count({
      where: {
        clubId,
        date: { [Op.gte]: twelveMonthsAgo }
      }
    });

    const trainingsCount3Months = await DiaryDate.count({
      where: {
        clubId,
        date: { [Op.gte]: threeMonthsAgo }
      }
    });

    const stats = [];
    const trainingDates12Months = await DiaryDate.findAll({
      where: {
        clubId,
        date: { [Op.gte]: twelveMonthsAgo }
      },
      attributes: ['date'],
      order: [['date', 'DESC']]
    });
    const trainingDateValues12Months = trainingDates12Months.map(entry => entry.date);

    for (const member of members) {
      const participation12Months = await Participant.count({
        include: [{
          model: DiaryDate,
          as: 'diaryDate',
          where: {
            clubId,
            date: { [Op.gte]: twelveMonthsAgo }
          }
        }],
        where: { memberId: member.id, attendanceStatus: 'present' }
      });

      const participation3Months = await Participant.count({
        include: [{
          model: DiaryDate,
          as: 'diaryDate',
          where: {
            clubId,
            date: { [Op.gte]: threeMonthsAgo }
          }
        }],
        where: { memberId: member.id, attendanceStatus: 'present' }
      });

      const participationTotal = await Participant.count({
        include: [{
          model: DiaryDate,
          as: 'diaryDate',
          where: { clubId }
        }],
        where: { memberId: member.id, attendanceStatus: 'present' }
      });

      const trainingDetails = await Participant.findAll({
        include: [{
          model: DiaryDate,
          as: 'diaryDate',
          where: { clubId }
        }],
        where: { memberId: member.id, attendanceStatus: 'present' },
        order: [['diaryDate', 'date', 'DESC']],
        limit: 50
      });

      const formattedTrainingDetails = trainingDetails.map((participation) => ({
        id: participation.id,
        date: participation.diaryDate.date,
        activityName: 'Training',
        startTime: '--:--',
        endTime: '--:--'
      }));

      const lastTrainingDate = trainingDetails.length ? trainingDetails[0].diaryDate.date : null;
      const lastTrainingTs = lastTrainingDate ? new Date(lastTrainingDate).getTime() : 0;
      const missedTrainingWeeks = countMissedTrainingWeeks(trainingDateValues12Months, lastTrainingDate);
      const notInTraining = missedTrainingWeeks >= 6;

      stats.push({
        id: member.id,
        firstName: member.firstName,
        lastName: member.lastName,
        birthDate: member.birthDate,
        ttr: member.ttr ?? null,
        qttr: member.qttr ?? null,
        trainingGroups: Array.isArray(member.trainingGroups)
          ? member.trainingGroups.map((group) => ({ id: group.id, name: group.name }))
          : [],
        participation12Months,
        participation3Months,
        participationTotal,
        participationRate12Months: trainingsCount12Months > 0 ? participation12Months / trainingsCount12Months : 0,
        lastTraining: lastTrainingDate,
        lastTrainingTs,
        missedTrainingWeeks,
        notInTraining,
        trainingDetails: formattedTrainingDetails
      });
    }

    stats.sort((a, b) => b.participationTotal - a.participationTotal);

    const trainingDays = await DiaryDate.findAll({
      where: {
        clubId,
        date: { [Op.gte]: twelveMonthsAgo }
      },
      include: [{
        model: Participant,
        as: 'participantList',
        attributes: ['id', 'memberId', 'attendanceStatus'],
        include: [{
          model: Member,
          as: 'member',
          attributes: ['id', 'firstName', 'lastName']
        }]
      }],
      order: [['date', 'DESC']]
    });

    const formattedTrainingDays = trainingDays.map((day) => ({
      id: day.id,
      date: day.date,
      participantCount: day.participantList
        ? day.participantList.filter((participant) => !participant.attendanceStatus || participant.attendanceStatus === 'present').length
        : 0,
      participants: day.participantList
        ? day.participantList
            .filter((participant) => !participant.attendanceStatus || participant.attendanceStatus === 'present')
            .map((participant) => ({
              id: participant.member?.id || participant.memberId,
              firstName: participant.member?.firstName || '',
              lastName: participant.member?.lastName || '',
            }))
            .filter((participant) => Number.isFinite(Number(participant.id)))
            .sort((a, b) => {
              const lastCompare = String(a.lastName || '').localeCompare(String(b.lastName || ''), 'de', { sensitivity: 'base' });
              if (lastCompare !== 0) return lastCompare;
              return String(a.firstName || '').localeCompare(String(b.firstName || ''), 'de', { sensitivity: 'base' });
            })
        : []
    }));

    const totalParticipants12Months = formattedTrainingDays.reduce((sum, day) => sum + (day.participantCount || 0), 0);
    const averageParticipants12Months = trainingsCount12Months > 0 ? totalParticipants12Months / trainingsCount12Months : 0;
    const attendanceRate12Months = (members.length > 0 && trainingsCount12Months > 0)
      ? (totalParticipants12Months / (members.length * trainingsCount12Months)) * 100
      : 0;
    const inactiveMembersCount = stats.filter((member) => member.notInTraining).length;
    const bestTrainingDay = formattedTrainingDays.reduce((best, day) => {
      if (!best || (day.participantCount || 0) > (best.participantCount || 0)) {
        return day;
      }
      return best;
    }, null);

    const weekdayNames = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
    const weekdayBuckets = weekdayNames.map((name, index) => ({
      weekday: name,
      weekdayIndex: index,
      trainingCount: 0,
      participantCount: 0,
      averageParticipants: 0,
    }));

    const monthlyTrend = buildLastMonthsTemplate(12);
    const monthlyTrendMap = new Map(monthlyTrend.map((entry) => [entry.key, entry]));

    for (const day of formattedTrainingDays) {
      const date = new Date(day.date);
      if (Number.isNaN(date.getTime())) {
        continue;
      }

      const weekdayEntry = weekdayBuckets[date.getDay()];
      weekdayEntry.trainingCount += 1;
      weekdayEntry.participantCount += day.participantCount || 0;

      const monthKey = getMonthKey(day.date);
      if (monthKey && monthlyTrendMap.has(monthKey)) {
        const monthEntry = monthlyTrendMap.get(monthKey);
        monthEntry.trainingCount += 1;
        monthEntry.participantCount += day.participantCount || 0;
      }
    }

    weekdayBuckets.forEach((entry) => {
      entry.averageParticipants = entry.trainingCount > 0 ? entry.participantCount / entry.trainingCount : 0;
    });

    monthlyTrend.forEach((entry) => {
      entry.averageParticipants = entry.trainingCount > 0 ? entry.participantCount / entry.trainingCount : 0;
    });

    const memberDistribution = {
      highlyActive: stats.filter((member) => member.participationRate12Months >= 0.75).length,
      regular: stats.filter((member) => member.participationRate12Months >= 0.4 && member.participationRate12Months < 0.75).length,
      occasional: stats.filter((member) => member.participationRate12Months > 0 && member.participationRate12Months < 0.4).length,
      inactive: stats.filter((member) => member.participation12Months === 0).length,
    };

    return {
      members: stats,
      trainingsCount12Months,
      trainingsCount3Months,
      trainingDays: formattedTrainingDays,
      overview: {
        activeMembersCount: members.length,
        totalParticipants12Months,
        averageParticipants12Months,
        attendanceRate12Months,
        inactiveMembersCount,
        bestTrainingDay,
      },
      weekdayStats: weekdayBuckets.filter((entry) => entry.trainingCount > 0),
      monthlyTrend,
      memberDistribution,
    };
  }
}

export default new TrainingStatsService();
