﻿import express, { Express } from 'express';
import cors from 'cors';
import { ENV } from './config/env';
import { connectDatabase } from './config/database';
import { errorHandler, notFoundHandler } from './middleware/error.middleware';

// Routes
import authRoutes from './routes/auth.routes';
import assetsRoutes from './routes/assets.routes';
import projectsRoutes from './routes/projects.routes';
import ticketsRoutes from './routes/tickets.routes';
import contractsRoutes from './routes/contracts.routes';
import workOrdersRoutes from './routes/workOrders.routes';
import pmSchedulesRoutes from './routes/pmSchedules.routes';
import inventoryRoutes from './routes/inventory.routes';
import complianceRoutes from './routes/compliance.routes';
import hseRoutes from './routes/hse.routes';
import risksRoutes from './routes/risks.routes';
import reportsRoutes from './routes/reports.routes';
import usersRoutes from './routes/users.routes';
import logsRoutes from './routes/logs.routes';
import settingsRoutes from './routes/settings.routes';
import companiesRoutes from './routes/companies.routes';
import rolesRoutes from './routes/roles.routes';
import customerGroupsRoutes from './routes/customerGroups.routes';
import contractTypesRoutes from './routes/contractTypes.routes';
import alertsRoutes from './routes/alerts.routes';
import kpiRoutes from './routes/kpi.routes';
import integrationsRoutes from './routes/integrations.routes';

const app: Express = express();

// Trust proxy to capture client WAN IP through Apache/Nginx
app.set('trust proxy', 1);

// Middleware
app.use(cors({
  origin: ENV.CORS_ORIGIN.split(',').map(origin => origin.trim()),
  credentials: true,
}));

// Increase body size limit to handle large screenshots (10MB)
app.use(express.json({ limit: '25mb' }));
app.use(express.urlencoded({ extended: true, limit: '25mb' }));
app.use('/uploads', express.static('uploads'));

// Health check
app.get('/health', (req, res) => {
  res.json({
    success: true,
    message: 'Server is running',
    timestamp: new Date().toISOString(),
  });
});

// API Routes - exposed at /api/... to match Apache proxy configuration
// Apache proxies /api/ → http://127.0.0.1:2123/api/
app.use(`${ENV.API_PREFIX}/auth`, authRoutes);
app.use(`${ENV.API_PREFIX}/assets`, assetsRoutes);
app.use(`${ENV.API_PREFIX}/projects`, projectsRoutes);
app.use(`${ENV.API_PREFIX}/tickets`, ticketsRoutes);
app.use(`${ENV.API_PREFIX}/contracts`, contractsRoutes);
app.use(`${ENV.API_PREFIX}/work-orders`, workOrdersRoutes);
app.use(`${ENV.API_PREFIX}/pm-schedules`, pmSchedulesRoutes);
app.use(`${ENV.API_PREFIX}/inventory`, inventoryRoutes);
app.use(`${ENV.API_PREFIX}/compliance`, complianceRoutes);
app.use(`${ENV.API_PREFIX}/hse-incidents`, hseRoutes);
app.use(`${ENV.API_PREFIX}/risks`, risksRoutes);
app.use(`${ENV.API_PREFIX}/reports`, reportsRoutes);
app.use(`${ENV.API_PREFIX}/users`, usersRoutes);
app.use(`${ENV.API_PREFIX}/logs`, logsRoutes);
app.use(`${ENV.API_PREFIX}/settings`, settingsRoutes);
app.use(`${ENV.API_PREFIX}/companies`, companiesRoutes);
app.use(`${ENV.API_PREFIX}/roles`, rolesRoutes);
app.use(`${ENV.API_PREFIX}/customer-groups`, customerGroupsRoutes);
app.use(`${ENV.API_PREFIX}/contract-types`, contractTypesRoutes);
app.use(`${ENV.API_PREFIX}/alerts`, alertsRoutes);
app.use(`${ENV.API_PREFIX}/kpis`, kpiRoutes);

// CSM <-> AOM server-to-server integration (service-token auth, mounted at /api/integrations/csm/*)
app.use(`${ENV.API_PREFIX}/integrations`, integrationsRoutes);

