const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const app = express();
const passport = require('passport');
const path = require('path');
const fs = require('fs');
const bodyParser = require('body-parser');
const config = require('./config');
const swaggerUI = require('swagger-ui-express');
const swaggerJsDoc = require('swagger-jsdoc');
const { logger, requestLogger } = require('./utils/logger');
const {
  uploadLimiter,
  downloadLimiter,
  searchLimiter,
  aiLimiter,
} = require('./middlewares/rateLimiter');

const authRoutes = require('./routes/auth');
const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const sqlRoutes = require('./routes/sql');

const openaiRoutes = require('./routes/openai');

const usersRoutes = require('./routes/users');

const rolesRoutes = require('./routes/roles');

const permissionsRoutes = require('./routes/permissions');

const projectsRoutes = require('./routes/projects');

const project_membershipsRoutes = require('./routes/project_memberships');

const assetsRoutes = require('./routes/assets');

const asset_variantsRoutes = require('./routes/asset_variants');

const presigned_url_requestsRoutes = require('./routes/presigned_url_requests');

const tour_pagesRoutes = require('./routes/tour_pages');

const project_audio_tracksRoutes = require('./routes/project_audio_tracks');

const publish_eventsRoutes = require('./routes/publish_events');

const pwa_cachesRoutes = require('./routes/pwa_caches');

const access_logsRoutes = require('./routes/access_logs');
const element_type_defaultsRoutes = require('./routes/element_type_defaults');
const project_element_defaultsRoutes = require('./routes/project_element_defaults');

const publishRoutes = require('./routes/publish');
const runtimeContextRoutes = require('./routes/runtime-context');
const { runtimeContextMiddleware } = require('./middlewares/runtime-context');
const {
  blockNonPublicRuntimeListEndpoints,
  sanitizePublicRuntimeListResponse,
} = require('./middlewares/runtime-public');

const getBaseUrl = (url) => {
  if (!url) return '';
  return url.endsWith('/api') ? url.slice(0, -4) : url;
};

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      version: '1.0.0',
      title: 'Tour Builder Platform',
      description:
        'Tour Builder Platform Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.',
    },
    servers: [
      {
        url: getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || config.swaggerUrl,
        description: 'Development server',
      },
    ],
    components: {
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
        },
      },
      responses: {
        UnauthorizedError: {
          description: 'Access token is missing or invalid',
        },
      },
    },
    security: [
      {
        bearerAuth: [],
      },
    ],
  },
  apis: ['./src/routes/*.js'],
};

const specs = swaggerJsDoc(options);
app.use(
  '/api-docs',
  function (req, res, next) {
    swaggerUI.host =
      getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || req.get('host');
    next();
  },
  swaggerUI.serve,
  swaggerUI.setup(specs),
);

app.enable('trust proxy');
app.use(
  helmet({
    contentSecurityPolicy: false,
    crossOriginEmbedderPolicy: false,
  }),
);
app.use(cors({ origin: true }));
require('./auth/auth');

// Request logger applied early so all routes are logged
app.use(requestLogger);

// Initialize passport JWT auth early (before file routes)
const jwtAuth = passport.authenticate('jwt', { session: false });

// Mount file routes BEFORE body-parser to avoid JSON parsing on binary uploads
// These routes handle their own body parsing (JSON for init/finalize, raw streams for chunks)
// Use downloadLimiter for download/presign (high traffic), uploadLimiter for uploads (strict)
app.use('/api/file/download', downloadLimiter);
app.use('/api/file/presign', downloadLimiter);
app.use('/api/file/upload', uploadLimiter);
app.use('/api/file/upload-sessions', uploadLimiter);
app.use('/api/file', fileRoutes);

// Body parser for all other routes
app.use(bodyParser.json({ limit: '1mb' }));
app.use(bodyParser.urlencoded({ extended: true, limit: '1mb' }));
app.use(runtimeContextMiddleware);

