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

/**
 * Repository für Worklog-Datenbankzugriff
 * Verwendet Sequelize ORM
 */
class WorklogRepository {
  /**
   * Alle Worklog-Einträge für einen Benutzer abrufen
   * @param {number} userId - ID des Benutzers
   * @param {Object} options - Filteroptionen
   * @returns {Promise<Array>} Liste der Worklog-Einträge
   */
  async findAllByUser(userId, options = {}) {
    const { Worklog } = database.getModels();
    const { limit, offset, orderBy = 'tstamp', order = 'DESC' } = options;
    
    return await Worklog.findAll({
      where: { user_id: userId },
      order: [[orderBy, order]],
      limit: limit || null,
      offset: offset || 0
    });
  }

  /**
   * Worklog-Eintrag anhand der ID abrufen
   * @param {number} id - Worklog-ID
   * @returns {Promise<Object|null>} Worklog-Eintrag oder null
   */
  async findById(id) {
    const { Worklog } = database.getModels();
    
    return await Worklog.findByPk(id);
  }

  /**
   * Neuen Worklog-Eintrag erstellen
   * @param {Object} worklogData - Worklog-Daten
   * @returns {Promise<Object>} Erstellter Worklog-Eintrag
   */
  async create(worklogData) {
    const { Worklog } = database.getModels();
    
    const { user_id, state, tstamp, relatedTo_id = null } = worklogData;
    
    return await Worklog.create({
      version: 0,
      user_id,
      state,
      tstamp: tstamp || new Date(),
      relatedTo_id
    });
  }

  /**
   * Worklog-Eintrag aktualisieren
   * @param {number} id - Worklog-ID
   * @param {Object} updateData - Zu aktualisierende Daten
   * @returns {Promise<Object>} Aktualisierter Worklog-Eintrag
   */
  async update(id, updateData) {
    const { Worklog } = database.getModels();
    
    const worklog = await Worklog.findByPk(id);
    if (!worklog) {
      throw new Error(`Worklog mit ID ${id} nicht gefunden`);
    }

    // Update-Daten vorbereiten
    const updates = {};
    
    if (updateData.state !== undefined) {
      updates.state = updateData.state;
    }
    if (updateData.tstamp !== undefined) {
      updates.tstamp = updateData.tstamp;
    }
    if (updateData.relatedTo_id !== undefined) {
      updates.relatedTo_id = updateData.relatedTo_id;
    }

    // Version inkrementieren
    updates.version = worklog.version + 1;

    await worklog.update(updates);
    return worklog;
  }

  /**
   * Worklog-Eintrag löschen
   * @param {number} id - Worklog-ID
   * @returns {Promise<boolean>} true wenn erfolgreich
   */
  async delete(id) {
    const { Worklog } = database.getModels();
    
    const deleted = await Worklog.destroy({
      where: { id }
    });
    
    return deleted > 0;
  }

  /**
   * Letzten Worklog-Eintrag für Benutzer finden
   * @param {number} userId - Benutzer-ID
   * @returns {Promise<Object|null>} Letzter Worklog-Eintrag oder null
   */
  async findLatestByUser(userId) {
    const { Worklog } = database.getModels();
    
    return await Worklog.findOne({
      where: { user_id: userId },
      order: [['tstamp', 'DESC'], ['id', 'DESC']],
      raw: true
    });
  }

  /**
   * Alle Worklog-Einträge für Benutzer finden
   * @param {number} userId - Benutzer-ID
   * @returns {Promise<Array>} Array von Worklog-Einträgen
   */
  async findByUser(userId) {
    const { Worklog } = database.getModels();
    
    return await Worklog.findAll({
      where: { user_id: userId },
      order: [['tstamp', 'ASC'], ['id', 'ASC']],
      raw: true
    });
  }

  /**
   * Letzten offenen Worklog-Eintrag für Benutzer finden
   * @param {number} userId - Benutzer-ID
   * @returns {Promise<Object|null>} Laufender Worklog-Eintrag oder null
   */
  async findRunningByUser(userId) {
    const { Worklog } = database.getModels();
    
    return await Worklog.findOne({
      where: {
        user_id: userId,
        relatedTo_id: null
      },
      order: [['tstamp', 'DESC']],
      include: [{
        model: Worklog,
        as: 'clockOut',
        required: false
      }]
    }).then(worklog => {
      // Nur zurückgeben wenn kein clockOut existiert
      if (worklog && !worklog.clockOut) {
        return worklog;
      }
      return null;
    });
  }

