import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
import { createServer } from 'http';
import https from 'https';
import fs from 'fs';
import { exec } from 'child_process';
import sequelize from './database.js';
import cors from 'cors';
import { initializeSocketIO } from './services/socketService.js';
import {
  User, Log, Club, UserClub, Member, DiaryDate, Participant, Activity, MemberNote,
  DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag,
  PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, ClubTeam, TeamDocument, Group,
  GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
  TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis, ClickTtAccount, MyTischtennisUpdateHistory, MyTischtennisFetchLog, ApiLog, MemberTransferConfig, MemberContact, MemberTtrHistory
} from './models/index.js';
import authRoutes from './routes/authRoutes.js';
import clubRoutes from './routes/clubRoutes.js';
import diaryRoutes from './routes/diaryRoutes.js';
import memberRoutes from './routes/memberRoutes.js';
import participantRoutes from './routes/participantRoutes.js';
import activityRoutes from './routes/activityRoutes.js';
import memberNoteRoutes from './routes/memberNoteRoutes.js';
import diaryTagRoutes from './routes/diaryTagRoutes.js';
import diaryNoteRoutes from './routes/diaryNoteRoutes.js';
import diaryMemberRoutes from './routes/diaryMemberRoutes.js';
import predefinedActivityRoutes from './routes/predefinedActivityRoutes.js';
import diaryDateActivityRoutes from './routes/diaryDateActivityRoutes.js';
import diaryMemberActivityRoutes from './routes/diaryMemberActivityRoutes.js';
import matchRoutes from './routes/matchRoutes.js';
import Season from './models/Season.js';
import Location from './models/Location.js';
import groupRoutes from './routes/groupRoutes.js';
import diaryDateTagRoutes from './routes/diaryDateTagRoutes.js';
import sessionRoutes from './routes/sessionRoutes.js';
import schedulerRoutes from './routes/schedulerRoutes.js';
import tournamentRoutes from './routes/tournamentRoutes.js';
import accidentRoutes from './routes/accidentRoutes.js';
import trainingStatsRoutes from './routes/trainingStatsRoutes.js';
import officialTournamentRoutes from './routes/officialTournamentRoutes.js';
import myTischtennisRoutes from './routes/myTischtennisRoutes.js';
import clickTtAccountRoutes from './routes/clickTtAccountRoutes.js';
import teamRoutes from './routes/teamRoutes.js';
import clubTeamRoutes from './routes/clubTeamRoutes.js';
import teamDocumentRoutes from './routes/teamDocumentRoutes.js';
import seasonRoutes from './routes/seasonRoutes.js';
import nuscoreSimpleProxyRoutes from './routes/nuscoreSimpleProxyRoutes.js';
import nuscoreApiRoutes from './routes/nuscoreApiRoutes.js';
import memberActivityRoutes from './routes/memberActivityRoutes.js';
import permissionRoutes from './routes/permissionRoutes.js';
import apiLogRoutes from './routes/apiLogRoutes.js';
import clickTtHttpPageRoutes from './routes/clickTtHttpPageRoutes.js';
import memberTransferConfigRoutes from './routes/memberTransferConfigRoutes.js';
import trainingGroupRoutes from './routes/trainingGroupRoutes.js';
import trainingTimeRoutes from './routes/trainingTimeRoutes.js';
import schedulerService from './services/schedulerService.js';
import { requestLoggingMiddleware } from './middleware/requestLoggingMiddleware.js';
import HttpError from './exceptions/HttpError.js';
 
const app = express();
const port = process.env.PORT || 3005;

function captureRawBody(req, res, buf, encoding) {
  if (!buf || buf.length === 0) return;
  req.rawBody = buf.toString(encoding || 'utf8');
}

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// CORS-Konfiguration - Socket.IO hat seine eigene CORS-Konfiguration
app.use(cors({
  origin: true,
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'authcode', 'userid']
}));

app.use('/api/clicktt/proxy', express.raw({
  type: ['multipart/form-data', 'application/octet-stream'],
  limit: '5mb',
  verify: captureRawBody,
}));
app.use(express.json({ verify: captureRawBody }));
app.use(express.urlencoded({ extended: true, verify: captureRawBody }));

// Request Logging Middleware - loggt alle API-Requests
// Wichtig: userId wird später in authMiddleware gesetzt, aber Middleware funktioniert auch ohne
app.use(requestLoggingMiddleware);

// Globale Fehlerbehandlung, damit der Server bei unerwarteten Fehlern nicht hart abstürzt
process.on('uncaughtException', (err) => {
  console.error('[uncaughtException]', err);
});
process.on('unhandledRejection', (reason, promise) => {
  console.error('[unhandledRejection]', reason);
});

