const express = require('express');
const session = require('express-session');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const nodemailer = require('nodemailer');
const { Pool } = require('pg');
const path = require('path');
const moment=require('moment')
require('dotenv').config();

const app = express();
const port = process.env.PORT;

// Middleware
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.set('view engine', 'ejs');
app.use(express.urlencoded({ extended: false }));

// Session-Konfiguration
app.use(session({
  secret: process.env.SESSIONSECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 1800000 }
}));

// Authentifizierungs-Middleware
const requireAuth = (req, res, next) => {
  if (!req.session.userId) {
    return res.redirect('/login');
  }
  next();
};

const requireAdmin = (req, res, next) => {
  if (req.session.role !== 'admin') {
    return res.status(403).send('Access denied');
  }
  next();
};

// Email-Konfiguration
const transporter = nodemailer.createTransport({
  host: process.env.MAILHOST,
  port: 465,
  secure: true,
  auth: {
    user: process.env.MAILUSER,
    pass: process.env.MAILPASS
  }
});

// Datenbankverbindung
const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

// Altersberechnung
const calculateAge = (birthdate) => {
  const today = new Date();
  const birthDate = new Date(birthdate);
  let age = today.getFullYear() - birthDate.getFullYear();
  const m = today.getMonth() - birthDate.getMonth();
  if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
    age--;
  }
  return age;
};

// Datumsformatierung
const formatDate = (date) => {
  const d = new Date(date);
  const day = String(d.getDate()).padStart(2, '0');
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const year = d.getFullYear();
  return `${day}.${month}.${year}`;
};

// Funktion zum Laden des aktuellen oder letzten Trainings
const getTraining = async (selectedDate) => {
  const today = selectedDate ? new Date(selectedDate) : new Date();
  const dayOfWeek = today.getDay();

  // Wenn heute Donnerstag ist
  if (dayOfWeek === 4) {
    const result = await pool.query(`
      SELECT tr.*, g1.name AS geraet_riege_1_name, g2.name AS geraet_riege_2_name, 
             g3.name AS geraet_riege_3_name, g4.name AS geraet_riege_4_name, 
             g5.name AS geraet_riege_5_name, 
             t1.name AS aufwaermleiter_name, t2.name AS spielleiter_name,
             sp1.name AS aufwaermen_name, sp2.name AS spiel_name
      FROM trainings tr
      LEFT JOIN geraete g1 ON tr.geraet_riege_1 = g1.id
      LEFT JOIN geraete g2 ON tr.geraet_riege_2 = g2.id
      LEFT JOIN geraete g3 ON tr.geraet_riege_3 = g3.id
      LEFT JOIN geraete g4 ON tr.geraet_riege_4 = g4.id
      LEFT JOIN geraete g5 ON tr.geraet_riege_5 = g5.id
      LEFT JOIN teilnehmende t1 ON tr.aufwaermleiter = t1.id
      LEFT JOIN teilnehmende t2 ON tr.spielleiter = t2.id
      LEFT JOIN spiele sp1 ON tr.aufwaermen = sp1.id
      LEFT JOIN spiele sp2 ON tr.spiel = sp2.id
      WHERE tr.datum = $1
      LIMIT 1
    `, [today]);

    return result.rows[0];
  } else {
    const result = await pool.query(`
      SELECT tr.*, g1.name AS geraet_riege_1_name, g2.name AS geraet_riege_2_name, 
             g3.name AS geraet_riege_3_name, g4.name AS geraet_riege_4_name, 
             g5.name AS geraet_riege_5_name, 
             t1.name AS aufwaermleiter_name, t2.name AS spielleiter_name,
             sp1.name AS aufwaermen_name, sp2.name AS spiel_name
      FROM trainings tr
      LEFT JOIN geraete g1 ON tr.geraet_riege_1 = g1.id
      LEFT JOIN geraete g2 ON tr.geraet_riege_2 = g2.id
      LEFT JOIN geraete g3 ON tr.geraet_riege_3 = g3.id
      LEFT JOIN geraete g4 ON tr.geraet_riege_4 = g4.id
      LEFT JOIN geraete g5 ON tr.geraet_riege_5 = g5.id
      LEFT JOIN teilnehmende t1 ON tr.aufwaermleiter = t1.id
      LEFT JOIN teilnehmende t2 ON tr.spielleiter = t2.id
      LEFT JOIN spiele sp1 ON tr.aufwaermen = sp1.id
      LEFT JOIN spiele sp2 ON tr.spiel = sp2.id
      WHERE tr.datum <= $1
      ORDER BY tr.datum DESC
      LIMIT 1
    `, [today]);

    return result.rows[0];
  }
};

