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, ClubTeamMember, TeamDocument, Group,
  GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
  TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis, ClickTtAccount, MyTischtennisUpdateHistory, MyTischtennisFetchLog, ApiLog, MemberTransferConfig, MemberContact, MemberTtrHistory, MemberPlayInterest,
  MemberOrder, MemberOrderHistory, MemberGroupPhoto, BillingTemplate, BillingTemplateField, BillingRun, BillingDocument, BillingDocumentValue, BillingUserSetting, FriendlyMatch, TrainingCancellation
  , FriendlyMatchShared, FriendlyMatchInvitation
  , CalendarEvent, ClubVenue, ClubRequest, ClubRequestNote, ClubSepaMandate, ClubPaymentClaim, ClubAccount, ClubAccountTransaction, ClubInvoiceParty, ClubInvoice, ClubInvoiceItem, ClubRole, ClubUserRole, ClubCommunicationThread, ClubCommunicationMessage, ClubCommunicationRecipient, ClubCommunicationDeliveryLog, ClubCommunicationTemplate, ClubDistributionGroup, ClubDistributionGroupMember
} 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 clubVenueRoutes from './routes/clubVenueRoutes.js';
import trainingCancellationRoutes from './routes/trainingCancellationRoutes.js';
import memberOrderRoutes from './routes/memberOrderRoutes.js';
import memberGroupPhotoRoutes from './routes/memberGroupPhotoRoutes.js';
import billingRoutes from './routes/billingRoutes.js';
import friendlyMatchRoutes from './routes/friendlyMatchRoutes.js';
import friendlyMatchSharedRoutes from './routes/friendlyMatchSharedRoutes.js';
import friendlyMatchInvitationRoutes from './routes/friendlyMatchInvitationRoutes.js';
import calendarRoutes from './routes/calendarRoutes.js';
import calendarEventRoutes from './routes/calendarEventRoutes.js';
import mobileFeedbackRoutes from './routes/mobileFeedbackRoutes.js';
import clubRequestRoutes from './routes/clubRequestRoutes.js';
import clubDashboardRoutes from './routes/clubDashboardRoutes.js';
import clubTaskRoutes from './routes/clubTaskRoutes.js';
import clubStatisticsRoutes from './routes/clubStatisticsRoutes.js';
import clubArchiveRoutes from './routes/clubArchiveRoutes.js';
import clubAccountRoutes from './routes/clubAccountRoutes.js';
import clubInvoiceRoutes from './routes/clubInvoiceRoutes.js';
import clubPaymentClaimRoutes from './routes/clubPaymentClaimRoutes.js';
import clubCommunicationRoutes from './routes/clubCommunicationRoutes.js';
import clubDocumentRoutes from './routes/clubDocumentRoutes.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;
const PUBLIC_SITE_URL = process.env.PUBLIC_SITE_URL || 'https://tt-tagebuch.de';
const publicSiteOrigin = new URL(PUBLIC_SITE_URL).origin;
const publicSiteHost = new URL(PUBLIC_SITE_URL).host;
const publicSiteProtocol = new URL(PUBLIC_SITE_URL).protocol.replace(':', '');

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);

const SEO_DEFAULTS = {
  title: 'Trainingstagebuch – Vereinsverwaltung für Tischtennis, Trainingsplanung & Turniere',
  description: 'Trainingstagebuch ist die Software für Tischtennisvereine: Mitgliederverwaltung, Trainingsplanung, Trainingstagebuch, Gruppen, Turniere, Team-Management, Statistiken und MyTischtennis-Integration.',
  robots: 'index,follow',
};