app.use('/api/auth', authRoutes);
app.use('/api/clubs', clubRoutes);
app.use('/api/clubmembers', memberRoutes);
app.use('/api/diary', diaryRoutes);
app.use('/api/participants', participantRoutes);
app.use('/api/activities', activityRoutes);
app.use('/api/membernotes', memberNoteRoutes);
app.use('/api/diarynotes', diaryNoteRoutes);
app.use('/api/tags', diaryTagRoutes);
app.use('/api/diarymember', diaryMemberRoutes);
app.use('/api/predefined-activities', predefinedActivityRoutes);
app.use('/api/diary-date-activities', diaryDateActivityRoutes);
app.use('/api/diary-member-activities', diaryMemberActivityRoutes);
app.use('/api/matches', matchRoutes);
app.use('/api/group', groupRoutes);
app.use('/api/diarydatetags', diaryDateTagRoutes);
app.use('/api/session', sessionRoutes);
app.use('/api/scheduler', schedulerRoutes);
app.use('/api/tournament', tournamentRoutes);
app.use('/api/accident', accidentRoutes);
app.use('/api/training-stats', trainingStatsRoutes);
app.use('/api/official-tournaments', officialTournamentRoutes);
app.use('/api/mytischtennis', myTischtennisRoutes);
app.use('/api/clicktt-account', clickTtAccountRoutes);
app.use('/api/teams', teamRoutes);
app.use('/api/club-teams', clubTeamRoutes);
app.use('/api/team-documents', teamDocumentRoutes);
app.use('/api/seasons', seasonRoutes);
app.use('/api/proxy/nuscore', nuscoreSimpleProxyRoutes);
app.use('/api/nuscore', nuscoreApiRoutes);
app.use('/api/member-activities', memberActivityRoutes);
app.use('/api/permissions', permissionRoutes);
app.use('/api/logs', apiLogRoutes);
app.use('/api/clicktt', clickTtHttpPageRoutes);
app.use('/api/member-transfer-config', memberTransferConfigRoutes);
app.use('/api/training-groups', trainingGroupRoutes);
app.use('/api/training-times', trainingTimeRoutes);

// Middleware für dynamischen kanonischen Tag (vor express.static)
const setCanonicalTag = (req, res, next) => {
  // Socket.IO-Requests komplett ignorieren
  if (req.path.startsWith('/socket.io/')) {
    return next();
  }
  
  // Nur für HTML-Anfragen (nicht für API, Assets, etc.)
  if (req.path.startsWith('/api') || req.path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|mp3|webmanifest|xml|txt)$/)) {
    return next();
  }

  // Prüfe, ob die Datei als statische Datei existiert (außer index.html)
  const staticPath = path.join(__dirname, '../frontend/dist', req.path);
  fs.access(staticPath, fs.constants.F_OK, (err) => {
    if (!err && req.path !== '/' && req.path !== '/index.html') {
      // Datei existiert und ist nicht index.html, lasse express.static sie servieren
      return next();
    }

    // Datei existiert nicht oder ist index.html, serviere index.html mit dynamischem kanonischen Tag
    const indexPath = path.join(__dirname, '../frontend/dist/index.html');
    fs.readFile(indexPath, 'utf8', (err, data) => {
      if (err) {
        return next();
      }

      // Bestimme die kanonische URL (bevorzuge non-www)
      const protocol = req.protocol || 'https';
      const host = req.get('host') || 'tt-tagebuch.de';
      const canonicalHost = host.replace(/^www\./, ''); // Entferne www falls vorhanden
      const canonicalUrl = `${protocol}://${canonicalHost}${req.path === '/' ? '' : req.path}`;

      // Ersetze den kanonischen Tag
      const updatedData = data.replace(
        /<link rel="canonical" href="[^"]*" \/>/,
        `<link rel="canonical" href="${canonicalUrl}" />`
      );

      res.setHeader('Content-Type', 'text/html');
      res.send(updatedData);
    });
  });
};

app.use(setCanonicalTag);
app.use(express.static(path.join(__dirname, '../frontend/dist')));

// Globale Fehlerbehandlung für API-Routen (MUSS nach allen Routes sein!)
app.use((err, req, res, next) => {
  if (res.headersSent) {
    return next(err);
  }

  const status = err?.statusCode || err?.status || 500;
  
  // Unterstützung für Fehlercodes
  let errorResponse;
  if (err instanceof HttpError && err.errorCode) {
    // Neues Format mit Fehlercode
    errorResponse = err.toJSON();
  } else {
    // Legacy-Format: String-Nachricht
    const message = err?.message || 'Interner Serverfehler';
    errorResponse = {
      message
    };
  }

  const response = {
    success: false,
    ...errorResponse,
    // Für Rückwärtskompatibilität: error-Feld mit Nachricht
    error: errorResponse.message || errorResponse.code || 'Interner Serverfehler'
  };

  if (process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development') {
    response.debug = {
      stack: err?.stack || null
    };
  }

  console.error('[ExpressError]', err);
  res.status(status).json(response);
});

