import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import authRouter from '@/api/routes/auth/index.routes.js';
import databaseRouter from '@/api/routes/database/index.routes.js';
import { storageRouter } from '@/api/routes/storage/index.routes.js';
import { metadataRouter } from '@/api/routes/metadata/index.routes.js';
import { logsRouter } from '@/api/routes/logs/index.routes.js';
import { docsRouter } from '@/api/routes/docs/index.routes.js';
import functionsRouter from '@/api/routes/functions/index.routes.js';
import secretsRouter from '@/api/routes/secrets/index.routes.js';
import { usageRouter } from '@/api/routes/usage/index.routes.js';
import { aiRouter } from '@/api/routes/ai/index.routes.js';
import { memoryRouter } from '@/api/routes/memory/index.routes.js';
import { realtimeRouter } from '@/api/routes/realtime/index.routes.js';
import { emailRouter } from '@/api/routes/email/index.routes.js';
import { deploymentsRouter } from '@/api/routes/deployments/index.routes.js';
import { webhooksRouter } from '@/api/routes/webhooks/index.routes.js';
import { s3GatewayRouter } from '@/api/routes/s3-gateway/index.routes.js';
import { paymentsRouter } from '@/api/routes/payments/index.routes.js';
import { advisorRouter } from '@/api/routes/advisor/index.routes.js';
import { errorMiddleware } from '@/api/middlewares/error.js';
import { destroyEmailCooldownInterval } from '@/api/middlewares/rate-limiters.js';
import { isCloudEnvironment } from '@/utils/environment.js';
import { RealtimeManager } from '@/infra/realtime/realtime.manager.js';
import fetch from 'node-fetch';
import { DatabaseManager } from '@/infra/database/database.manager.js';
import { LogService } from '@/services/logs/log.service.js';
import { StorageService } from '@/services/storage/storage.service.js';
import { SocketManager } from '@/infra/socket/socket.manager.js';
import { OAuthPKCEService } from '@/services/auth/oauth-pkce.service.js';
import { seedBackend } from '@/utils/seed.js';
import logger from '@/utils/logger.js';
import { initSqlParser } from '@/utils/sql-parser.js';
import { FunctionService } from '@/services/functions/function.service.js';
import packageJson from '../../package.json';
import { schedulesRouter } from '@/api/routes/schedules/index.routes.js';
import { servicesRouter } from '@/api/routes/compute/services.routes.js';
import { analyticsRouter } from '@/api/routes/analytics/index.routes.js';
import { webscraperRouter } from '@/api/routes/webscraper/index.routes.js';
import { appConfig } from '@/infra/config/app.config.js';
import { TelemetryService } from '@/services/telemetry/telemetry.service.js';
import { TokenManager } from '@/infra/security/token.manager.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Load .env file from the root directory (parent of backend)
const envPath = path.resolve(__dirname, '../../.env');
if (fs.existsSync(envPath)) {
  dotenv.config({ path: envPath });
} else {
  // Fallback to default behavior (looks in current working directory)
  dotenv.config();
}