const requireRuntimeReadOrAuth = (req, res, next) => {
  const headerEnvironment = req.runtimeContext?.headerEnvironment;
  const isReadOnlyRequest = ['GET', 'OPTIONS'].includes(req.method);
  const hasAuthHeader = Boolean(req.headers.authorization);

  // Only production is public. Stage requires authentication (workspace for review).
  const isPublicEnvironment = headerEnvironment === 'production';

  if (isPublicEnvironment && isReadOnlyRequest && !hasAuthHeader) {
    req.isRuntimePublicRequest = true;
    return next();
  }

  req.isRuntimePublicRequest = false;
  return jwtAuth(req, res, next);
};

// Health check endpoint (no auth required)
app.get('/api/health', async (req, res) => {
  const db = require('./db/models');
  const health = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    environment: process.env.NODE_ENV || 'development',
  };

  try {
    await db.sequelize.authenticate();
    health.database = 'connected';
  } catch (error) {
    health.status = 'degraded';
    health.database = 'disconnected';
    health.databaseError = error.message;
  }

  const statusCode = health.status === 'ok' ? 200 : 503;
  res.status(statusCode).json(health);
});

app.use('/api/auth', authRoutes);
app.use('/api/runtime-context', runtimeContextRoutes);

app.use('/api/users', jwtAuth, usersRoutes);

app.use('/api/roles', jwtAuth, rolesRoutes);

app.use('/api/permissions', jwtAuth, permissionsRoutes);

const mountRuntimeEntityRoute = (path, entityName, router) => {
  app.use(
    path,
    requireRuntimeReadOrAuth,
    blockNonPublicRuntimeListEndpoints,
    sanitizePublicRuntimeListResponse(entityName),
    router,
  );
};

mountRuntimeEntityRoute('/api/projects', 'projects', projectsRoutes);

app.use('/api/project_memberships', jwtAuth, project_membershipsRoutes);

app.use('/api/assets', jwtAuth, assetsRoutes);

app.use('/api/asset_variants', jwtAuth, asset_variantsRoutes);

app.use('/api/presigned_url_requests', jwtAuth, presigned_url_requestsRoutes);

mountRuntimeEntityRoute('/api/tour_pages', 'tour_pages', tour_pagesRoutes);

mountRuntimeEntityRoute(
  '/api/project_audio_tracks',
  'project_audio_tracks',
  project_audio_tracksRoutes,
);

app.use('/api/publish_events', jwtAuth, publish_eventsRoutes);

app.use('/api/pwa_caches', jwtAuth, pwa_cachesRoutes);

app.use('/api/access_logs', jwtAuth, access_logsRoutes);
app.use('/api/element-type-defaults', jwtAuth, element_type_defaultsRoutes);
// Backwards compatibility alias for old API endpoint
app.use('/api/ui-elements', jwtAuth, element_type_defaultsRoutes);
app.use(
  '/api/project-element-defaults',
  jwtAuth,
  project_element_defaultsRoutes,
);

app.use('/api/publish', jwtAuth, publishRoutes);

app.use('/api/openai', jwtAuth, aiLimiter, openaiRoutes);
app.use('/api/ai', jwtAuth, aiLimiter, openaiRoutes);

app.use('/api/search', jwtAuth, searchLimiter, searchRoutes);
app.use('/api/sql', jwtAuth, sqlRoutes);

const publicDir = path.join(__dirname, '../public');

if (fs.existsSync(publicDir)) {
  app.use('/', express.static(publicDir));

  app.get('*', function (request, response) {
    response.sendFile(path.resolve(publicDir, 'index.html'));
  });
}

// Generic error handler
app.use((err, req, res, _next) => {
  if (!res.headersSent) {
    logger.error({ err, url: req.url, method: req.method }, 'Unhandled error');
    res.status(500).json({ message: 'Internal server error' });
  }
});

const PORT = process.env.NODE_ENV === 'dev_stage' ? 3000 : 8080;

app.listen(PORT, () => {
  logger.info(
    { port: PORT, env: process.env.NODE_ENV || 'development' },
    'Server started',
  );
});

module.exports = app;
