import express from 'express';
import compression from 'compression';
import helmet from 'helmet';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import rateLimit from 'express-rate-limit';
import dotenv from 'dotenv';
import path from 'path';

dotenv.config({ path: path.join(__dirname, '../.env') });

// Validate required environment variables at startup
const REQUIRED_ENV = ['DATABASE_URL', 'JWT_SECRET', 'GROK_API_KEY', 'SPORTSGAMEODDS_API_KEY'];
for (const key of REQUIRED_ENV) {
  if (!process.env[key]) {
    console.error(`[FATAL] Missing required env var: ${key}`);
    process.exit(1);
  }
}
// Warn about payment gateway keys (non-fatal so other features still work)
const PAYMENT_ENV = ['AUTHNET_API_LOGIN_ID', 'AUTHNET_TRANSACTION_KEY'];
for (const key of PAYMENT_ENV) {
  if (!process.env[key] || process.env[key] === 'REPLACE_ME') {
    console.warn(`[WARN] Payment gateway not configured: ${key} — purchases will fail until set`);
  }
}
if (process.env.NODE_ENV === 'production' && !process.env.ADMIN_EMAILS && !process.env.ADMIN_USER_IDS) {
  console.error('[ERROR] Admin allowlist missing. Production admin routes are disabled until ADMIN_EMAILS or ADMIN_USER_IDS is set.');
}

import authRoutes from './routes/auth';
import eventsRoutes from './routes/events';
import forecastRoutes from './routes/forecast';
import purchaseRoutes from './routes/purchase';
import userRoutes from './routes/user';
import statsRoutes from './routes/stats';
import logosRoutes from './routes/logos';
import weatherReportRoutes from './routes/weather-report';
import freePickRoutes from './routes/free-pick';
import geoRoutes from './routes/geo';
import behaviorRoutes from './routes/behavior';
import surveyRoutes from './routes/survey';
import blogRoutes from './routes/blog';
import complianceRoutes from './routes/compliance';
import affiliateRoutes from './routes/affiliate';
import pageviewsRoutes from './routes/pageviews';
import newsRoutes from './routes/news';
import forecastAssetsRoutes from './routes/forecast-assets';
import clippedRoutes from './routes/clipped';
import clipperRoutes from './routes/clipper';
import socialRoutes from './routes/social';
import rainwireRoutes from './routes/rainwire';
import adminForecastRoutes from './routes/admin-forecast';
import adminIntegrityRoutes from './routes/admin-integrity';
import adminSocialBotRoutes from './routes/admin-social-bot';
import profileRoutes from './routes/profile';
import pushRoutes from './routes/push';
import archiveRoutes from './routes/archive';
import kieRoutes from './routes/kie';
import trafficRoutes from './routes/traffic';
import { isAllowedCorsOrigin } from './lib/cors';
import { blockBots } from './middleware/bot-detect';
import { AUTH_COOKIE_NAME } from './middleware/auth';
import { checkDatabaseHealth } from './db';
import { runPendingMigrations } from './db/migrate';
import { apiErrorHandler, notFoundHandler } from './middleware/error-handler';

const app = express();
const PORT = parseInt(process.env.PORT || '3021', 10);
const isProduction = process.env.NODE_ENV === 'production';
const authorizeNetOrigins = [
  'https://accept.authorize.net',
  ...(isProduction ? [] : ['https://test.authorize.net']),
];

// Trust proxy (behind Apache reverse proxy)
app.set('trust proxy', 1);

// Compress JSON and other text responses before they go over the wire.
app.use(compression({ threshold: 1024 }));

// Security
app.use(helmet({
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
  frameguard: { action: 'deny' },
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://www.googletagmanager.com", "https://www.google-analytics.com"],
      styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://www.google-analytics.com", "https://rainmakersports.app", "https://api.x.ai", ...authorizeNetOrigins],
      frameSrc: ["'self'", ...authorizeNetOrigins],
      frameAncestors: ["'none'"],
    },
  },
}));

// CORS
const allowedOrigins = (process.env.CORS_ORIGINS || process.env.FRONTEND_URL || 'http://localhost:3020')
  .split(',')
  .map(s => s.trim())
  .filter(Boolean);
app.use(cors({
  origin: (origin, callback) => {
    if (isAllowedCorsOrigin(origin, allowedOrigins)) {
      callback(null, true);
    } else {
      callback(null, false);
    }
  },
  credentials: true,
}));

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 1000,
  standardHeaders: true,
  legacyHeaders: false,
});
app.use(limiter);

// Authorize.Net webhook accepts both JSON and form-encoded Silent Post
app.use('/api/purchase/webhook', express.urlencoded({ extended: true }));
app.use('/api/purchase/webhook', express.json());

// JSON body parser for everything else
app.use(express.json({ limit: '1mb' }));

// Cookie parser (needed for affiliate tracking)
app.use(cookieParser());

function isTrustedMutationOrigin(value: string | undefined, allowed: Set<string>): boolean {
  if (!value) return false;
  try {
    return allowed.has(new URL(value).origin);
  } catch {
    return false;
  }
}