// Funktion zum Laden aller Spiele
const getAllSpiele = async () => {
    const result = await pool.query(`
      SELECT s.id, s.name, 
             COALESCE(EXTRACT(EPOCH FROM (NOW() - MAX(tr.datum))) / 604800, EXTRACT(EPOCH FROM (NOW() - '1970-01-01'::date)) / 604800) AS weeks_since_last
      FROM spiele s
      LEFT JOIN trainings tr ON s.id = tr.spiel
      GROUP BY s.id
      ORDER BY weeks_since_last ASC;
    `);
    return result.rows;
  };
  
  // Funktion zum Hinzufügen eines neuen Spiels, falls es noch nicht existiert
  const addNewSpiel = async (spielName) => {
    try {
      const result = await pool.query('INSERT INTO spiele (name) VALUES ($1) RETURNING id', [spielName]);
      return result.rows[0].id;
    } catch (error) {
      console.error('Error adding new spiel:', error);
      throw error;
    }
  };

// Funktion zum Laden der vier Leute, die am längsten nicht Aufwärmen bzw. Spiel geleitet haben
// Funktion zum Laden der vier Leute, die am längsten nicht Aufwärmen geleitet haben
const getCandidatesForAufwaermleiter = async () => {
  const result = await pool.query(`
  SELECT t.id, t.name, 
  COALESCE(EXTRACT(EPOCH FROM (NOW() - MAX(tr.datum))) / 604800, EXTRACT(EPOCH FROM (NOW() - '1970-01-01'::date)) / 604800) AS weeks_since_last
FROM teilnehmende t
LEFT JOIN trainings tr ON t.id = tr.aufwaermleiter
WHERE t.helfer = true
GROUP BY t.id
ORDER BY weeks_since_last DESC;
  `);
  return result.rows;
};

// Funktion zum Laden der vier Leute, die am längsten nicht Spiel geleitet haben
const getCandidatesForSpielleiter = async () => {
  const result = await pool.query(`
    SELECT t.id, t.name, 
           COALESCE(EXTRACT(EPOCH FROM (NOW() - MAX(tr.datum))) / 604800, EXTRACT(EPOCH FROM (NOW() - '1970-01-01'::date)) / 604800) AS weeks_since_last
    FROM teilnehmende t
    LEFT JOIN trainings tr ON t.id = tr.spielleiter
    WHERE t.helfer = true
    GROUP BY t.id
    ORDER BY weeks_since_last DESC;
  `);
  return result.rows;
};

// Funktion zum Laden der Riegenteilnehmer absteigend nach Alter sortiert
const getRiegenMitgliederSortedByAge = async () => {
    const result = await pool.query(`
      SELECT r.riegennummer, t.name, t.alter
      FROM riegen r
      JOIN teilnehmende t ON r.fremdid_teilnehmende = t.id
      ORDER BY r.riegennummer, t.alter DESC
    `);
    return result.rows;
  };

// Registrierung
app.post('/register', async (req, res) => {
  const { username, password } = req.body;
  try {
    const hashedPassword = await bcrypt.hash(password, 10);
    await pool.query('INSERT INTO users (username, password) VALUES ($1, $2)', [username, hashedPassword]);
    res.redirect('/login');
  } catch (error) {
    console.error('Error registering user:', error);
    res.status(500).send('Internal Server Error');
  }
});

// Login
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  try {
    const userResult = await pool.query('SELECT * FROM users WHERE username = $1', [username]);
    if (userResult.rows.length > 0) {
      const user = userResult.rows[0];
      const match = await bcrypt.compare(password, user.password);
      if (match) {
        req.session.userId = user.id;
        req.session.role=user.role;
        res.redirect('/');
      } else {
        res.redirect('/login');
      }
    } else {
      res.redirect('/login');
    }
  } catch (error) {
    console.error('Error logging in:', error);
    res.status(500).send('Internal Server Error');
  }
});

// Logout
app.post('/logout', (req, res) => {
  req.session.destroy(err => {
    if (err) {
      return res.status(500).send('Internal Server Error');
    }
    res.redirect('/login');
  });
});