const SEO_ROUTE_CONFIG = {
  '/': {
    title: SEO_DEFAULTS.title,
    description: SEO_DEFAULTS.description,
    robots: 'index,follow',
  },
  '/vereinssoftware-tischtennis': {
    title: 'Vereinssoftware für Tischtennisvereine | Trainingstagebuch',
    description: 'Webbasierte Vereinssoftware für Tischtennisvereine mit Mitgliederverwaltung, Trainingsplanung, Mannschaftsorganisation, Turnieren und Auswertungen.',
    robots: 'index,follow',
  },
  '/mitgliederverwaltung-verein': {
    title: 'Mitgliederverwaltung für Vereine | Trainingstagebuch',
    description: 'Mitgliederverwaltung für Vereine mit Stammdaten, Rollen, Gruppenbezug und organisatorischer Verbindung zu Training und Vereinsabläufen.',
    robots: 'index,follow',
  },
  '/trainingsplanung-tischtennis': {
    title: 'Trainingsplanung für Tischtennisvereine | Trainingstagebuch',
    description: 'Trainingsplanung für Tischtennisvereine mit Gruppen, Anwesenheiten, Trainingstagebuch und digitaler Organisation von Trainingstagen.',
    robots: 'index,follow',
  },
  '/turniersoftware-tischtennis': {
    title: 'Turniersoftware für Tischtennis | Trainingstagebuch',
    description: 'Turniersoftware für Tischtennisvereine mit Teilnehmerverwaltung, Gruppen, Paarungen, Ergebnissen und Organisation interner oder offizieller Turniere.',
    robots: 'index,follow',
  },
  '/login': {
    title: 'Login | Trainingstagebuch',
    description: 'Im Trainingstagebuch einloggen und Vereinsdaten, Trainingsplanung, Mitglieder und Turniere verwalten.',
    robots: 'noindex,follow',
  },
  '/register': {
    title: 'Registrieren | Trainingstagebuch',
    description: 'Kostenlos im Trainingstagebuch registrieren und die Vereinsverwaltung für Tischtennisvereine kennenlernen.',
    robots: 'noindex,follow',
  },
  '/activate': {
    title: 'Konto aktivieren | Trainingstagebuch',
    description: 'Aktivierung des Benutzerkontos im Trainingstagebuch.',
    robots: 'noindex,follow',
  },
  '/forgot-password': {
    title: 'Passwort vergessen | Trainingstagebuch',
    description: 'Zugang zum Trainingstagebuch wiederherstellen.',
    robots: 'noindex,follow',
  },
  '/reset-password': {
    title: 'Passwort zurücksetzen | Trainingstagebuch',
    description: 'Passwort im Trainingstagebuch sicher zurücksetzen.',
    robots: 'noindex,follow',
  },
  '/impressum': {
    title: 'Impressum | Trainingstagebuch',
    description: 'Impressum von Trainingstagebuch.',
    robots: 'index,follow',
  },
  '/datenschutz': {
    title: 'Datenschutzerklärung | Trainingstagebuch',
    description: 'Datenschutzerklärung von Trainingstagebuch.',
    robots: 'index,follow',
  },
  '/konto-loeschen': {
    title: 'Konto und Daten löschen | Trainingstagebuch',
    description: 'Informationen zur Löschung des Benutzerkontos und personenbezogener Daten im Trainingstagebuch.',
    robots: 'index,follow',
  },
};

const SEO_NOINDEX_PREFIXES = [
  '/createclub',
  '/showclub',
  '/members',
  '/diary',
  '/calendar',
  '/pending-approvals',
  '/schedule',
  '/tournaments',
  '/tournament-participations',
  '/training-stats',
  '/club-settings',
  '/predefined-activities',
  '/mytischtennis-account',
  '/clicktt-account',
  '/team-management',
  '/permissions',
  '/logs',
  '/clicktt',
  '/member-transfer-settings',
  '/personal-settings',
  '/orders',
  '/billing',
];

function normalizeSeoPath(pathname = '/') {
  if (!pathname || pathname === '') return '/';
  if (pathname === '/') return '/';
  return pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
}

function getSeoConfigForPath(pathname = '/') {
  const normalizedPath = normalizeSeoPath(pathname);
  const matchedPrefix = Object.keys(SEO_ROUTE_CONFIG)
    .filter((routePath) => routePath !== '/' && normalizedPath.startsWith(routePath))
    .sort((a, b) => b.length - a.length)[0];

  const configuredSeo = (matchedPrefix && SEO_ROUTE_CONFIG[matchedPrefix]) || SEO_ROUTE_CONFIG[normalizedPath];
  if (configuredSeo) {
    return configuredSeo;
  }

  const shouldNoindex = SEO_NOINDEX_PREFIXES.some((routePath) => normalizedPath.startsWith(routePath));
  return {
    ...SEO_DEFAULTS,
    robots: shouldNoindex ? 'noindex,follow' : SEO_DEFAULTS.robots
  };
}

