import express, { Express, Request, Response, NextFunction, type RequestHandler } from 'express';
import { Server } from 'http';
import path from 'path';
import { readFileSync, existsSync } from 'fs';
import cors from 'cors';
import compression from 'compression';
import { apiRateLimiter, strictRateLimiter } from './middleware/rate-limit';
import tokenRoutes from './routes/tokens';
import reportRoutes from './routes/report';
import adminRoutes from './routes/admin';
import configRoutes from './routes/config';
import bridgeRoutes from './routes/bridge';
import quoteRoutes from './routes/quote';
import routeTreeRoutes from './routes/routes';
import tokenMappingRoutes from './routes/token-mapping';
import polygonMapperReportRoutes from './routes/polygon-mapper';
import officialProtocolsReportRoutes from './routes/official-protocols';
import explorerRegistryOpenApiRoutes from './routes/explorer-registry-openapi';
import heatmapRoutes from './routes/heatmap';
import arbitrageRoutes from './routes/arbitrage';
import aggregatorRouteMatrixRoutes from './routes/aggregator-routes';
import partnerPayloadRoutes from './routes/partner-payloads';
import plannerV2Routes from './routes/planner-v2';
import omnlRoutes from './routes/omnl';
import omnlIpsasRoutes from './routes/omnl-ipsas';
import omnlPayoutJournalRoutes from './routes/omnl-payout-journal-routes';
import omnlComplianceRoutes from './routes/omnl-compliance-routes';
import omnlTerminalRoutes from './routes/omnl-terminal-routes';
import checkpointRoutes from './routes/checkpoint';
import reserveCapacityRoutes from './routes/reserve-capacity';
import hoLiquidityRoutes from './routes/ho-liquidity-routes';
import metamaskPriceRoutes from './routes/metamask-prices';
import { MultiChainIndexer } from '../indexer/chain-indexer';
import { OmnlEventPoller } from '../indexer/omnl-event-poller';
import { getDatabasePool } from '../database/client';
import winston from 'winston';

// Setup logger
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      ),
    }),
  ],
});

export class ApiServer {
  private app: Express;
  private port: number;
  private indexerEnabled: boolean;
  private indexer: MultiChainIndexer | null;
  private omnlPoller: OmnlEventPoller | null;
  private server: Server | null;

  private resolveTrustProxySetting(): boolean | number | string {
    const raw = (process.env.EXPRESS_TRUST_PROXY ?? process.env.TRUST_PROXY ?? '1').trim();
    const normalized = raw.toLowerCase();

    if (normalized === 'true') return true;
    if (normalized === 'false') return false;
    if (/^\d+$/.test(raw)) return parseInt(raw, 10);
    return raw;
  }

  constructor() {
    this.app = express();
    this.port = parseInt(process.env.PORT || '3000', 10);
    this.indexerEnabled = this.resolveFeatureFlag('ENABLE_INDEXER', true);
    this.indexer = this.indexerEnabled ? new MultiChainIndexer() : null;
    this.omnlPoller = this.resolveFeatureFlag('ENABLE_OMNL_EVENT_POLLER', false) ? new OmnlEventPoller() : null;
    this.server = null;

    this.setupMiddleware();
    this.setupRoutes();
    this.setupErrorHandling();
  }

  private resolveFeatureFlag(name: string, fallback: boolean): boolean {
    const raw = (process.env[name] || '').trim().toLowerCase();
    if (!raw) return fallback;
    if (['1', 'true', 'yes', 'on'].includes(raw)) return true;
    if (['0', 'false', 'no', 'off'].includes(raw)) return false;
    return fallback;
  }