// Benutzer freischalten (nur Admin)
app.post('/activate', requireAuth, requireAdmin, async (req, res) => {
  const { userId } = req.body;
  try {
    await pool.query('UPDATE users SET is_active = TRUE WHERE id = $1', [userId]);
    res.redirect('/admin');
  } catch (error) {
    console.error('Error activating user:', error);
    res.status(500).send('Internal Server Error');
  }
});

// Passwort-Zurücksetzung anfordern
app.post('/forgot-password', async (req, res) => {
  const { email } = req.body;
  console.log ( email );
  try {
    const userResult = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
    if (userResult.rows.length > 0) {
      const user = userResult.rows[0];
      const token = crypto.randomBytes(20).toString('hex');
      const resetLink = `http://tkd.boergmann.it/reset-password/${token}`;
      console.log(resetLink);
      await pool.query('UPDATE users SET reset_password_token = $1, reset_password_expires = $2 WHERE id = $3', [token, Date.now() + 3600, user.id]);

      const mailOptions = {
        to: user.email,
        from: 'admin@boergmann.it',
        subject: 'Password Reset',
        text: `Click the following link to reset your password: ${resetLink}`
      };

      transporter.sendMail(mailOptions, (err) => {
        if (err) {
          console.error('Error sending email:', err);
          res.status(500).send('Error sending email');
        } else {
          res.send('Password reset link sent');
        }
      });
    } else {
      res.status(400).send('Email not found');
    }
  } catch (error) {
    console.error('Error in forgot-password:', error);
    res.status(500).send('Internal Server Error');
  }
});

app.get('/forgot-password', async (req, res) => {
  res.render('forgot-password',  {session: req.session, token : '123'})
})

// Passwort zurücksetzen
app.get('/reset-password/:token', async (req, res) => {
  const { token } = req.params;
  try {
    const userResult = await pool.query('SELECT * FROM users WHERE reset_password_token = $1 AND reset_password_expires > $2', [token, Date.now()]);
    if (userResult.rows.length > 0) {
      res.render('reset-password', { token }); // Stelle sicher, dass es eine reset-password.ejs gibt
    } else {
      res.status(400).send('Password reset token is invalid or has expired');
    }
  } catch (error) {
    console.error('Error in reset-password:', error);
    res.status(500).send('Internal Server Error');
  }
});

app.post('/reset-password/:token', async (req, res) => {
  const { token } = req.params;
  const { password } = req.body;
  try {
    const userResult = await pool.query('SELECT * FROM users WHERE reset_password_token = $1 AND reset_password_expires > $2', [token, Date.now()]);
    if (userResult.rows.length > 0) {
      const user = userResult.rows[0];
      const hashedPassword = await bcrypt.hash(password, 10);
      await pool.query('UPDATE users SET password = $1, reset_password_token = NULL, reset_password_expires = NULL WHERE id = $2', [hashedPassword, user.id]);
      res.redirect('/login');
    } else {
      res.status(400).send('Password reset token is invalid or has expired');
    }
  } catch (error) {
    console.error('Error in reset-password:', error);
    res.status(500).send('Internal Server Error');
  }
});

// Profilseite
app.get('/profile', requireAuth, (req, res) => {
  res.render('profile', { session: req.session}); // Stelle sicher, dass es eine profile.ejs gibt
});



app.post('/profile', requireAuth, async (req, res) => {
  const { email, password } = req.body;
  try {
    if (email) {
      await pool.query('UPDATE users SET email = $1 WHERE id = $2', [email, req.session.userId]);
    }
    if (password) {
      const hashedPassword = await bcrypt.hash(password, 10);
      await pool.query('UPDATE users SET password = $1 WHERE id = $2', [hashedPassword, req.session.userId]);
    }
    res.redirect('/profile');
  } catch (error) {
    console.error('Error updating profile:', error);
    res.status(500).send('Internal Server Error');
  }
});

app.post('/update-training', async (req, res) => {
  const { trainingId, type, spielId } = req.body;

  try {
    if (type === 'spiel') {
      await pool.query('UPDATE trainings SET spiel = $1 WHERE id = $2', [spielId, trainingId]);
    } else if (type === 'aufwaermen') {
      await pool.query('UPDATE trainings SET aufwaermen = $1 WHERE id = $2', [spielId, trainingId]);
    }
    res.redirect('/');
  } catch (err) {
    console.error(err);
    res.send("Error " + err);
  }
});