function escapeHtmlAttribute(value = '') {
  return String(value)
    .replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

function isPublicFacingHost(host = '') {
  const normalizedHost = String(host).toLowerCase().replace(/:\d+$/, '');
  return normalizedHost === 'tt-tagebuch.de' || normalizedHost === 'www.tt-tagebuch.de';
}

function getCanonicalRequestContext(req) {
  const forwardedProtoHeader = req.get('x-forwarded-proto');
  const forwardedHostHeader = req.get('x-forwarded-host');
  const forwardedProto = forwardedProtoHeader?.split(',')[0]?.trim();
  const forwardedHost = forwardedHostHeader?.split(',')[0]?.trim();
  const requestHost = forwardedHost || req.get('host') || publicSiteHost;
  const requestProtocol = forwardedProto || req.protocol || publicSiteProtocol;
  const canonicalHost = isPublicFacingHost(requestHost) ? requestHost.replace(/^www\./, '') : publicSiteHost;
  const canonicalProtocol = isPublicFacingHost(requestHost) ? requestProtocol : publicSiteProtocol;

  return {
    requestHost,
    requestProtocol,
    canonicalHost,
    canonicalProtocol,
    canonicalOrigin: `${canonicalProtocol}://${canonicalHost}`,
  };
}

function stripJsonLdByType(html, schemaType) {
  const escapedType = schemaType.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const pattern = new RegExp(
    `<script type="application/ld\\+json">[\\s\\S]*?"@type"\\s*:\\s*"${escapedType}"[\\s\\S]*?<\\/script>\\s*`,
    'g'
  );
  return html.replace(pattern, '');
}

app.set('trust proxy', true);

// 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);

// Normalisierungs-Middleware: Wenn `drawingData` als Objekt ankommt, immer als JSON-String speichern.
// Vermeidet Typ-Mismatches zwischen Client (Objekt) und DB/Code (stringified JSON).
function normalizeDrawingDataInObject(obj) {
  if (!obj || typeof obj !== 'object') return;
  if (Array.isArray(obj)) return obj.forEach(normalizeDrawingDataInObject);

  // Direkte Felder
  try {
    if (obj.drawingData && typeof obj.drawingData === 'object') {
      obj.drawingData = JSON.stringify(obj.drawingData);
    }
  } catch (e) {
    // Ignore
  }

  // häufiges Pattern: predefinedActivity.drawingData
  try {
    if (obj.predefinedActivity && obj.predefinedActivity.drawingData && typeof obj.predefinedActivity.drawingData === 'object') {
      obj.predefinedActivity.drawingData = JSON.stringify(obj.predefinedActivity.drawingData);
    }
  } catch (e) {}

  // Rekursiv durch alle Keys gehen
  for (const k of Object.keys(obj)) {
    try { normalizeDrawingDataInObject(obj[k]); } catch (e) {}
  }
}

app.use((req, res, next) => {
  try {
    if (req.body) normalizeDrawingDataInObject(req.body);
    if (req.query) normalizeDrawingDataInObject(req.query);
  } catch (e) {
    // Never fail the request because of normalization
    console.error('[normalizeDrawingData] Error:', e && e.message ? e.message : e);
  }
  next();
});

// 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);
app.use('/api/club-venues', clubVenueRoutes);
app.use('/api/training-cancellations', trainingCancellationRoutes);
app.use('/api/member-orders', memberOrderRoutes);
app.use('/api/member-group-photos', memberGroupPhotoRoutes);
app.use('/api/billing', billingRoutes);
app.use('/api/friendly-matches', friendlyMatchSharedRoutes);
app.use('/api/friendly-matches', friendlyMatchRoutes);
app.use('/api/friendly-match-invitations', friendlyMatchInvitationRoutes);
app.use('/api/calendar', calendarRoutes);
app.use('/api/calendar-events', calendarEventRoutes);
app.use('/api/mobile-feedback', mobileFeedbackRoutes);
app.use('/api/club-requests', clubRequestRoutes);
app.use('/api/club-dashboard', clubDashboardRoutes);
app.use('/api/club-tasks', clubTaskRoutes);
app.use('/api/club-statistics', clubStatisticsRoutes);
app.use('/api/club-archive', clubArchiveRoutes);
app.use('/api/club-accounts', clubAccountRoutes);
app.use('/api/club-invoices', clubInvoiceRoutes);
app.use('/api/club-payment-claims', clubPaymentClaimRoutes);
app.use('/api/club-communication', clubCommunicationRoutes);
app.use('/api/club-documents', clubDocumentRoutes);

// Middleware für dynamischen kanonischen Tag (vor express.static)
const setCanonicalTag = (req, res, next) => {
  const normalizedPath = normalizeSeoPath(req.path);
  const { requestHost, requestProtocol, canonicalHost, canonicalProtocol, canonicalOrigin } = getCanonicalRequestContext(req);

  if (requestHost.replace(/^www\./, '') === canonicalHost && requestHost !== canonicalHost) {
    const redirectUrl = `${canonicalProtocol}://${canonicalHost}${normalizedPath === '/' ? '/' : normalizedPath}${req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : ''}`;
    return res.redirect(301, redirectUrl);
  }

  // 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();
      }

      const canonicalUrl = `${canonicalOrigin}${normalizedPath === '/' ? '' : normalizedPath}`;
      const seo = getSeoConfigForPath(normalizedPath);

      let updatedData = data.replace(
        /<title>[^<]*<\/title>/,
        `<title>${escapeHtmlAttribute(seo.title)}</title>`
      );

      updatedData = updatedData.replace(
        /<meta name="description" content="[^"]*" \/>/,
        `<meta name="description" content="${escapeHtmlAttribute(seo.description)}" />`
      );

      updatedData = updatedData.replace(
        /<meta name="robots" content="[^"]*" \/>/,
        `<meta name="robots" content="${escapeHtmlAttribute(seo.robots)}" />`
      );

      updatedData = updatedData.replace(
        /<link rel="canonical" href="[^"]*" \/>/,
        `<link rel="canonical" href="${escapeHtmlAttribute(canonicalUrl)}" />`
      );

      updatedData = updatedData.replace(
        /<meta property="og:title" content="[^"]*" \/>/,
        `<meta property="og:title" content="${escapeHtmlAttribute(seo.title)}" />`
      );

      updatedData = updatedData.replace(
        /<meta property="og:description" content="[^"]*" \/>/,
        `<meta property="og:description" content="${escapeHtmlAttribute(seo.description)}" />`
      );

      updatedData = updatedData.replace(
        /<meta property="og:url" content="[^"]*" \/>/,
        `<meta property="og:url" content="${escapeHtmlAttribute(canonicalUrl)}" />`
      );

      updatedData = updatedData.replace(
        /<meta name="twitter:title" content="[^"]*" \/>/,
        `<meta name="twitter:title" content="${escapeHtmlAttribute(seo.title)}" />`
      );

      updatedData = updatedData.replace(
        /<meta name="twitter:description" content="[^"]*" \/>/,
        `<meta name="twitter:description" content="${escapeHtmlAttribute(seo.description)}" />`
      );

      if (normalizedPath !== '/') {
        updatedData = stripJsonLdByType(updatedData, 'FAQPage');
        updatedData = stripJsonLdByType(updatedData, 'SoftwareApplication');
      }

      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(ClubVenue);
    await safeSync(UserClub);
    await safeSync(ClubRole);
    await safeSync(ClubUserRole);
    await safeSync(ClubAccount);
    await safeSync(ClubAccountTransaction);
    await safeSync(ClubInvoiceParty);
    await safeSync(ClubInvoice);
    await safeSync(ClubInvoiceItem);
    await safeSync(ClubDistributionGroup);
    await safeSync(ClubDistributionGroupMember);
    await safeSync(ClubCommunicationThread);
    await safeSync(ClubCommunicationMessage);
    await safeSync(ClubCommunicationRecipient);
    await safeSync(ClubCommunicationDeliveryLog);
    await safeSync(ClubCommunicationTemplate);
    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(MemberGroupPhoto);
    await safeSync(MemberTtrHistory);
    await safeSync(MemberPlayInterest);
    await safeSync(MemberOrder);
    await safeSync(MemberOrderHistory);
    await safeSync(BillingTemplate);
    await safeSync(BillingTemplateField);
    await safeSync(BillingRun);
    await safeSync(BillingDocument);
    await safeSync(BillingDocumentValue);
    await safeSync(BillingUserSetting);
    await safeSync(FriendlyMatch);
    await safeSync(FriendlyMatchInvitation);
    await safeSync(FriendlyMatchShared);
    await safeSync(ClubTeam);
    await safeSync(TrainingCancellation);
    await safeSync(CalendarEvent);
    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);
  }
})();
