// src/routes/health.routes.ts
/**
 * @file Health check endpoints implementing ADR-020: Health Checks and Liveness/Readiness Probes.
 *
 * Provides endpoints for:
 * - Liveness probe (/live) - Is the server process running?
 * - Readiness probe (/ready) - Is the server ready to accept traffic?
 * - Individual service health checks (db, redis, storage)
 */
import { Router, Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { checkTablesExist, getPoolStatus, getPool } from '../services/db/connection.db';
import { connection as redisConnection } from '../services/queueService.server';
import fs from 'node:fs/promises';
import { getSimpleWeekAndYear } from '../utils/dateUtils';
import { validateRequest } from '../middleware/validation.middleware';
import { sendSuccess, sendError, ErrorCode } from '../utils/apiResponse';

const router = Router();

// --- Types for Health Check Response ---

interface ServiceHealth {
  status: 'healthy' | 'degraded' | 'unhealthy';
  latency?: number;
  message?: string;
  details?: Record<string, unknown>;
}

interface ReadinessResponse {
  status: 'healthy' | 'degraded' | 'unhealthy';
  timestamp: string;
  uptime: number;
  services: {
    database: ServiceHealth;
    redis: ServiceHealth;
    storage: ServiceHealth;
  };
}

// --- Helper Functions ---

/**
 * Checks database connectivity with timing.
 */
async function checkDatabase(): Promise<ServiceHealth> {
  const start = Date.now();
  try {
    const pool = getPool();
    await pool.query('SELECT 1');
    const latency = Date.now() - start;
    const poolStatus = getPoolStatus();

    // Consider degraded if waiting connections > 3
    const status = poolStatus.waitingCount > 3 ? 'degraded' : 'healthy';

    return {
      status,
      latency,
      details: {
        totalConnections: poolStatus.totalCount,
        idleConnections: poolStatus.idleCount,
        waitingConnections: poolStatus.waitingCount,
      },
    };
  } catch (error) {
    return {
      status: 'unhealthy',
      latency: Date.now() - start,
      message: error instanceof Error ? error.message : 'Database connection failed',
    };
  }
}

/**
 * Checks Redis connectivity with timing.
 */
async function checkRedis(): Promise<ServiceHealth> {
  const start = Date.now();
  try {
    const reply = await redisConnection.ping();
    const latency = Date.now() - start;

    if (reply === 'PONG') {
      return { status: 'healthy', latency };
    }
    return {
      status: 'unhealthy',
      latency,
      message: `Unexpected ping response: ${reply}`,
    };
  } catch (error) {
    return {
      status: 'unhealthy',
      latency: Date.now() - start,
      message: error instanceof Error ? error.message : 'Redis connection failed',
    };
  }
}

/**
 * Checks storage accessibility.
 */
async function checkStorage(): Promise<ServiceHealth> {
  const storagePath =
    process.env.STORAGE_PATH || '/var/www/flyer-crawler.projectium.com/flyer-images';
  const start = Date.now();
  try {
    await fs.access(storagePath, fs.constants.W_OK);
    return {
      status: 'healthy',
      latency: Date.now() - start,
      details: { path: storagePath },
    };
  } catch {
    return {
      status: 'unhealthy',
      latency: Date.now() - start,
      message: `Storage not accessible: ${storagePath}`,
    };
  }
}

// --- Zod Schemas for Health Routes (as per ADR-003) ---
// These routes do not expect any input, so we define empty schemas
// to maintain a consistent validation pattern across the application.
const emptySchema = z.object({});

/**
 * @openapi
 * /health/ping:
 *   get:
 *     summary: Simple ping endpoint
 *     description: Returns a pong response to verify server is responsive. Use this for basic connectivity checks.
 *     tags:
 *       - Health
 *     responses:
 *       200:
 *         description: Server is responsive
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     message:
 *                       type: string
 *                       example: pong
 */
router.get('/ping', validateRequest(emptySchema), (_req: Request, res: Response) => {
  return sendSuccess(res, { message: 'pong' });
});

// =============================================================================
// KUBERNETES PROBES (ADR-020)
// =============================================================================

/**
 * @openapi
 * /health/live:
 *   get:
 *     summary: Liveness probe
 *     description: |
 *       Returns 200 OK if the server process is running.
 *       If this fails, the orchestrator should restart the container.
 *       This endpoint is intentionally simple and has no external dependencies.
 *     tags:
 *       - Health
 *     responses:
 *       200:
 *         description: Server process is alive
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     status:
 *                       type: string
 *                       example: ok
 *                     timestamp:
 *                       type: string
 *                       format: date-time
 */
router.get('/live', validateRequest(emptySchema), (_req: Request, res: Response) => {
  return sendSuccess(res, {
    status: 'ok',
    timestamp: new Date().toISOString(),
  });
});

/**
 * @openapi
 * /health/ready:
 *   get:
 *     summary: Readiness probe
 *     description: |
 *       Returns 200 OK if the server is ready to accept traffic.
 *       Checks all critical dependencies (database, Redis, storage).
 *       If this fails, the orchestrator should remove the container from the load balancer.
 *     tags:
 *       - Health
 *     responses:
 *       200:
 *         description: Server is ready to accept traffic
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     status:
 *                       type: string
 *                       enum: [healthy, degraded, unhealthy]
 *                     timestamp:
 *                       type: string
 *                       format: date-time
 *                     uptime:
 *                       type: number
 *                       description: Server uptime in seconds
 *                     services:
 *                       type: object
 *                       properties:
 *                         database:
 *                           $ref: '#/components/schemas/ServiceHealth'
 *                         redis:
 *                           $ref: '#/components/schemas/ServiceHealth'
 *                         storage:
 *                           $ref: '#/components/schemas/ServiceHealth'
 *       503:
 *         description: Service is unhealthy and should not receive traffic
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ErrorResponse'
 */
router.get('/ready', validateRequest(emptySchema), async (req: Request, res: Response) => {
  // Check all services in parallel for speed
  const [database, redis, storage] = await Promise.all([
    checkDatabase(),
    checkRedis(),
    checkStorage(),
  ]);

  // Determine overall status
  // - 'healthy' if all critical services (db, redis) are healthy
  // - 'degraded' if any service is degraded but none unhealthy
  // - 'unhealthy' if any critical service is unhealthy
  const criticalServices = [database, redis];
  const allServices = [database, redis, storage];

  let overallStatus: 'healthy' | 'degraded' | 'unhealthy' = 'healthy';

  if (criticalServices.some((s) => s.status === 'unhealthy')) {
    overallStatus = 'unhealthy';
  } else if (allServices.some((s) => s.status === 'degraded')) {
    overallStatus = 'degraded';
  }

  const response: ReadinessResponse = {
    status: overallStatus,
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    services: {
      database,
      redis,
      storage,
    },
  };

  // Return appropriate HTTP status code
  // 200 = healthy or degraded (can still handle traffic)
  // 503 = unhealthy (should not receive traffic)
  if (overallStatus === 'unhealthy') {
    return sendError(res, ErrorCode.SERVICE_UNAVAILABLE, 'Service unhealthy', 503, response);
  }
  return sendSuccess(res, response);
});

/**
 * GET /api/health/startup - Startup probe for container orchestration.
 *
 * Similar to readiness but used during container startup.
 * The orchestrator will not send liveness/readiness probes until this succeeds.
 * This allows for longer initialization times without triggering restarts.
 */
router.get('/startup', validateRequest(emptySchema), async (req: Request, res: Response) => {
  // For startup, we only check database connectivity
  // Redis and storage can be checked later in readiness
  const database = await checkDatabase();

  if (database.status === 'unhealthy') {
    return sendError(res, ErrorCode.SERVICE_UNAVAILABLE, 'Waiting for database connection', 503, {
      status: 'starting',
      database,
    });
  }

  return sendSuccess(res, {
    status: 'started',
    timestamp: new Date().toISOString(),
    database,
  });
});

/**
 * GET /api/health/db-schema - Checks if all essential database tables exist.
 * This is a critical check to ensure the database schema is correctly set up.
 */
router.get('/db-schema', validateRequest(emptySchema), async (req, res, next: NextFunction) => {
  try {
    const requiredTables = ['users', 'profiles', 'flyers', 'flyer_items', 'stores'];
    const missingTables = await checkTablesExist(requiredTables);

    if (missingTables.length > 0) {
      // Create a new error to be handled by the global error handler
      return next(
        new Error(`Database schema check failed. Missing tables: ${missingTables.join(', ')}.`),
      );
    }
    return sendSuccess(res, { message: 'All required database tables exist.' });
  } catch (error: unknown) {
    if (error instanceof Error) {
      return next(error);
    }
    const message =
      (error as { message?: string })?.message ||
      'An unknown error occurred during DB schema check.';
    return next(new Error(message));
  }
});

/**
 * GET /api/health/storage - Verifies that the application's file storage path is accessible and writable.
 * This is important for features like file uploads.
 */
router.get('/storage', validateRequest(emptySchema), async (req, res, next: NextFunction) => {
  const storagePath =
    process.env.STORAGE_PATH || '/var/www/flyer-crawler.projectium.com/flyer-images';
  try {
    await fs.access(storagePath, fs.constants.W_OK); // Use fs.promises
    return sendSuccess(res, {
      message: `Storage directory '${storagePath}' is accessible and writable.`,
    });
  } catch {
    next(
      new Error(
        `Storage check failed. Ensure the directory '${storagePath}' exists and is writable by the application.`,
      ),
    );
  }
});

/**
 * GET /api/health/db-pool - Checks the status of the database connection pool.
 * This helps diagnose issues related to database connection saturation.
 */
router.get(
  '/db-pool',
  validateRequest(emptySchema),
  (req: Request, res: Response, next: NextFunction) => {
    try {
      const status = getPoolStatus();
      const isHealthy = status.waitingCount < 5;
      const message = `Pool Status: ${status.totalCount} total, ${status.idleCount} idle, ${status.waitingCount} waiting.`;

      if (isHealthy) {
        return sendSuccess(res, { message, ...status });
      } else {
        req.log.warn(`Database pool health check shows high waiting count: ${status.waitingCount}`);
        return sendError(
          res,
          ErrorCode.INTERNAL_ERROR,
          `Pool may be under stress. ${message}`,
          500,
          status,
        );
      }
    } catch (error: unknown) {
      if (error instanceof Error) {
        return next(error);
      }
      const message =
        (error as { message?: string })?.message ||
        'An unknown error occurred during DB pool check.';
      return next(new Error(message));
    }
  },
);

/**
 * GET /api/health/time - Returns the server's current time, year, and week number.
 * Useful for verifying time synchronization and for features dependent on week numbers.
 */
router.get('/time', validateRequest(emptySchema), (req: Request, res: Response) => {
  const now = new Date();
  const { year, week } = getSimpleWeekAndYear(now);
  return sendSuccess(res, {
    currentTime: now.toISOString(),
    year,
    week,
  });
});

/**
 * GET /api/health/redis - Checks the health of the Redis connection.
 */
router.get(
  '/redis',
  validateRequest(emptySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const reply = await redisConnection.ping();
      if (reply === 'PONG') {
        return sendSuccess(res, { message: 'Redis connection is healthy.' });
      }
      throw new Error(`Unexpected Redis ping response: ${reply}`); // This will be caught below
    } catch (error: unknown) {
      if (error instanceof Error) {
        return next(error);
      }
      const message =
        (error as { message?: string })?.message ||
        'An unknown error occurred during Redis health check.';
      return next(new Error(message));
    }
  },
);

export default router;