const trustedMutationOrigins = new Set(allowedOrigins);
app.use((req, res, next) => {
  if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
    next();
    return;
  }

  if (req.path === '/api/purchase/webhook') {
    next();
    return;
  }

  const authCookie = req.cookies?.[AUTH_COOKIE_NAME];
  const usingAuthCookie = typeof authCookie === 'string' && authCookie.length > 0;
  if (!usingAuthCookie) {
    next();
    return;
  }

  const origin = typeof req.headers.origin === 'string' ? req.headers.origin : undefined;
  const referer = typeof req.headers.referer === 'string' ? req.headers.referer : undefined;
  const isTrusted = isTrustedMutationOrigin(origin, trustedMutationOrigins)
    || isTrustedMutationOrigin(referer, trustedMutationOrigins);

  if (!isTrusted) {
    res.status(403).json({ error: 'CSRF check failed' });
    return;
  }

  next();
});

// Forecast-specific rate limiter + bot detection (before route registration)
const forecastLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 30,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many forecast requests, please try again later' },
});
app.use('/api/forecast', blockBots, forecastLimiter);
app.use('/api/forecast-assets', blockBots, forecastLimiter);

// Admin rate limiter — tighter limit for expensive admin operations
const adminLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many admin requests, please try again later' },
});
app.use('/api/admin', adminLimiter);

// Static: serve generated headline card images
app.use('/api/news-cards', express.static(path.join(__dirname, '../../public/news-cards'), {
  maxAge: '1h',
  immutable: false,
}));

// Static: serve social engine card images
app.use('/api/social-cards', express.static(path.join(__dirname, '../../public/social-cards'), {
  maxAge: 0,
  immutable: false,
  setHeaders: (res) => {
    res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
    res.setHeader('Pragma', 'no-cache');
    res.setHeader('Expires', '0');
  },
}));

// Routes
app.use('/api/auth', authRoutes);
app.use('/api/events', eventsRoutes);
app.use('/api/forecast', forecastRoutes);
app.use('/api/purchase', purchaseRoutes);
app.use('/api/user', userRoutes);
app.use('/api/stats', statsRoutes);
app.use('/api/logos', logosRoutes);
app.use('/api/weather-report', weatherReportRoutes);
app.use('/api/free-pick', freePickRoutes);
app.use('/api/geo', geoRoutes);
app.use('/api/behavior', behaviorRoutes);
app.use('/api/survey', surveyRoutes);
app.use('/api/blog', blogRoutes);
app.use('/api/compliance', complianceRoutes);
app.use('/api/affiliate', affiliateRoutes);
app.use('/api/pageviews', pageviewsRoutes);
app.use('/api/news', newsRoutes);
app.use('/api/forecast-assets', forecastAssetsRoutes);
app.use('/api/clipped', clippedRoutes);
app.use('/api/clipper', clipperRoutes);
app.use('/api/social', socialRoutes);
app.use('/api/rainwire', rainwireRoutes);
app.use('/api/admin/forecast', adminForecastRoutes);
app.use('/api/admin/publishing-integrity', adminIntegrityRoutes);
app.use('/api/admin/social-bot', adminSocialBotRoutes);
app.use('/api/profile', profileRoutes);
app.use('/api/push', pushRoutes);
app.use('/api/archive', archiveRoutes);
app.use('/api/kie', kieRoutes);
app.use('/api/traffic', trafficRoutes);

// Health check
app.get('/api/health', async (_req, res) => {
  try {
    const database = await checkDatabaseHealth();
    res.json({
      status: 'ok',
      service: 'rainmaker-api',
      timestamp: new Date().toISOString(),
      database,
    });
  } catch (err) {
    console.error('Health check failed:', err);
    res.status(503).json({
      status: 'degraded',
      service: 'rainmaker-api',
      timestamp: new Date().toISOString(),
      database: { ok: false },
    });
  }
});

app.use('/api', notFoundHandler);
app.use(apiErrorHandler);

// Global error handlers — prevent silent crashes
process.on('uncaughtException', (err) => {
  console.error('[FATAL] Uncaught exception:', err);
  process.exit(1);
});
process.on('unhandledRejection', (reason) => {
  console.error('[FATAL] Unhandled rejection:', reason);
  process.exit(1);
});

async function startServer(): Promise<void> {
  if (process.env.DB_MIGRATIONS_AUTO_APPLY !== 'false') {
    const migrationResult = await runPendingMigrations();
    console.log(
      `[db:migrate] startup complete: applied=${migrationResult.applied.length} skipped=${migrationResult.skipped.length}`,
    );
  } else {
    console.warn('[db:migrate] auto-apply disabled via DB_MIGRATIONS_AUTO_APPLY=false');
  }

  app.listen(PORT, '127.0.0.1', () => {
    console.log(`Rainmaker API running on port ${PORT}`);
  });
}

startServer().catch((err) => {
  console.error('[FATAL] Startup failed:', err);
  process.exit(1);
});