app.post('/update-leader', async (req, res) => {
  const { trainingId, type, leaderId } = req.body;

  try {
    if (type === 'aufwaermleiter') {
      await pool.query('UPDATE trainings SET aufwaermleiter = $1 WHERE id = $2', [leaderId, trainingId]);
    } else if (type === 'spielleiter') {
      await pool.query('UPDATE trainings SET spielleiter = $1 WHERE id = $2', [leaderId, trainingId]);
    }
    res.redirect('/');
  } catch (err) {
    console.error(err);
    res.send("Error " + err);
  }
});

// Admin-Seite
app.get('/admin', requireAuth, requireAdmin, async (req, res) => {
  const usersResult = await pool.query('SELECT * FROM users WHERE is_active = FALSE');
  res.render('admin', { users: usersResult.rows, session: req.session }); // Stelle sicher, dass es eine admin.ejs gibt
});

// Teilnehmer_innen
app.get('/', requireAuth, async (req, res) => {
  try {
    const selectedDate = req.query.date;
	
    const training = await getTraining(selectedDate);

    const trainingsResult = await pool.query('SELECT datum FROM trainings ORDER BY datum ASC');
    const trainingsDates = trainingsResult.rows.map(tr => ({
      datum: formatDate(tr.datum),
      rawDatum: tr.datum
    }));

    if (training) {
      training.datum = formatDate(training.datum);
    }

    const aufwaermleiterCandidates = await getCandidatesForAufwaermleiter();
    const spielleiterCandidates = await getCandidatesForSpielleiter();
    const spielCandidates = await getAllSpiele();
    const aufwaermenCandidates = await getAllSpiele();

    res.render('trainings', {
      training,
      trainingsDates,
      selectedDate: training.datum,
      aufwaermleiterCandidates,
      spielleiterCandidates,
      aufwaermenCandidates, // Übergeben der Kandidaten für Aufwärmleiter
      spielCandidates, // Übergeben der Spiele
      session: req.session
    });
  } catch (err) {
    console.error(err);
    res.send("Error " + err);
  }
});

app.get('/riege', requireAuth, async (req, res) => {
  try {
    // Abrufen der Riegendaten einschließlich der Teilnehmer und deren Altersberechnung
    const result = await pool.query(`
      SELECT r.riegennummer, t.id, t.name, t.geburtsdatum, r.helfer
      FROM riegen r
      JOIN teilnehmende t ON r.fremdID_Teilnehmende = t.id
      ORDER BY r.riegennummer, t.geburtsdatum ASC
    `);

    // Gruppieren der Riegenteilnehmer nach Riegennummer
    const riegen = {};
    result.rows.forEach(row => {
      const age = calculateAge(row.geburtsdatum);
      if (!riegen[row.riegennummer]) {
        riegen[row.riegennummer] = [];
      }
      riegen[row.riegennummer].push({
        id: row.id,
        name: row.name,
        age: age,
        helfer: row.helfer,
      });
    });

    res.render('riegen', { riegen: riegen, session: req.session });
  } catch (error) {
    console.error('Error fetching riegen:', error);
    res.status(500).send('Internal Server Error');
  }
});

app.get('/teilnehmer', requireAuth, async (req, res) => {
  try {
    const teilnehmendeResult = await pool.query('SELECT * FROM teilnehmende');

    const teilnehmende = teilnehmendeResult.rows.map(t => ({
      ...t,
      age: calculateAge(t.geburtsdatum)
    })).sort((a, b) => b.age - a.age);

    res.render('teilnehmer', { teilnehmende, session: req.session });
  } catch (err) {
    console.error(err);
    res.send("Error " + err);
  }
});

app.get('/mitglied/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  try {
    const userResult = await pool.query('SELECT * FROM teilnehmende WHERE id = $1', [id]);
    if (userResult.rows.length > 0) {
      const mitglied = userResult.rows.map(t => ({
        ...t,
        age: calculateAge(t.geburtsdatum)
      }))
      res.render('mitglied', { 
        id,
       mitglied: mitglied[0],
       session: req.session
      });
    } else {
      res.status(400).send('Mitglied existiert nicht');
    }
  } catch (error) {
    console.error('Error in Mitglied:', error);
    res.status(500).send('Internal Server Error');
  }
});

// Login und Registrierung anzeigen
app.get('/login', (req, res) => {
  res.render('login', {session: req.session}); // Stelle sicher, dass es eine login.ejs gibt
});

app.get('/register', (req, res) => {
  res.render('register'); // Stelle sicher, dass es eine register.ejs gibt
});

const server = app.listen(port, '0.0.0.0', () => {
    console.log(`Server is running on http://localhost:${port}/`);
  });