(async () => {
  try {
    await sequelize.authenticate();

    // Einmalige Migration: deutsche Spaltennamen -> englische
    const renameColumnIfExists = async (table, from, to, typeSql) => {
      try {
        const [rows] = await sequelize.query(`SHOW COLUMNS FROM \`${table}\` LIKE :col`, { replacements: { col: from } });
        if (Array.isArray(rows) && rows.length > 0) {
          await sequelize.query(`ALTER TABLE \`${table}\` CHANGE \`${from}\` \`${to}\` ${typeSql}`);
        }
      } catch (e) {
        console.error(`[migration] Failed to rename ${table}.${from} -> ${to}:`, e.message);
      }
    };

    // official_competitions
    await renameColumnIfExists('official_competitions', 'altersklasse_wettbewerb', 'age_class_competition', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'leistungsklasse', 'performance_class', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'startzeit', 'start_time', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'meldeschluss_datum', 'registration_deadline_date', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'meldeschluss_online', 'registration_deadline_online', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'stichtag', 'cutoff_date', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'offen_fuer', 'open_to', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'vorrunde', 'preliminary_round', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'endrunde', 'final_round', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'max_teilnehmer', 'max_participants', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_competitions', 'startgeld', 'entry_fee', 'VARCHAR(255) NULL');

    // official_tournaments
    await renameColumnIfExists('official_tournaments', 'termin', 'event_date', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_tournaments', 'veranstalter', 'organizer', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_tournaments', 'ausrichter', 'host', 'VARCHAR(255) NULL');
    await renameColumnIfExists('official_tournaments', 'austragungsorte', 'venues', 'TEXT NULL');
    await renameColumnIfExists('official_tournaments', 'konkurrenztypen', 'competition_types', 'TEXT NULL');
    await renameColumnIfExists('official_tournaments', 'meldeschluesse', 'registration_deadlines', 'TEXT NULL');

    const isDev = process.env.STAGE === 'dev';
    
    // Foreign Keys temporär deaktivieren für MySQL
    try {
      await sequelize.query('SET FOREIGN_KEY_CHECKS = 0');
    } catch (e) {
      console.warn('[sync] Could not disable foreign key checks:', e?.message);
    }
    
    const safeSync = async (model) => {
      try {
        if (isDev) {
          await model.sync({ alter: true });
        } else {
          await model.sync();
        }
      } catch (e) {
        try {
          console.error(`[sync] ${model?.name || 'model'} alter failed:`, e?.message || e);
          await model.sync();
        } catch (e2) {
          console.error(`[sync] fallback failed for ${model?.name || 'model'}:`, e2?.message || e2);
        }
      }
    };

    await safeSync(User);
    await safeSync(Club);
    await safeSync(UserClub);
    await safeSync(Log);
    await safeSync(Member);
    await safeSync(DiaryDate);
    await safeSync(Participant);
    await safeSync(Activity);
    await safeSync(MemberNote);
    await safeSync(DiaryNote);
    await safeSync(DiaryTag);
    await safeSync(MemberDiaryTag);
    await safeSync(DiaryDateTag);
    await safeSync(DiaryMemberTag);
    await safeSync(DiaryMemberNote);
    await safeSync(PredefinedActivity);
    await safeSync(PredefinedActivityImage);
    await safeSync(DiaryDateActivity);
    await safeSync(DiaryMemberActivity);
    await safeSync(OfficialTournament);
    await safeSync(OfficialCompetition);
    await safeSync(OfficialCompetitionMember);
    await safeSync(Season);
    await safeSync(League);
    await safeSync(Team);
    await safeSync(Location);
    await safeSync(Match);
    await safeSync(Group);
    await safeSync(GroupActivity);
    await safeSync(Tournament);
    await safeSync(TournamentGroup);
    await safeSync(TournamentMember);
    await safeSync(TournamentMatch);
    await safeSync(TournamentResult);
    await safeSync(Accident);
    await safeSync(UserToken);
    await safeSync(MyTischtennis);
    await safeSync(ClickTtAccount);
    await safeSync(MyTischtennisUpdateHistory);
    await safeSync(MyTischtennisFetchLog);
    await safeSync(ApiLog);
    await safeSync(MemberTransferConfig);
    await safeSync(MemberContact);
    await safeSync(MemberTtrHistory);
    await safeSync(ClubTeam);
    await safeSync(TeamDocument);

    // Foreign Keys wieder aktivieren
    try {
      await sequelize.query('SET FOREIGN_KEY_CHECKS = 1');
    } catch (e) {
      console.warn('[sync] Could not enable foreign key checks:', e?.message);
    }

    // Start scheduler service
    schedulerService.start();

    // Erstelle HTTP-Server für API
    const httpServer = createServer(app);
    
    // WICHTIG: Socket.IO muss VOR dem Server-Start initialisiert werden
    // damit es Upgrade-Requests abfangen kann
    let socketIOInitialized = false;
    
    // Erstelle HTTPS-Server für Socket.IO (direkt mit SSL)
    const httpsPort = process.env.HTTPS_PORT || 3051;
    
    // Prüfe, ob SSL-Zertifikate vorhanden sind
    const sslKeyPath = '/etc/letsencrypt/live/tt-tagebuch.de/privkey.pem';
    const sslCertPath = '/etc/letsencrypt/live/tt-tagebuch.de/fullchain.pem';
    
    if (fs.existsSync(sslKeyPath) && fs.existsSync(sslCertPath)) {
      try {
        console.log('📜 Lade SSL-Zertifikate...');
        const httpsOptions = {
          key: fs.readFileSync(sslKeyPath),
          cert: fs.readFileSync(sslCertPath)
        };
        console.log('✅ SSL-Zertifikate erfolgreich geladen');
        
        // Erstelle HTTPS-Server mit Express-App
        const httpsServer = https.createServer(httpsOptions, app);
        
        // Initialisiere Socket.IO auf HTTPS-Server VOR dem Listen
        initializeSocketIO(httpsServer);
        socketIOInitialized = true;
        
        // Prüfe, ob Port bereits belegt ist
        httpsServer.on('error', (err) => {
          if (err.code === 'EADDRINUSE') {
            console.error(`❌ Port ${httpsPort} ist bereits belegt!`);
            console.error('   → Möglicherweise läuft bereits ein anderer Server auf diesem Port');
            console.error('   → Prüfe mit: lsof -i :3051 oder netstat -tlnp | grep 3051');
            socketIOInitialized = false;
          } else {
            console.error('❌ HTTPS-Server Error:', err.message);
            console.error('   Code:', err.code);
            socketIOInitialized = false;
          }
        });
        
        httpsServer.on('clientError', (err, socket) => {
          if (socket && !socket.destroyed) {
            console.error('❌ HTTPS-Server Client Error:', err.message);
            socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
          }
        });
        
        // Starte HTTPS-Server
        httpsServer.listen(httpsPort, '0.0.0.0', () => {
          console.log(`🚀 HTTPS-Server für Socket.IO läuft auf Port ${httpsPort}`);
          console.log(`   Socket.IO Endpoint: https://tt-tagebuch.de:${httpsPort}/socket.io/`);
          console.log(`   Prüfe mit: lsof -i :${httpsPort} oder netstat -tlnp | grep ${httpsPort}`);
        });
        
        // Prüfe nach kurzer Verzögerung, ob Server wirklich läuft
        setTimeout(() => {
          if (socketIOInitialized) {
            exec(`lsof -i :${httpsPort} || netstat -tlnp 2>/dev/null | grep :${httpsPort} || echo "Port nicht gefunden"`, (error, stdout) => {
              if (stdout && !stdout.includes('Port nicht gefunden')) {
                console.log(`✅ Port ${httpsPort} ist aktiv und erreichbar`);
              } else {
                console.warn(`⚠️  Port ${httpsPort} scheint nicht aktiv zu sein - prüfe Server-Logs`);
              }
            });
          }
        }, 2000);
      } catch (err) {
        console.error('⚠️  HTTPS-Server konnte nicht gestartet werden:', err.message);
        console.error('   Stack:', err.stack);
        console.log('   → Socket.IO läuft auf HTTP-Server (nur für Entwicklung)');
        socketIOInitialized = false;
      }
    } else {
      console.log('ℹ️  SSL-Zertifikate nicht gefunden - Socket.IO läuft auf HTTP-Server (nur für Entwicklung)');
      console.log(`   Erwartete Pfade: ${sslKeyPath}, ${sslCertPath}`);
      console.log(`   Prüfe mit: ls -la ${sslKeyPath} ${sslCertPath}`);
    }
    
    // Fallback: Socket.IO auf HTTP-Server (wenn noch nicht initialisiert)
    // WICHTIG: VOR dem httpServer.listen() initialisieren
    if (!socketIOInitialized) {
      initializeSocketIO(httpServer);
      console.log('   ✅ Socket.IO erfolgreich auf HTTP-Server initialisiert');
    }
    
    // HTTP-Server starten NACH Socket.IO-Initialisierung
    httpServer.listen(port, () => {
      console.log(`🚀 HTTP-Server läuft auf Port ${port}`);
    });
  } catch (err) {
    console.error('Unable to synchronize the database:', err);
  }
})();
