// ABOUTME: Application entry point that initializes services and starts the HTTP server
// ABOUTME: Manages lifecycle of Prisma, TAB API client, schedulers, and metrics collection

import logger from './utils/logger';
import config from './config';
import express from 'express';
import promClient from 'prom-client';
import { PrismaClient } from '@prisma/client';
import { TabApiClient } from './api/tab';
import { SchedulerManager } from './schedulers';
import { DataQualityCollector } from './metrics/data-quality';
import { ObservabilityService } from './services/observability-service';
import { registerRoutes } from './routes';
import { correlationIdMiddleware, requestLoggingMiddleware } from './middleware/request-logging';

// Use default Prometheus registry (shared with TabApiClient and MeetingService)
const register = promClient.register;

// Add default metrics (CPU, memory, etc.)
promClient.collectDefaultMetrics({ register });

// Global instances
let prisma: PrismaClient;
let apiClient: TabApiClient;
let schedulerManager: SchedulerManager;
let dataQualityCollector: DataQualityCollector;
let observabilityService: ObservabilityService;
let dataQualityInterval: NodeJS.Timeout;

async function main() {
  logger.info('🏇 Horse Racing Scraper starting...');
  logger.info({ env: config.env }, 'Environment');
  logger.info({ features: config.features }, 'Features');

  // Initialize Prisma
  prisma = new PrismaClient({
    log: config.env === 'development' ? ['query', 'error', 'warn'] : ['error'],
  });

  try {
    await prisma.$connect();
    logger.info('📊 Database connected successfully');
  } catch (error) {
    logger.error({
      error: error instanceof Error ? error.message : 'Unknown error',
    }, 'Failed to connect to database');
    process.exit(1);
  }

  // Initialize TAB API Client
  apiClient = new TabApiClient({
    baseUrl: config.tabApi.baseUrl,
    rateLimitPerMinute: config.rateLimiting.perMinute,
    maxRetries: config.rateLimiting.retryAttempts,
    retryDelay: config.rateLimiting.retryDelayMs,
    timeout: 10000,
  });
  logger.info('🔌 TAB API Client initialized');

  // Initialize Scheduler Manager
  schedulerManager = new SchedulerManager(apiClient, prisma);
  schedulerManager.startAll();
  logger.info('⏰ Schedulers started');

  // Initialize Data Quality Collector
  dataQualityCollector = new DataQualityCollector(prisma);

  // Collect data quality metrics every 60 seconds
  dataQualityInterval = setInterval(async () => {
    try {
      await dataQualityCollector.collect();
    } catch (error) {
      logger.error({
        error: error instanceof Error ? error.message : 'Unknown error',
      }, 'Data quality collection failed');
    }
  }, 60000);

  // Run initial collection
  await dataQualityCollector.collect();
  logger.info('📊 Data quality collector started');

  // Initialize Observability Service
  observabilityService = new ObservabilityService(prisma);
  logger.info('🔍 Observability service initialized');

  // Create Express app for metrics
  const app = express();

  // Request logging and correlation ID middleware (applied before all routes)
  app.use(correlationIdMiddleware);
  app.use(requestLoggingMiddleware);

  // Health check endpoint
  app.get('/health', async (_req, res) => {
    try {
      await prisma.$queryRaw`SELECT 1`;
      const schedulerStatus = schedulerManager.getStatus();

      res.json({
        status: 'healthy',
        timestamp: new Date().toISOString(),
        database: 'connected',
        schedulers: schedulerStatus,
      });
    } catch (error) {
      res.status(503).json({
        status: 'unhealthy',
        timestamp: new Date().toISOString(),
        database: 'disconnected',
        error: error instanceof Error ? error.message : 'Unknown error',
      });
    }
  });

  // Metrics endpoint for Prometheus
  app.get('/metrics', async (_req, res) => {
    res.set('Content-Type', register.contentType);
    res.end(await register.metrics());
  });

  // Scheduler status endpoint
  app.get('/schedulers', (_req, res) => {
    const status = schedulerManager.getStatus();
    res.json(status);
  });

  // Manual trigger endpoints for testing
  app.post('/trigger/:schedulerType', async (req, res) => {
    const { schedulerType } = req.params;

    try {
      const scheduler = schedulerManager.getScheduler(schedulerType as any);

      if (!scheduler) {
        return res.status(404).json({
          error: 'Scheduler not found',
          schedulerType,
        });
      }

      const result = await (scheduler as any).triggerManually();

      return res.json({
        success: true,
        schedulerType,
        result,
      });
    } catch (error) {
      return res.status(500).json({
        error: error instanceof Error ? error.message : 'Unknown error',
        schedulerType,
      });
    }
  });

  // Observability API endpoints
  app.get('/api/races/upcoming', async (req, res) => {
    try {
      const hoursAhead = req.query.hoursAhead ? parseInt(req.query.hoursAhead as string) : 2;

      if (isNaN(hoursAhead) || hoursAhead < 1 || hoursAhead > 24) {
        return res.status(400).json({
          error: 'Invalid hoursAhead parameter. Must be between 1 and 24.',
        });
      }

      const races = await observabilityService.getUpcomingRaces(hoursAhead);

      const nextScrapeTypes = [
        { type: 'pre_race_t60', nextRunAt: new Date(Date.now() + 5 * 60 * 1000).toISOString() },
        { type: 'pre_race_t15', nextRunAt: new Date(Date.now() + 5 * 60 * 1000).toISOString() },
      ];

      return res.json({
        races,
        metadata: {
          totalRaces: races.length,
          nextScrapeTypes,
        },
      });
    } catch (error) {
      logger.error({ error }, 'Failed to get upcoming races');
      return res.status(500).json({
        error: error instanceof Error ? error.message : 'Unknown error',
      });
    }
  });

  app.get('/api/races/timeline', async (req, res) => {
    try {
      let date: Date;

      if (req.query.date) {
        date = new Date(req.query.date as string);
        if (isNaN(date.getTime())) {
          return res.status(400).json({
            error: 'Invalid date parameter. Use YYYY-MM-DD format.',
          });
        }
      } else {
        date = new Date();
      }

      const timeline = await observabilityService.getRaceTimeline(date);

      return res.json(timeline);
    } catch (error) {
      logger.error({ error }, 'Failed to get race timeline');
      return res.status(500).json({
        error: error instanceof Error ? error.message : 'Unknown error',
      });
    }
  });

  app.get('/api/data-freshness', async (_req, res) => {
    try {
      const freshness = await observabilityService.calculateDataFreshness();

      return res.json(freshness);
    } catch (error) {
      logger.error({ error }, 'Failed to calculate data freshness');
      return res.status(500).json({
        error: error instanceof Error ? error.message : 'Unknown error',
      });
    }
  });

  // Register REST API routes for ingested data access
  registerRoutes(app, prisma);

  // Start metrics server on port 9090
  const metricsPort = 9090;
  app.listen(metricsPort, '0.0.0.0', () => {
    logger.info(`📊 Metrics server listening on port ${metricsPort}`);
    logger.info(`   Health: http://localhost:${metricsPort}/health`);
    logger.info(`   Metrics: http://localhost:${metricsPort}/metrics`);
    logger.info(`   Schedulers: http://localhost:${metricsPort}/schedulers`);
    logger.info(`   Upcoming Races: http://localhost:${metricsPort}/api/races/upcoming`);
    logger.info(`   Race Timeline: http://localhost:${metricsPort}/api/races/timeline`);
    logger.info(`   Data Freshness: http://localhost:${metricsPort}/api/data-freshness`);
  });

  logger.info('✅ Application initialized successfully');
  logger.info('📊 Real API metrics will be collected from scheduler operations');
}

// Graceful shutdown
process.on('SIGTERM', async () => {
  logger.info('SIGTERM received, shutting down gracefully');

  if (dataQualityInterval) {
    clearInterval(dataQualityInterval);
  }

  if (schedulerManager) {
    schedulerManager.stopAll();
  }

  if (prisma) {
    await prisma.$disconnect();
  }

  process.exit(0);
});

process.on('SIGINT', async () => {
  logger.info('SIGINT received, shutting down gracefully');

  if (dataQualityInterval) {
    clearInterval(dataQualityInterval);
  }

  if (schedulerManager) {
    schedulerManager.stopAll();
  }

  if (prisma) {
    await prisma.$disconnect();
  }

  process.exit(0);
});

main().catch(error => {
  logger.error({ error }, 'Failed to start application');
  process.exit(1);
});