export async function createApp() {
  // Initialize database first
  const dbManager = DatabaseManager.getInstance();
  await dbManager.initialize(); // create data/app.db

  // Initialize storage service
  const storageService = StorageService.getInstance();
  await storageService.initialize(); // create data/storage

  // Initialize logs service
  const logService = LogService.getInstance();
  await logService.initialize(); // connect to CloudWatch

  // Initialize SQL parser WASM module
  await initSqlParser();

  const app = express();

  // Enable trust proxy setting for rate limiting behind proxies/load balancers.
  app.set('trust proxy', appConfig.server.trustProxy);

  // Basic middleware
  app.use(
    cors({
      origin: true, // Allow all origins (matches Better Auth's trustedOrigins: ['*'])
      credentials: true, // Allow cookies/credentials
      exposedHeaders: ['Content-Range', 'Preference-Applied'],
    })
  );
  app.use(cookieParser()); // Parse cookies for refresh token handling
  app.use((req: Request, res: Response, next: NextFunction) => {
    const startTime = Date.now();
    const originalSend = res.send;
    const originalJson = res.json;

    // Track response size
    let responseSize = 0;

    // Override send method
    res.send = function (
      data: string | Buffer | Record<string, unknown> | unknown[] | number | boolean
    ) {
      if (data !== undefined && data !== null) {
        if (typeof data === 'string') {
          responseSize = Buffer.byteLength(data);
        } else if (Buffer.isBuffer(data)) {
          responseSize = data.length;
        } else if (typeof data === 'number' || typeof data === 'boolean') {
          responseSize = Buffer.byteLength(String(data));
        } else {
          try {
            responseSize = Buffer.byteLength(JSON.stringify(data));
          } catch {
            // Handle circular references or unstringifiable objects
            responseSize = 0;
          }
        }
      }
      return originalSend.call(this, data);
    };

    // Override json method
    res.json = function (
      data: Record<string, unknown> | unknown[] | string | number | boolean | null
    ) {
      if (data !== undefined) {
        try {
          responseSize = Buffer.byteLength(JSON.stringify(data));
        } catch {
          // Handle circular references or unstringifiable objects
          responseSize = 0;
        }
      }
      return originalJson.call(this, data);
    };

    // Log after response is finished
    res.on('finish', () => {
      // Skip logging for logs endpoints to avoid infinite loops
      if (req.path.includes('/logs/')) {
        return;
      }

      const duration = Date.now() - startTime;
      logger.info('HTTP Request', {
        method: req.method,
        path: req.path,
        status: res.statusCode,
        size: responseSize,
        duration: `${duration}ms`,
        ip: req.ip || req.socket.remoteAddress,
        userAgent: req.headers['user-agent'],
        timestamp: new Date().toISOString(),
      });
    });

    next();
  });

  // Mount webhooks with raw body parser BEFORE JSON middleware
  // This ensures signature verification uses the original bytes
  app.use('/api/webhooks', express.raw({ type: 'application/json' }), webhooksRouter);

  // Mount the S3 protocol gateway BEFORE JSON middleware so request bodies
  // stream through untouched. The gateway handles raw streams itself
  // (including STREAMING-AWS4-HMAC-SHA256-PAYLOAD chunked signatures).
  app.use('/storage/v1/s3', s3GatewayRouter);

  // Apply JSON and URL-encoded middleware for all other routes.
  // We use high defaults (100mb/10mb) to ensure a smooth "out-of-the-box" experience
  // for large metadata/storage requests, as per project standards.
  // Users can override these via environment variables for hardened security.
  const jsonLimit = appConfig.server.maxJsonBodySize;
  const urlencodedLimit = appConfig.server.maxUrlencodedBodySize;

  app.use(express.json({ limit: jsonLimit }));
  app.use(express.urlencoded({ extended: true, limit: urlencodedLimit }));

  // Create API router and mount all API routes under /api
  const apiRouter = express.Router();

  const jwksHandler = async (_req: Request, res: Response) => {
    try {
      const jwks = await TokenManager.getInstance().getJwks();
      res.json(jwks);
    } catch (error) {
      logger.error('Failed to serve JWKS', { error });
      res.status(500).json({ error: 'Internal Server Error' });
    }
  };

  app.get('/.well-known/jwks.json', jwksHandler);
  apiRouter.get('/.well-known/jwks.json', jwksHandler);

  apiRouter.get('/health', (_req: Request, res: Response) => {
    const version = packageJson.version;
    res.json({
      status: 'ok',
      version,
      service: 'Insforge OSS Backend',
      timestamp: new Date().toISOString(),
    });
  });

  // Mount all routes
  apiRouter.use('/auth', authRouter);
  apiRouter.use('/database', databaseRouter);
  apiRouter.use('/storage', storageRouter);
  apiRouter.use('/metadata', metadataRouter);
  apiRouter.use('/logs', logsRouter);
  apiRouter.use('/docs', docsRouter);
  apiRouter.use('/functions', functionsRouter);
  apiRouter.use('/secrets', secretsRouter);
  apiRouter.use('/usage', usageRouter);
  apiRouter.use('/ai', aiRouter);
  apiRouter.use('/memory', memoryRouter);
  apiRouter.use('/realtime', realtimeRouter);
  apiRouter.use('/email', emailRouter);
  apiRouter.use('/deployments', deploymentsRouter);
  apiRouter.use('/schedules', schedulesRouter);
  apiRouter.use('/payments', paymentsRouter);
  apiRouter.use('/compute/services', servicesRouter);
  apiRouter.use('/analytics', analyticsRouter);
  apiRouter.use('/webscraper', webscraperRouter);
  apiRouter.use('/advisor', advisorRouter);

  // Mount all API routes under /api prefix
  app.use('/api', apiRouter);

  // Proxy function execution to Deno Deploy or local runtime
  // this logic is used for backward compatibility, we will let the sdk directly call the edge function
  app.all('/functions/:slug', async (req: Request, res: Response) => {
    const { slug } = req.params;

    try {
      const functionService = FunctionService.getInstance();
      const localRuntime = appConfig.functions.denoRuntimeUrl;

      // Get target base URL: prefer Subhosting deployment, fallback to local runtime
      const baseUrl =
        (functionService.isSubhostingConfigured() && (await functionService.getDeploymentUrl())) ||
        localRuntime;

      // Build target URL with query string
      const targetUrl = new URL(`/${slug}`, baseUrl);
      targetUrl.search = new URL(req.url, `http://${req.headers.host}`).search;

      // Build headers, filtering out non-string values and overriding host
      const headers: Record<string, string> = {};
      for (const [key, value] of Object.entries(req.headers)) {
        if (typeof value === 'string') {
          headers[key] = value;
        }
      }
      headers.host = targetUrl.host;

      const response = await fetch(targetUrl, {
        method: req.method,
        headers,
        body: ['GET', 'HEAD'].includes(req.method) ? undefined : JSON.stringify(req.body),
      });

      // Read response as raw bytes to preserve binary data (images, PDFs, etc.)
      const responseBody = Buffer.from(await response.arrayBuffer());

      // Forward response headers, excluding:
      // - transfer-encoding, content-length: recalculated by Express
      // - connection: hop-by-hop header
      // - content-encoding: node-fetch already decompresses the response,
      //   so we must not tell the client it's still compressed
      const responseHeaders: Record<string, string> = {};
      for (const [key, value] of response.headers.entries()) {
        if (
          ['transfer-encoding', 'content-length', 'connection', 'content-encoding'].includes(key)
        ) {
          continue;
        }
        responseHeaders[key] = value;
      }

      res
        .status(response.status)
        .set(responseHeaders)
        .set('Access-Control-Allow-Origin', '*')
        .send(responseBody);
    } catch (error) {
      logger.error('Failed to proxy function', { slug, error: String(error) });
      res.status(502).json({ error: error instanceof Error ? error.message : String(error) });
    }
  });

  // Redirect root to dashboard login (only for non-insforge cloud environments)
  if (!isCloudEnvironment()) {
    app.get('/', (_req: Request, res: Response) => {
      res.redirect('/dashboard/login');
    });
  }

  // Serve main frontend if it exists
  const frontendPath = path.join(__dirname, 'frontend');
  if (fs.existsSync(frontendPath)) {
    app.use(express.static(frontendPath, { index: false }));
    // Catch all handler for SPA routes
    app.get(['/cloud*', '/dashboard*'], (_req: Request, res: Response) => {
      res.sendFile(path.join(frontendPath, 'index.html'));
    });
  } else {
    // Catch-all for 404 errors - Traditional REST format
    app.use('*', (req: Request, res: Response) => {
      res.status(404).json({
        error: 'NOT_FOUND',
        message: `Endpoint ${req.originalUrl} not found`,
        statusCode: 404,
        nextActions: 'Please check the API documentation for available endpoints',
      });
    });
  }

  app.use(errorMiddleware);
  await seedBackend();

  return app;
}