// Backward compatibility: Also support /api/v1 routes for versioning
app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/assets', assetsRoutes);
app.use('/api/v1/projects', projectsRoutes);
app.use('/api/v1/tickets', ticketsRoutes);
app.use('/api/v1/contracts', contractsRoutes);
app.use('/api/v1/work-orders', workOrdersRoutes);
app.use('/api/v1/pm-schedules', pmSchedulesRoutes);
app.use('/api/v1/inventory', inventoryRoutes);
app.use('/api/v1/compliance', complianceRoutes);
app.use('/api/v1/hse-incidents', hseRoutes);
app.use('/api/v1/risks', risksRoutes);
app.use('/api/v1/reports', reportsRoutes);
app.use('/api/v1/users', usersRoutes);
app.use('/api/v1/logs', logsRoutes);
app.use('/api/v1/settings', settingsRoutes);
app.use('/api/v1/companies', companiesRoutes);
app.use('/api/v1/roles', rolesRoutes);
app.use('/api/v1/customer-groups', customerGroupsRoutes);
app.use('/api/v1/contract-types', contractTypesRoutes);
app.use('/api/v1/alerts', alertsRoutes);
app.use('/api/v1/kpis', kpiRoutes);

// Dashboard endpoints at /api/... (matches Apache proxy)
app.get(`${ENV.API_PREFIX}/kpis`, async (req, res) => {
  try {
    const { KPI } = await import('./models/KPI');
    const { default: mongoose } = await import('mongoose');
    const { findProjectByIdOrCode } = await import('./utils/projectHelper');
    const { siteId } = req.query;

    if (!siteId) {
      res.status(400).json({ success: false, message: 'siteId is required' });
      return;
    }

    // Handle both ObjectId and string ID (demo data)
    let projectObjectId: any;

    // Check if siteId is a valid MongoDB ObjectId
    if (mongoose.Types.ObjectId.isValid(siteId as string)) {
      projectObjectId = new mongoose.Types.ObjectId(siteId as string);
    } else {
      // Use helper function to find project without Mongoose casting errors
      const project = await findProjectByIdOrCode(siteId as string);
      if (!project) {
        res.status(404).json({ success: false, message: `Project with ID or code "${siteId}" not found` });
        return;
      }
      projectObjectId = project._id;
    }

    // Use native MongoDB query if projectObjectId is a string to avoid ObjectId casting
    let kpis;
    if (typeof projectObjectId === 'string' && !mongoose.Types.ObjectId.isValid(projectObjectId)) {
      if (!mongoose.connection.db) {
        throw new Error('Database connection not established');
      }
      const kpisCollection = mongoose.connection.db.collection('kpis');
      kpis = await kpisCollection.find({ siteId: projectObjectId }).sort({ date: -1 }).limit(12).toArray();
    } else {
      kpis = await KPI.find({ siteId: projectObjectId }).sort({ date: -1 }).limit(12).lean();
    }
    res.json({ success: true, data: kpis.reverse() });
  } catch (error: any) {
    res.status(500).json({ success: false, message: error.message });
  }
});

// Alerts endpoints moved to alerts.routes.ts

app.get(`${ENV.API_PREFIX}/financial/revenue`, async (req, res) => {
  try {
    const { FinancialData } = await import('./models/Financial');
    const { default: mongoose } = await import('mongoose');
    const { findProjectByIdOrCode } = await import('./utils/projectHelper');
    const { projectId } = req.query;

    if (!projectId) {
      res.status(400).json({ success: false, message: 'projectId is required' });
      return;
    }

    // Handle both ObjectId and string ID (demo data)
    let projectObjectId: any;

    // Check if projectId is a valid MongoDB ObjectId
    if (mongoose.Types.ObjectId.isValid(projectId as string)) {
      projectObjectId = new mongoose.Types.ObjectId(projectId as string);
    } else {
      // Use helper function to find project without Mongoose casting errors
      const project = await findProjectByIdOrCode(projectId as string);
      if (!project) {
        res.status(404).json({ success: false, message: `Project with ID or code "${projectId}" not found` });
        return;
      }
      projectObjectId = project._id;
    }

    // Use native MongoDB query if projectObjectId is a string to avoid ObjectId casting
    let financials;
    if (typeof projectObjectId === 'string' && !mongoose.Types.ObjectId.isValid(projectObjectId)) {
      const db = mongoose.connection.db;
      if (!db) throw new Error('Database connection not established');
      const financialsCollection = db.collection('financials');
      financials = await financialsCollection.find({ projectId: projectObjectId }).sort({ month: -1 }).toArray();
    } else {
      financials = await FinancialData.find({ projectId: projectObjectId }).sort({ month: -1 }).lean();
    }
    const breakdown = [
      { name: 'Nhân sự O&M', value: 35 },
      { name: 'Vật tư thay thế', value: 20 },
      { name: 'Vệ sinh tấm pin', value: 15 },
      { name: 'Bảo hiểm', value: 10 },
      { name: 'Thuê đất', value: 10 },
      { name: 'Khác', value: 10 },
    ];

    res.json({
      success: true,
      data: { yearly: financials, breakdown },
    });
  } catch (error: any) {
    res.status(500).json({ success: false, message: error.message });
  }
});