  /**
   * Vacation-Einträge für einen Benutzer in einem Datumsbereich abrufen
   * @param {number} userId - Benutzer-ID
   * @param {Date} startDate - Start-Datum
   * @param {Date} endDate - End-Datum
   * @returns {Promise<Array>} Array von Vacation-Einträgen mit expandierten Tagen
   */
  async getVacationsByUserInDateRange(userId, startDate, endDate) {
    const { Vacation } = database.getModels();
    
    try {
      // Hole alle Vacation-Einträge, die sich mit dem Datumsbereich überschneiden
      const vacations = await Vacation.findAll({
        where: {
          user_id: userId,
          [Op.or]: [
            {
              first_day: {
                [Op.between]: [startDate, endDate]
              }
            },
            {
              last_day: {
                [Op.between]: [startDate, endDate]
              }
            },
            {
              [Op.and]: [
                { first_day: { [Op.lte]: startDate } },
                { last_day: { [Op.gte]: endDate } }
              ]
            }
          ]
        },
        raw: true,
        order: [['first_day', 'ASC']]
      });

      // Expandiere jeden Vacation-Eintrag in einzelne Tage
      const expandedVacations = [];
      
      vacations.forEach(vac => {
        const first = new Date(vac.first_day);
        const last = new Date(vac.last_day);
        
        // Iteriere über alle Tage im Urlaubsbereich
        for (let d = new Date(first); d <= last; d.setDate(d.getDate() + 1)) {
          // Nur Tage hinzufügen, die im gewünschten Bereich liegen
          if (d >= startDate && d <= endDate) {
            // Überspringe Wochenenden (Samstag=6, Sonntag=0)
            const dayOfWeek = d.getDay();
            if (dayOfWeek !== 0 && dayOfWeek !== 6) {
              expandedVacations.push({
                date: new Date(d),
                half_day: vac.vacation_type === 1 ? 1 : 0,
                vacation_type: vac.vacation_type
              });
            }
          }
        }
      });

      return expandedVacations;
    } catch (error) {
      console.error('Fehler beim Abrufen der Vacation-Einträge:', error);
      return [];
    }
  }

  /**
   * Sick-Einträge für einen Benutzer in einem Datumsbereich abrufen
   * @param {number} userId - Benutzer-ID
   * @param {Date} startDate - Start-Datum
   * @param {Date} endDate - End-Datum
   * @returns {Promise<Array>} Array von Sick-Einträgen mit expandierten Tagen
   */
  async getSickByUserInDateRange(userId, startDate, endDate) {
    const { Sick, SickType } = database.getModels();
    
    try {
      // Hole alle Sick-Einträge, die sich mit dem Datumsbereich überschneiden
      const sickEntries = await Sick.findAll({
        where: {
          user_id: userId,
          [Op.or]: [
            {
              first_day: {
                [Op.between]: [startDate, endDate]
              }
            },
            {
              last_day: {
                [Op.between]: [startDate, endDate]
              }
            },
            {
              [Op.and]: [
                { first_day: { [Op.lte]: startDate } },
                { last_day: { [Op.gte]: endDate } }
              ]
            }
          ]
        },
        include: [{
          model: SickType,
          as: 'sickType',
          attributes: ['description']
        }],
        order: [['first_day', 'ASC']]
      });

      // Expandiere jeden Sick-Eintrag in einzelne Tage
      const expandedSick = [];
      
      sickEntries.forEach(sick => {
        const first = new Date(sick.first_day);
        const last = new Date(sick.last_day);
        const sickTypeDesc = sick.sickType?.description || 'self';
        
        // Iteriere über alle Tage im Krankheitsbereich
        for (let d = new Date(first); d <= last; d.setDate(d.getDate() + 1)) {
          // Nur Tage hinzufügen, die im gewünschten Bereich liegen
          if (d >= startDate && d <= endDate) {
            // Überspringe Wochenenden (Samstag=6, Sonntag=0)
            const dayOfWeek = d.getDay();
            if (dayOfWeek !== 0 && dayOfWeek !== 6) {
              expandedSick.push({
                date: new Date(d),
                sick_type: sickTypeDesc,
                sick_type_id: sick.sick_type_id
              });
            }
          }
        }
      });

      return expandedSick;
    } catch (error) {
      console.error('Fehler beim Abrufen der Sick-Einträge:', error);
      return [];
    }
  }

