const database = require('../config/database');
const { Op } = require('sequelize');

/**
 * Service-Klasse für Kalender-Daten
 * Lädt alle Informationen für einen Monat
 */
class CalendarService {
  constructor() {
    this.defaultUserId = 1;
  }

  /**
   * Holt Kalenderdaten für einen Monat
   * @param {number} userId - Benutzer-ID
   * @param {number} year - Jahr
   * @param {number} month - Monat (1-12)
   * @returns {Promise<Object>} Kalenderdaten
   */
  async getCalendarMonth(userId, year, month) {
    const { Holiday, Sick, Vacation, User, State } = database.getModels();
    const sequelize = database.sequelize;
    
    // Hole User-Bundesland
    const user = await User.findByPk(userId, { 
      attributes: ['state_id'],
      raw: true 
    });
    const userStateId = user?.state_id;
    
    // Berechne Start- und End-Datum des Monats
    const firstDay = new Date(year, month - 1, 1);
    const lastDay = new Date(year, month, 0);
    
    // Erweitere den Bereich um die Tage aus dem vorherigen/nächsten Monat
    const startDate = new Date(firstDay);
    startDate.setDate(startDate.getDate() - ((startDate.getDay() + 6) % 7)); // Montag der ersten Woche
    
    const endDate = new Date(lastDay);
    const daysToAdd = (7 - ((endDate.getDay() + 6) % 7)) % 7;
    endDate.setDate(endDate.getDate() + daysToAdd); // Sonntag der letzten Woche
    
    const startDateStr = this._formatDate(startDate);
    const endDateStr = this._formatDate(endDate);
    
    // Hole alle Feiertage für diesen Zeitraum UND das Bundesland des Users
    // Feiertag gilt wenn: Kein State (Bundesfeiertag) ODER User's State ist dabei
    const holidays = await Holiday.findAll({
      where: {
        date: {
          [Op.between]: [startDateStr, endDateStr]
        }
      },
      include: [
        {
          model: State,
          as: 'states',
          attributes: ['id', 'state_name'],
          through: { attributes: [] },
          required: false // LEFT JOIN, damit Bundesfeiertage (ohne States) auch dabei sind
        }
      ]
    });
    
    const holidayMap = new Map();
    holidays.forEach(h => {
      // Feiertag gilt wenn: Keine States (Bundesfeiertag) ODER User's State ist dabei
      const holidayStates = h.states || [];
      const isFederal = holidayStates.length === 0;
      const appliesToUser = isFederal || holidayStates.some(s => s.id === userStateId);
      
      if (appliesToUser) {
        holidayMap.set(h.date, h.description);
      }
    });
    
    // Hole alle Krankheitstage
    const sickEntries = await Sick.findAll({
      where: {
        user_id: userId,
        [Op.or]: [
          {
            first_day: {
              [Op.between]: [startDateStr, endDateStr]
            }
          },
          {
            last_day: {
              [Op.between]: [startDateStr, endDateStr]
            }
          },
          {
            [Op.and]: [
              { first_day: { [Op.lte]: startDateStr } },
              { last_day: { [Op.gte]: endDateStr } }
            ]
          }
        ]
      },
      raw: true
    });
    
    // Erstelle Set mit allen Krankheitstagen
    const sickDays = new Set();
    sickEntries.forEach(sick => {
      const start = new Date(Math.max(new Date(sick.first_day + 'T00:00:00'), new Date(startDateStr + 'T00:00:00')));
      const end = new Date(Math.min(new Date(sick.last_day + 'T00:00:00'), new Date(endDateStr + 'T00:00:00')));
      
      for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
        sickDays.add(this._formatDate(d));
      }
    });
    
    // Hole alle Urlaubstage
    const vacationEntries = await Vacation.findAll({
      where: {
        user_id: userId,
        [Op.or]: [
          {
            first_day: {
              [Op.between]: [startDateStr, endDateStr]
            }
          },
          {
            last_day: {
              [Op.between]: [startDateStr, endDateStr]
            }
          },
          {
            [Op.and]: [
              { first_day: { [Op.lte]: startDateStr } },
              { last_day: { [Op.gte]: endDateStr } }
            ]
          }
        ]
      },
      raw: true
    });
    