// Backward compatibility: Also support /api/v1 routes for versioning
app.get('/api/v1/kpis', async (req, res) => {
  try {
    const { KPI } = await import('./models/KPI');
    const { default: mongoose } = await import('mongoose');
    const { findProjectByIdOrCode } = await import('./utils/projectHelper');
    const { siteId } = req.query;

    if (!siteId) {
      res.status(400).json({ success: false, message: 'siteId is required' });
      return;
    }

    // Handle both ObjectId and string ID (demo data)
    let projectObjectId: any;

    // Check if siteId is a valid MongoDB ObjectId
    if (mongoose.Types.ObjectId.isValid(siteId as string)) {
      projectObjectId = new mongoose.Types.ObjectId(siteId as string);
    } else {
      // Use helper function to find project without Mongoose casting errors
      const project = await findProjectByIdOrCode(siteId as string);
      if (!project) {
        res.status(404).json({ success: false, message: `Project with ID or code "${siteId}" not found` });
        return;
      }
      projectObjectId = project._id;
    }

    // Use native MongoDB query if projectObjectId is a string to avoid ObjectId casting
    let kpis;
    if (typeof projectObjectId === 'string' && !mongoose.Types.ObjectId.isValid(projectObjectId)) {
      if (!mongoose.connection.db) {
        throw new Error('Database connection not established');
      }
      const kpisCollection = mongoose.connection.db.collection('kpis');
      kpis = await kpisCollection.find({ siteId: projectObjectId }).sort({ date: -1 }).limit(12).toArray();
    } else {
      kpis = await KPI.find({ siteId: projectObjectId }).sort({ date: -1 }).limit(12).lean();
    }
    res.json({ success: true, data: kpis.reverse() });
  } catch (error: any) {
    res.status(500).json({ success: false, message: error.message });
  }
});

// Alerts endpoints moved to alerts.routes.ts

app.get('/api/v1/financial/revenue', async (req, res) => {
  try {
    const { FinancialData } = await import('./models/Financial');
    const { default: mongoose } = await import('mongoose');
    const { findProjectByIdOrCode } = await import('./utils/projectHelper');
    const { projectId } = req.query;

    if (!projectId) {
      res.status(400).json({ success: false, message: 'projectId is required' });
      return;
    }

    // Handle both ObjectId and string ID (demo data)
    let projectObjectId: any;

    // Check if projectId is a valid MongoDB ObjectId
    if (mongoose.Types.ObjectId.isValid(projectId as string)) {
      projectObjectId = new mongoose.Types.ObjectId(projectId as string);
    } else {
      // Use helper function to find project without Mongoose casting errors
      const project = await findProjectByIdOrCode(projectId as string);
      if (!project) {
        res.status(404).json({ success: false, message: `Project with ID or code "${projectId}" not found` });
        return;
      }
      projectObjectId = project._id;
    }

    // Use native MongoDB query if projectObjectId is a string to avoid ObjectId casting
    let financials;
    if (typeof projectObjectId === 'string' && !mongoose.Types.ObjectId.isValid(projectObjectId)) {
      if (!mongoose.connection.db) {
        throw new Error('Database connection not established');
      }
      const financialsCollection = mongoose.connection.db.collection('financials');
      financials = await financialsCollection.find({ projectId: projectObjectId }).sort({ month: -1 }).toArray();
    } else {
      financials = await FinancialData.find({ projectId: projectObjectId }).sort({ month: -1 }).lean();
    }
    const breakdown = [
      { name: 'Nhân sự O&M', value: 35 },
      { name: 'Vật tư thay thế', value: 20 },
      { name: 'Vệ sinh tấm pin', value: 15 },
      { name: 'Bảo hiểm', value: 10 },
      { name: 'Thuê đất', value: 10 },
      { name: 'Khác', value: 10 },
    ];

    res.json({
      success: true,
      data: { yearly: financials, breakdown },
    });
  } catch (error: any) {
    res.status(500).json({ success: false, message: error.message });
  }
});