// Use PORT from config (already parsed from env, falls back to 7130)
const PORT = appConfig.app.port;

async function initializeServer() {
  try {
    const app = await createApp();
    const server = app.listen(PORT, () => {
      logger.info(`Backend API service listening on port ${PORT}`);
    });

    // Initialize Socket.IO service
    const socketService = SocketManager.getInstance();
    socketService.initialize(server);

    // Initialize RealtimeManager (pg_notify listener)
    const realtimeManager = RealtimeManager.getInstance();
    await realtimeManager.initialize();

    // Sync existing functions to Deno Deploy (non-blocking)
    const functionService = FunctionService.getInstance();
    functionService.syncDeployment().catch((err) => {
      logger.error('Failed to sync functions to Deno Deploy', {
        error: err instanceof Error ? err.message : String(err),
      });
    });

    TelemetryService.getInstance().start();
  } catch (error) {
    logger.error('Failed to initialize server', {
      error: error instanceof Error ? error.message : String(error),
      stack: error instanceof Error ? error.stack : undefined,
    });
    process.exit(1);
  }
}

void initializeServer();

async function cleanup() {
  logger.info('Shutting down gracefully...');

  try {
    const realtimeManager = RealtimeManager.getInstance();
    await realtimeManager.close();
  } catch (error) {
    logger.error('Error closing RealtimeManager', {
      error: error instanceof Error ? error.message : String(error),
    });
  }

  try {
    const socketService = SocketManager.getInstance();
    socketService.close();
  } catch (error) {
    logger.error('Error closing SocketManager', {
      error: error instanceof Error ? error.message : String(error),
    });
  }

  try {
    const oAuthPKCEService = OAuthPKCEService.getInstance();
    oAuthPKCEService.destroy();
  } catch (error) {
    logger.error('Error closing OAuthPKCEService', {
      error: error instanceof Error ? error.message : String(error),
    });
  }

  try {
    TelemetryService.getInstance().stop();
  } catch (error) {
    logger.error('Error closing TelemetryService', {
      error: error instanceof Error ? error.message : String(error),
    });
  }

  try {
    destroyEmailCooldownInterval();
  } catch (error) {
    logger.error('Error clearing email cooldown interval', {
      error: error instanceof Error ? error.message : String(error),
    });
  }

  process.exit(0);
}

process.on('SIGINT', () => void cleanup());
process.on('SIGTERM', () => void cleanup());