  /**
   * Timefix-Einträge für Worklog-IDs abrufen
   * @param {Array<number>} worklogIds - Array von Worklog-IDs
   * @returns {Promise<Map>} Map von worklog_id zu Timefix-Einträgen
   */
  async getTimefixesByWorklogIds(worklogIds) {
    if (!worklogIds || worklogIds.length === 0) {
      return new Map();
    }

    const { Timefix } = database.getModels();
    
    try {
      const timefixes = await Timefix.findAll({
        where: {
          worklog_id: {
            [Op.in]: worklogIds
          }
        },
        raw: true
      });

      // Gruppiere nach worklog_id
      const timefixMap = new Map();
      timefixes.forEach(fix => {
        if (!timefixMap.has(fix.worklog_id)) {
          timefixMap.set(fix.worklog_id, []);
        }
        timefixMap.get(fix.worklog_id).push(fix);
      });

      return timefixMap;
    } catch (error) {
      console.error('Fehler beim Abrufen der Timefix-Einträge:', error);
      return new Map();
    }
  }

  /**
   * Worklog-Paare für Benutzer in einem Datumsbereich finden
   * @param {number} userId - Benutzer-ID
   * @param {Date} startDate - Start-Datum
   * @param {Date} endDate - End-Datum
   * @returns {Promise<Array>} Array von Worklog-Paaren
   */
  async findPairsByUserInDateRange(userId, startDate, endDate) {
    const { Worklog } = database.getModels();
    
    try {
      const results = await Worklog.findAll({
        attributes: ['id', 'version', 'user_id', 'state', 'tstamp', 'relatedTo_id'],
        where: {
          user_id: userId,
          tstamp: {
            [Op.between]: [startDate, endDate]
          }
        },
        order: [['tstamp', 'ASC']],
        raw: true
      });

      // Gruppiere Start/Stop-Paare basierend auf dem action-Feld
      const pairs = [];
      const startEntries = {};
      
      results.forEach(entry => {
        // Parse state JSON
        let action = '';
        try {
          const state = typeof entry.state === 'string' ? JSON.parse(entry.state) : entry.state;
          action = state.action || state;
        } catch (e) {
          action = entry.state;
        }
        
        if (action === 'start work') {
          startEntries[entry.id] = entry;
        } else if (action === 'stop work' && entry.relatedTo_id) {
          const startEntry = startEntries[entry.relatedTo_id];
          if (startEntry) {
            pairs.push({
              id: startEntry.id,
              start_time: startEntry.tstamp,
              end_time: entry.tstamp,
              start_state: startEntry.state,
              end_state: entry.state
            });
            delete startEntries[entry.relatedTo_id];
          }
        }
      });

      // Füge laufende Einträge hinzu
      Object.values(startEntries).forEach(startEntry => {
        pairs.push({
          id: startEntry.id,
          start_time: startEntry.tstamp,
          end_time: null,
          start_state: startEntry.state,
          end_state: null
        });
      });

      return pairs;
    } catch (error) {
      console.error('Fehler beim Abrufen der Worklog-Paare:', error);
      return [];
    }
  }

  /**
   * Worklog-Einträge nach Datumsbereich abrufen
   * @param {number} userId - Benutzer-ID
   * @param {Date} startDate - Startdatum
   * @param {Date} endDate - Enddatum
   * @returns {Promise<Array>} Gefilterte Worklog-Einträge
   */
  async findByDateRange(userId, startDate, endDate) {
    const { Worklog } = database.getModels();
    
    return await Worklog.findAll({
      where: {
        user_id: userId,
        tstamp: {
          [Op.between]: [startDate, endDate]
        }
      },
      order: [['tstamp', 'ASC']] // Aufsteigend, damit Start vor Stop kommt
    });
  }