// Error handling
app.use(notFoundHandler);
app.use(errorHandler);

// Start server
const startServer = async (): Promise<void> => {
  try {
    // Connect to database
    await connectDatabase();

    // Start listening
    app.listen(ENV.PORT, '0.0.0.0', () => {
      console.log('🚀 Backend Server Started');
      console.log(`   Environment: ${ENV.NODE_ENV}`);
      console.log(`   Port: ${ENV.PORT}`);
      // console.log(`   API Prefix: ${ENV.API_PREFIX}`);
      // // Hide password in MongoDB URI for logging
      // const safeMongoUri = ENV.MONGODB_URI.includes('@')
      //   ? ENV.MONGODB_URI.replace(/\/\/([^:]+):([^@]+)@/, '//$1:***@')
      //   : ENV.MONGODB_URI;
      // console.log(`   MongoDB: ${safeMongoUri}`);
      console.log(`   CORS Origin: ${ENV.CORS_ORIGIN}`);
      console.log(`\n   Health Check: http://localhost:${ENV.PORT}/health`);
      console.log(`   API Base: http://localhost:${ENV.PORT}${ENV.API_PREFIX}`);
      console.log(`   Also supports: http://localhost:${ENV.PORT}/api/v1 (backward compatibility)`);
      console.log(`\n   Production URLs:`);
      console.log(`   - Frontend (prod): https://aom.vuphong.com/`);
      console.log(`   - Frontend (dev): https://aom.vuphong.com/dev/`);
      console.log(`   - Backend API: https://aom.vuphong.com${ENV.API_PREFIX}/\n`);

      // Start scheduled alert checks
      startScheduledAlerts();

      // Start scheduled KPI and Asset Sync jobs
      startScheduledDataCollection();
    });
  } catch (error) {
    console.error('Failed to start server:', error);
    process.exit(1);
  }
};

/**
 * Start scheduled alert checks
 * Runs daily at 8:00 AM and every 4 hours for ticket SLA checks
 */
const startScheduledAlerts = (): void => {
  const { runAllScheduledAlerts, checkTicketSLAs } = require('./services/scheduledAlerts');

  // Calculate milliseconds until next 8:00 AM
  const getMsUntil8AM = (): number => {
    const now = new Date();
    const next8AM = new Date();
    next8AM.setHours(8, 0, 0, 0);
    
    // If it's already past 8 AM today, schedule for tomorrow
    if (now.getTime() >= next8AM.getTime()) {
      next8AM.setDate(next8AM.getDate() + 1);
    }
    
    return next8AM.getTime() - now.getTime();
  };

  // Run immediately on startup (optional - can remove if you want to wait until 8 AM)
  setTimeout(() => {
    // console.log('[Scheduled Alerts] Running initial alert check...');
    runAllScheduledAlerts().catch((err: unknown) => {
      // console.error('[Scheduled Alerts] Error in initial check:', err);
    });
  }, 30000); // Wait 30 seconds after server starts

  // Schedule daily checks at 8:00 AM
  setTimeout(() => {
    // console.log('[Scheduled Alerts] Starting daily alert checks at 8:00 AM');
    
    // Run immediately
    runAllScheduledAlerts().catch((err: unknown) => {
      // console.error('[Scheduled Alerts] Error in daily check:', err);
    });

    // Then schedule to run every 24 hours
    setInterval(() => {
      // console.log('[Scheduled Alerts] Running daily alert check...');
      runAllScheduledAlerts().catch((err: unknown) => {
        // console.error('[Scheduled Alerts] Error in daily check:', err);
      });
    }, 24 * 60 * 60 * 1000); // 24 hours
  }, getMsUntil8AM());

  // Schedule ticket SLA checks every 4 hours (more frequent for time-sensitive alerts)
  // console.log('[Scheduled Alerts] Starting ticket SLA checks every 4 hours');
  setInterval(() => {
    // console.log('[Scheduled Alerts] Running ticket SLA check...');
    checkTicketSLAs().catch((err: unknown) => {
      // console.error('[Scheduled Alerts] Error in ticket SLA check:', err);
    });
  }, 4 * 60 * 60 * 1000); // 4 hours

  // Run ticket SLA check immediately
  setTimeout(() => {
    checkTicketSLAs().catch((err: unknown) => {
      // console.error('[Scheduled Alerts] Error in initial ticket SLA check:', err);
    });
  }, 60000); // Wait 1 minute after server starts
};