  private setupMiddleware(): void {
    const trustProxy = this.resolveTrustProxySetting();
    this.app.set('trust proxy', trustProxy);

    // CORS
    this.app.use(cors());

    // Compression (Express 5 typings — runtime-compatible)
    this.app.use(compression() as unknown as RequestHandler);

    // Body parsing
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));

    // Rate limiting
    this.app.use('/api/v1', apiRateLimiter);

    // Request logging
    this.app.use((req: Request, res: Response, next: NextFunction) => {
      logger.info(`${req.method} ${req.path}`, {
        ip: req.ip,
        forwardedFor: req.get('x-forwarded-for'),
        trustProxy,
        userAgent: req.get('user-agent'),
      });
      next();
    });

    const publicPath = path.join(__dirname, '../../public');
    if (existsSync(publicPath)) {
      const staticOpts = {
        immutable: true,
        maxAge: '1d',
      } as const;
      this.app.use('/static', express.static(publicPath, staticOpts));
      this.app.use('/omnl/static', express.static(publicPath, staticOpts));
      this.app.use('/reserve/static', express.static(publicPath, staticOpts));
      this.app.use(
        '/chain138-attestation',
        express.static(path.join(publicPath, 'chain138-attestation'), staticOpts)
      );
    }
  }

  private setupRoutes(): void {
    // Health check
    this.app.get('/health', async (req: Request, res: Response) => {
      if (process.env.OMNL_SETTLEMENT_TERMINAL_ONLY === '1') {
        res.json({
          status: 'healthy',
          mode: 'omnl-settlement-terminal-only',
          timestamp: new Date().toISOString(),
          services: {
            database: 'skipped',
            indexer: 'disabled',
          },
        });
        return;
      }
      try {
        // Check database connection
        const pool = getDatabasePool();
        await pool.query('SELECT 1');

        res.json({
          status: 'healthy',
          timestamp: new Date().toISOString(),
          services: {
            database: 'connected',
            indexer: this.indexerEnabled ? 'running' : 'disabled',
          },
        });
      } catch (error) {
        res.status(503).json({
          status: 'unhealthy',
          timestamp: new Date().toISOString(),
          error: error instanceof Error ? error.message : 'Unknown error',
        });
      }
    });

    const dashboardPath = path.join(__dirname, '../../public/omnl-dashboard.html');
    const complianceConsolePath = path.join(__dirname, '../../public/omnl-compliance-console.html');
    const settlementTerminalPath = path.join(__dirname, '../../public/omnl-settlement-terminal.html');
    const reserveDashboardPath = path.join(__dirname, '../../public/reserve-dashboard.html');
    const reserveInstitutionalPath = path.join(__dirname, '../../public/reserve-institutional.html');

    const authorizeOmnlHtml = (req: Request, res: Response): boolean => {
      const tok = process.env.OMNL_DASHBOARD_TOKEN?.trim();
      if (!tok) return true;
      const q = String(req.query.access_token ?? '').trim();
      const h = String(req.headers['x-omnl-dashboard-token'] ?? '').trim();
      if (q !== tok && h !== tok) {
        res.status(401).type('text/plain').send('Unauthorized: set access_token query or X-OMNL-Dashboard-Token header');
        return false;
      }
      return true;
    };

    this.app.get('/omnl/compliance', (req: Request, res: Response) => {
      if (!authorizeOmnlHtml(req, res)) return;
      if (!existsSync(complianceConsolePath)) {
        res.status(404).type('text/plain').send('omnl-compliance-console.html missing');
        return;
      }
      res.type('html').send(readFileSync(complianceConsolePath, 'utf8'));
    });

    this.app.get('/omnl/dashboard', (req: Request, res: Response) => {
      if (!authorizeOmnlHtml(req, res)) return;
      if (!existsSync(dashboardPath)) {
        res.status(404).type('text/plain').send('omnl-dashboard.html missing');
        return;
      }
      res.type('html').send(readFileSync(dashboardPath, 'utf8'));
    });

    this.app.get('/omnl/terminal', (req: Request, res: Response) => {
      if (!authorizeOmnlHtml(req, res)) return;
      if (!existsSync(settlementTerminalPath)) {
        res.status(404).type('text/plain').send('omnl-settlement-terminal.html missing');
        return;
      }
      res.type('html').send(readFileSync(settlementTerminalPath, 'utf8'));
    });

    /** Public semi-public reserve capacity dashboard (no auth). */
    this.app.get('/reserve', (_req: Request, res: Response) => {
      if (!existsSync(reserveDashboardPath)) {
        res.status(404).type('text/plain').send('reserve-dashboard.html missing');
        return;
      }
      res.type('html').send(readFileSync(reserveDashboardPath, 'utf8'));
    });

    /** Institutional wrapper (same API; used by reserve.d-bis.org NPM upstream). */
    this.app.get('/reserve/institutional', (_req: Request, res: Response) => {
      const file = existsSync(reserveInstitutionalPath) ? reserveInstitutionalPath : reserveDashboardPath;
      if (!existsSync(file)) {
        res.status(404).type('text/plain').send('reserve dashboard missing');
        return;
      }
      res.type('html').send(readFileSync(file, 'utf8'));
    });

    // Public API catalog (register before routers so GET /api/v1 is not swallowed by middleware-only mounts)
    const sendApiV1Catalog = (_req: Request, res: Response) => {
      res.json({
        service: 'token-aggregation',
        version: '1.0.0',
        note: 'Prefix paths with your public API origin (e.g. https://explorer.d-bis.org).',
        paths: {
          catalog: '/api/v1',
          health: '/health',
          chains: '/api/v1/chains',
          networks: '/api/v1/networks',
          config: '/api/v1/config',
          tokens: '/api/v1/tokens',
          tokenDetail: '/api/v1/tokens/{address}',
          quote: '/api/v1/quote',
          bridgeRoutes: '/api/v1/bridge/routes',
          bridgeMessages: '/api/v1/bridge/messages?lane=138-monad',
          bridgeStatus: '/api/v1/bridge/status',
          bridgeMetrics: '/api/v1/bridge/metrics',
          bridgePreflight: '/api/v1/bridge/preflight',
          tokenMappingPairs: '/api/v1/token-mapping/pairs',
          tokenMappingResolve: '/api/v1/token-mapping/resolve',
          reportPolygonMapper: '/api/v1/report/polygon-mapper',
          reportPolygonMapperOfficialExport: '/api/v1/report/polygon-mapper/official-export',
          reportOfficialProtocols: '/api/v1/report/official-protocols',
          reportOpenApi: '/api/v1/report/openapi.json',
          reportTokenList: '/api/v1/report/token-list',
          metamaskSpotPricesV2: '/api/v1/prices/metamask/v2/chains/{chainId}/spot-prices',
          metamaskPriceFeedReport: '/api/v1/report/metamask-price-feed?chainId=138',
          reportTokenListEthereumMainnet: '/api/v1/report/token-list/static/ethereum-mainnet',
          routesTree: '/api/v1/routes/tree',
          plannerProvidersCapabilities: '/api/v2/providers/capabilities',
          plannerRoutesPlan: '/api/v2/routes/plan',
          plannerIntentsPlan: '/api/v2/intents/plan',
          plannerInternalExecutionPlan: '/api/v2/routes/internal-execution-plan',
          reserveCapacity: '/api/v1/reserve/capacity',
          reserveProofIndex: '/api/v1/reserve/proof-index',
          reserveDashboard: '/reserve',
        },
      });
    };
    this.app.get('/api/v1', sendApiV1Catalog);
    this.app.get('/api/v1/', sendApiV1Catalog);

    // API routes
    this.app.use('/api/v1', tokenRoutes);
    this.app.use('/api/v1', configRoutes);
    this.app.use('/api/v1/report', reportRoutes);
    this.app.use('/api/v1/report', polygonMapperReportRoutes);
    this.app.use('/api/v1/report', officialProtocolsReportRoutes);
    this.app.use('/api/v1/report', explorerRegistryOpenApiRoutes);
    this.app.use('/api/v1/bridge', bridgeRoutes);
    this.app.use('/api/v1', routeTreeRoutes);
    this.app.use('/api/v1/token-mapping', tokenMappingRoutes);
    this.app.use('/api/v1', quoteRoutes);
    this.app.use('/api/v1', heatmapRoutes);
    this.app.use('/api/v1', arbitrageRoutes);
    this.app.use('/api/v1', aggregatorRouteMatrixRoutes);
    this.app.use('/api/v1', partnerPayloadRoutes);
    this.app.use('/api/v1', checkpointRoutes);
    this.app.use('/api/v1', reserveCapacityRoutes);
    this.app.use('/api/v1', hoLiquidityRoutes);
    this.app.use('/api/v1/prices/metamask', metamaskPriceRoutes);
    this.app.use('/api/v1', omnlComplianceRoutes);
    this.app.use('/api/v1', omnlTerminalRoutes);
    this.app.use('/api/v1', omnlRoutes);
    this.app.use('/api/v1', omnlIpsasRoutes);
    this.app.use('/api/v1', omnlPayoutJournalRoutes);
    this.app.use('/api/v2', plannerV2Routes);

    // Admin routes (stricter rate limit)
    this.app.use('/api/v1/admin', strictRateLimiter, adminRoutes);

    // Root
    this.app.get('/', (req: Request, res: Response) => {
      res.json({
        name: 'Token Aggregation Service',
        version: '1.0.0',
        endpoints: {
          health: '/health',
          api: '/api/v1',
          apiV2: '/api/v2',
          omnlOpenApi: '/api/v1/omnl/openapi.json',
          omnlCatalog: '/api/v1/omnl/catalog',
          omnlDashboard: '/omnl/dashboard',
          reserveDashboard: '/reserve',
        },
      });
    });
  }

  private setupErrorHandling(): void {
    // 404 handler
    this.app.use((req: Request, res: Response) => {
      res.status(404).json({ error: 'Not found' });
    });

    // Error handler
    // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Express error handler requires 4-arg signature
    this.app.use((err: Error, req: Request, res: Response, _next: NextFunction) => {
      logger.error('Unhandled error:', err);
      res.status(500).json({
        error: 'Internal server error',
        message: process.env.NODE_ENV === 'development' ? err.message : undefined,
      });
    });
  }

  async start(): Promise<void> {
    try {
      // Start server
      this.server = this.app.listen(this.port, () => {
        logger.info(`Token Aggregation Service listening on port ${this.port}`);
        logger.info(`Health check: http://localhost:${this.port}/health`);
        logger.info(`API: http://localhost:${this.port}/api/v1`);
      });

      if (this.indexer) {
        this.indexer
          .initialize()
          .then(() => this.indexer?.startAll())
          .catch((error) => {
            logger.error('Token aggregation indexer failed after API startup:', error);
          });
      } else {
        logger.info('Token aggregation indexer disabled by ENABLE_INDEXER flag');
      }

      this.omnlPoller?.start();
    } catch (error) {
      logger.error('Failed to start server:', error);
      process.exit(1);
    }
  }

  async stop(): Promise<void> {
    this.omnlPoller?.stop();
    this.indexer?.stopAll();
    if (this.server) {
      await new Promise<void>((resolve, reject) => {
        this.server?.close((error) => {
          if (error) {
            reject(error);
            return;
          }
          resolve();
        });
      });
      this.server = null;
    }
    logger.info('Server stopped');
  }
}