    // Erstelle Map mit Urlaubstagen (und ob es halbe Tage sind)
    const vacationDays = new Map();
    vacationEntries.forEach(vac => {
      const start = new Date(Math.max(new Date(vac.first_day + 'T00:00:00'), new Date(startDateStr + 'T00:00:00')));
      const end = new Date(Math.min(new Date(vac.last_day + 'T00:00:00'), new Date(endDateStr + 'T00:00:00')));
      
      for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
        vacationDays.set(this._formatDate(d), vac.half_day === 1);
      }
    });
    
    // Hole Arbeitszeiten über den TimeEntryService (ähnlich wie WeekOverview)
    // Wir verwenden eine vereinfachte Query die alle worklog-Einträge holt
    const workQuery = `
      SELECT DATE(tstamp) as work_date, tstamp, state, relatedTo_id, id
      FROM worklog
      WHERE user_id = :userId
        AND DATE(tstamp) BETWEEN :startDate AND :endDate
      ORDER BY tstamp ASC
    `;
    
    const workEntries = await sequelize.query(workQuery, {
      replacements: {
        userId,
        startDate: startDateStr,
        endDate: endDateStr
      },
      type: sequelize.QueryTypes.SELECT
    });
    
    // Gruppiere nach Datum und berechne Arbeitszeit
    const workMap = new Map();
    const entriesByDate = new Map();
    
    workEntries.forEach(entry => {
      if (!entriesByDate.has(entry.work_date)) {
        entriesByDate.set(entry.work_date, []);
      }
      entriesByDate.get(entry.work_date).push(entry);
    });
    
    // Berechne für jeden Tag die Arbeitszeit (Nettoarbeitszeit mit Pausenabzug)
    entriesByDate.forEach((entries, date) => {
      let totalWorkMinutes = 0;
      let totalPauseMinutes = 0;
      
      // Parse state für alle Einträge
      const parsedEntries = entries.map(entry => {
        let state = entry.state;
        if (typeof state === 'string' && state.startsWith('{')) {
          try {
            state = JSON.parse(state);
          } catch (e) {
            // ignore
          }
        }
        const action = state?.action || state;
        return { ...entry, action };
      });
      
      // Finde alle start work -> stop work Paare und berechne Bruttoarbeitszeit
      parsedEntries.forEach(entry => {
        if (entry.action === 'stop work') {
          // Finde das zugehörige start work über relatedTo_id
          const startEntry = parsedEntries.find(e => e.id === entry.relatedTo_id && e.action === 'start work');
          
          if (startEntry) {
            const start = new Date(startEntry.tstamp);
            const end = new Date(entry.tstamp);
            const minutes = (end - start) / (1000 * 60);
            totalWorkMinutes += minutes;
            
            // Berechne Pausen innerhalb dieses Arbeitsblocks
            const pauseEntries = parsedEntries.filter(e => 
              new Date(e.tstamp) > start && 
              new Date(e.tstamp) < end &&
              (e.action === 'start pause' || e.action === 'stop pause')
            );
            
            // Paare start pause -> stop pause
            pauseEntries.forEach(pauseEntry => {
              if (pauseEntry.action === 'stop pause') {
                const pauseStart = pauseEntries.find(pe => 
                  pe.id === pauseEntry.relatedTo_id && 
                  pe.action === 'start pause'
                );
                
                if (pauseStart) {
                  const pStart = new Date(pauseStart.tstamp);
                  const pEnd = new Date(pauseEntry.tstamp);
                  const pauseMins = (pEnd - pStart) / (1000 * 60);
                  totalPauseMinutes += pauseMins;
                }
              }
            });
          }
        }
      });
      
      const netMinutes = totalWorkMinutes - totalPauseMinutes;
      
      if (netMinutes > 0) {
        workMap.set(date, netMinutes / 60); // Convert to hours
      }
    });
    
    // Erstelle Kalendertage
    const days = [];
    const currentDate = new Date(startDate);
    
    while (currentDate <= endDate) {
      const dateStr = this._formatDate(currentDate);
      const day = currentDate.getDate();
      const isCurrentMonth = currentDate.getMonth() === (month - 1);
      const isToday = this._isToday(currentDate);
      
      const dayData = {
        date: dateStr,
        day,
        isCurrentMonth,
        isToday,
        holiday: holidayMap.get(dateStr) || null,
        sick: sickDays.has(dateStr),
        vacation: vacationDays.has(dateStr) ? (vacationDays.get(dateStr) ? 'half' : 'full') : null,
        workedHours: workMap.get(dateStr) || null
      };
      
      days.push(dayData);
      currentDate.setDate(currentDate.getDate() + 1);
    }
    
    // Gruppiere in Wochen
    const weeks = [];
    for (let i = 0; i < days.length; i += 7) {
      weeks.push(days.slice(i, i + 7));
    }
    
    return {
      year,
      month,
      weeks
    };
  }

  /**
   * Formatiert ein Datum als YYYY-MM-DD
   */
  _formatDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
  }

  /**
   * Prüft ob ein Datum heute ist
   */
  _isToday(date) {
    const today = new Date();
    return date.getDate() === today.getDate() &&
           date.getMonth() === today.getMonth() &&
           date.getFullYear() === today.getFullYear();
  }
}

module.exports = new CalendarService();