/**
 * Start scheduled data collection jobs
 * - KPI Collection Job: Runs daily at 6:00 AM
 * - Asset Sync Job: Runs daily at 2:00 AM
 */
const startScheduledDataCollection = (): void => {
  const { runKPICollectionJob } = require('./jobs/kpiCollectionJob');
  const { runAssetSyncJob } = require('./jobs/assetSyncJob');

  // Calculate milliseconds until next 6:00 AM (KPI Collection)
  const getMsUntil6AM = (): number => {
    const now = new Date();
    const next6AM = new Date();
    next6AM.setHours(6, 0, 0, 0);
    
    if (now.getTime() >= next6AM.getTime()) {
      next6AM.setDate(next6AM.getDate() + 1);
    }
    
    return next6AM.getTime() - now.getTime();
  };

  // Calculate milliseconds until next 2:00 AM (Asset Sync)
  const getMsUntil2AM = (): number => {
    const now = new Date();
    const next2AM = new Date();
    next2AM.setHours(2, 0, 0, 0);
    
    if (now.getTime() >= next2AM.getTime()) {
      next2AM.setDate(next2AM.getDate() + 1);
    }
    
    return next2AM.getTime() - now.getTime();
  };

  // Schedule KPI Collection Job at 6:00 AM
  setTimeout(() => {
    // console.log('[Scheduled Jobs] Starting KPI Collection Job at 6:00 AM');
    
    // Run immediately if it's past 6 AM
    const now = new Date();
    if (now.getHours() >= 6) {
      runKPICollectionJob().catch((err: any) => {
        // console.error('[Scheduled Jobs] Error in KPI Collection Job:', err);
      });
    }

    // Then schedule to run every 24 hours
    setInterval(() => {
      // console.log('[Scheduled Jobs] Running KPI Collection Job...');
      runKPICollectionJob().catch((err: any) => {
        // console.error('[Scheduled Jobs] Error in KPI Collection Job:', err);
      });
    }, 24 * 60 * 60 * 1000); // 24 hours
  }, getMsUntil6AM());

  // Schedule Asset Sync Job at 2:00 AM
  setTimeout(() => {
    // console.log('[Scheduled Jobs] Starting Asset Sync Job at 2:00 AM');
    
    // Run immediately if it's past 2 AM
    const now = new Date();
    if (now.getHours() >= 2) {
      runAssetSyncJob().catch((err: any) => {
        // console.error('[Scheduled Jobs] Error in Asset Sync Job:', err);
      });
    }

    // Then schedule to run every 24 hours
    setInterval(() => {
      // console.log('[Scheduled Jobs] Running Asset Sync Job...');
      runAssetSyncJob().catch((err: any) => {
        // console.error('[Scheduled Jobs] Error in Asset Sync Job:', err);
      });
    }, 24 * 60 * 60 * 1000); // 24 hours
  }, getMsUntil2AM());

  // console.log('[Scheduled Jobs] Scheduled data collection jobs initialized');
};

// Handle graceful shutdown
process.on('SIGTERM', async () => {
  console.log('SIGTERM received. Shutting down gracefully...');
  const { disconnectDatabase } = await import('./config/database');
  await disconnectDatabase();
  process.exit(0);
});

process.on('SIGINT', async () => {
  console.log('SIGINT received. Shutting down gracefully...');
  const { disconnectDatabase } = await import('./config/database');
  await disconnectDatabase();
  process.exit(0);
});

startServer();