  /**
   * Zusammengehörige Worklog-Paare abrufen (Clock In/Out)
   * @param {number} userId - Benutzer-ID
   * @param {Object} options - Optionen
   * @returns {Promise<Array>} Liste von Worklog-Paaren
   */
  async findPairsByUser(userId, options = {}) {
    const { Worklog } = database.getModels();
    const sequelize = database.getSequelize();
    const { limit, offset } = options;

    // Raw Query für bessere Performance bei Pairs
    const query = `
      SELECT 
        w1.id as start_id,
        w1.tstamp as start_time,
        w1.state as start_state,
        w2.id as end_id,
        w2.tstamp as end_time,
        w2.state as end_state,
        TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp) as duration
      FROM worklog w1
      LEFT JOIN worklog w2 ON w2.relatedTo_id = w1.id
      WHERE w1.user_id = :userId
        AND w1.relatedTo_id IS NULL
      ORDER BY w1.tstamp DESC
      ${limit ? `LIMIT ${limit}` : ''}
      ${offset ? `OFFSET ${offset}` : ''}
    `;

    const results = await sequelize.query(query, {
      replacements: { userId },
      type: sequelize.constructor.QueryTypes.SELECT
    });

    return Array.isArray(results) ? results : [];
  }

  /**
   * Statistiken für Benutzer berechnen
   * @param {number} userId - Benutzer-ID
   * @returns {Promise<Object>} Statistik-Objekt
   */
  async getStatistics(userId) {
    const sequelize = database.getSequelize();
    
    const query = `
      SELECT 
        COUNT(DISTINCT w1.id) as total_entries,
        COUNT(DISTINCT w2.id) as completed_entries,
        COUNT(DISTINCT CASE WHEN w2.id IS NULL THEN w1.id END) as running_entries,
        COALESCE(SUM(TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp)), 0) as total_seconds
      FROM worklog w1
      LEFT JOIN worklog w2 ON w2.relatedTo_id = w1.id
      WHERE w1.user_id = :userId
        AND w1.relatedTo_id IS NULL
    `;

    const results = await sequelize.query(query, {
      replacements: { userId },
      type: sequelize.constructor.QueryTypes.SELECT
    });

    return (Array.isArray(results) && results[0]) ? results[0] : {
      total_entries: 0,
      completed_entries: 0,
      running_entries: 0,
      total_seconds: 0
    };
  }

  /**
   * Statistiken für heute abrufen
   * @param {number} userId - Benutzer-ID
   * @returns {Promise<Object>} Heutige Statistiken
   */
  async getTodayStatistics(userId) {
    const sequelize = database.getSequelize();
    
    const query = `
      SELECT 
        COUNT(DISTINCT w1.id) as entries,
        COALESCE(SUM(TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp)), 0) as seconds
      FROM worklog w1
      LEFT JOIN worklog w2 ON w2.relatedTo_id = w1.id
      WHERE w1.user_id = :userId
        AND w1.relatedTo_id IS NULL
        AND DATE(w1.tstamp) = CURDATE()
    `;

    const results = await sequelize.query(query, {
      replacements: { userId },
      type: sequelize.constructor.QueryTypes.SELECT
    });

    return (Array.isArray(results) && results[0]) ? results[0] : { entries: 0, seconds: 0 };
  }

  /**
   * Feiertage in einem Datumsbereich abrufen
   * @param {Date} startDate - Start-Datum
   * @param {Date} endDate - End-Datum
   * @returns {Promise<Array>} Array von Holiday-Einträgen mit Datum und Stunden
   */
  async getHolidaysInDateRange(startDate, endDate, userId = null) {
    const { Holiday, State, User } = database.getModels();
    
    try {
      // Hole User-Bundesland (falls userId angegeben)
      let userStateId = null;
      if (userId) {
        const user = await User.findByPk(userId, { 
          attributes: ['state_id'],
          raw: true 
        });
        userStateId = user?.state_id;
      }
      
      const holidays = await Holiday.findAll({
        where: {
          date: {
            [Op.between]: [startDate, endDate]
          }
        },
        include: [
          {
            model: State,
            as: 'states',
            attributes: ['id'],
            through: { attributes: [] },
            required: false
          }
        ],
        order: [['date', 'ASC']]
      });

      // Filtere nach User-Bundesland (falls userId angegeben)
      if (userId && userStateId) {
        return holidays.filter(h => {
          const holidayStates = h.states || [];
          const isFederal = holidayStates.length === 0;
          const appliesToUser = isFederal || holidayStates.some(s => s.id === userStateId);
          return appliesToUser;
        }).map(h => ({
          date: h.date,
          hours: h.hours,
          description: h.description
        }));
      }
      
      // Ohne userId: Alle Feiertage zurückgeben
      return holidays.map(h => ({
        date: h.date,
        hours: h.hours,
        description: h.description
      }));
    } catch (error) {
      console.error('Fehler beim Abrufen der Feiertage:', error);
      return [];
    }
  }
}

module.exports = new WorklogRepository();
