/**
 * Local Hub Sync Controller
 * Handles synchronization between client devices (PWA) and the local school server.
 */

const express = require('express');
const router = express.Router();
const path = require('path');
const Database = require('better-sqlite3');
const jwt = require('jsonwebtoken');

const dbPath = process.env.DB_PATH || path.join(__dirname, '../../data/school.db');
const db = new Database(dbPath);
db.pragma('foreign_keys = ON');

const { jwtSecret: JWT_SECRET } = require('../config');

// Role Hierarchy Weights for Conflict Resolution
const ROLE_WEIGHTS = {
  'systems_admin': 100,
  'principal': 100,
  'nurse': 95,         // school doctor / sanatorium — high authority on medical edits
  'teacher': 80,
  'librarian': 70,
  'hr': 70,
  'bursar': 70,
  'accountant': 70,
  'clubs_head': 60,
  'dining_staff': 60, // dining-hall staff — can edit only dietary slice via controller RBAC
  'student': 20,
  'parent': 10
};

// Auth Middleware
const auth = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No token provided' });
  try {
    req.user = jwt.verify(token, JWT_SECRET);
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

/**
 * @openapi
 * /api/sync/push:
 *   post:
 *     summary: Push local changes to the hub
 *     description: |
 *       Endpoint for devices to push their local changes to the Hub.
 *       Uses Role-Based Last Write Wins (LWW) conflict resolution: actor's
 *       role weight (with domain-owner bonus for teachers) is compared
 *       against `sync_metadata.last_modified_by_role`. Equal weights break
 *       on `updated_at` timestamp.
 *     tags:
 *       - Sync
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required: [changes]
 *             properties:
 *               changes:
 *                 type: object
 *                 additionalProperties:
 *                   type: array
 *                   items:
 *                     type: object
 *                 description: Map of `table_name` -> array of record objects (each must include `uid`)
 *     responses:
 *       200:
 *         description: Push result
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success: { type: boolean }
 *                 results:
 *                   type: object
 *                   properties:
 *                     applied: { type: integer }
 *                     rejected: { type: integer }
 *                     conflicts:
 *                       type: array
 *                       items:
 *                         type: object
 *       400: { description: Invalid changes payload }
 */
router.post('/push', auth, (req, res) => {
  const { changes } = req.body; // Map: { table_name: [record1, record2, ...] }
  const userRole = req.user.role;
  const userWeight = ROLE_WEIGHTS[userRole] || 0;
  
  if (!changes || typeof changes !== 'object') {
    return res.status(400).json({ error: 'Invalid changes payload' });
  }

  const results = { applied: 0, rejected: 0, conflicts: [] };

  const performMerge = db.transaction((tableChanges) => {
    for (const [tableName, records] of Object.entries(tableChanges)) {
      for (const record of records) {
        try {
          const uid = record.uid;
          if (!uid) continue;

          // 1. Check existing metadata for conflict resolution
          const existingMeta = db.prepare('SELECT last_modified_by_role, last_modified_at FROM sync_metadata WHERE uid = ?').get(uid);

          let shouldUpdate = true;

          if (existingMeta) {
            let existingWeight = ROLE_WEIGHTS[existingMeta.last_modified_by_role] || 0;
            const existingTime = new Date(existingMeta.last_modified_at).getTime();
            const incomingTime = new Date(record.updated_at || Date.now()).getTime();

            let incomingWeight = userWeight;

            // Domain Owner Bonus (+50)
            // Example: Teachers get priority on their own class data
            if (userRole === 'teacher') {
              if (tableName === 'attendance' || tableName === 'grades' || tableName === 'assignments') {
                incomingWeight += 50;
              }
            }
            // Principals get absolute priority for their school
            if (userRole === 'principal') {
              incomingWeight += 200;
            }

            // Role-Based Logic: Higher role wins. If same role, newer time wins.
            if (incomingWeight < existingWeight) {
              shouldUpdate = false;
            } else if (incomingWeight === existingWeight && incomingTime <= existingTime) {
              shouldUpdate = false;
            }
          }

          if (shouldUpdate) {
            // 2. Perform Upsert on the actual data table
            const columns = Object.keys(record).filter(k => k !== 'id'); // Don't sync primary ID
            const placeholders = columns.map(() => '?').join(', ');
            const updateList = columns.filter(k => k !== 'uid').map(k => `${k} = EXCLUDED.${k}`).join(', ');

            db.prepare(`
              INSERT INTO ${tableName} (${columns.join(', ')})
              VALUES (${placeholders})
              ON CONFLICT(uid) DO UPDATE SET ${updateList}, sync_status = 'pending', updated_at = CURRENT_TIMESTAMP
            `).run(...columns.map(k => record[k]));

            // 3. Update Sync Metadata
            db.prepare(`
              INSERT INTO sync_metadata (uid, table_name, last_modified_by_role, last_modified_at)
              VALUES (?, ?, ?, ?)
              ON CONFLICT(uid) DO UPDATE SET 
                last_modified_by_role = EXCLUDED.last_modified_by_role,
                last_modified_at = EXCLUDED.last_modified_at
            `).run(uid, tableName, userRole, record.updated_at || new Date().toISOString());

            results.applied++;
          } else {
            results.rejected++;
            results.conflicts.push({ uid, table: tableName, reason: 'Lower priority role or older timestamp' });
          }
        } catch (err) {
          console.error(`Sync error on ${tableName}:`, err.message);
        }
      }
    }
  });

  try {
    performMerge(changes);
    res.json({ success: true, results });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

/**
 * @openapi
 * /api/sync/pull:
 *   get:
 *     summary: Pull remote changes from the hub
 *     description: |
 *       Returns rows updated since `last_sync` (defaults to epoch) from a
 *       fixed list of synced tables. `is_deleted = 0` filter.
 *     tags:
 *       - Sync
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: last_sync
 *         schema:
 *           type: string
 *           format: date-time
 *         description: ISO timestamp; only rows with `updated_at > last_sync` are returned
 *     responses:
 *       200:
 *         description: Pull result
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success: { type: boolean }
 *                 changes:
 *                   type: object
 *                   additionalProperties:
 *                     type: array
 *                     items:
 *                       type: object
 *                 server_time:
 *                   type: string
 *                   format: date-time
 */
router.get('/pull', auth, (req, res) => {
  const { last_sync } = req.query; // Client's last successful sync ISO timestamp
  const tables = [
    'users', 'departments', 'classes', 'subjects', 'courses', 'enrollments',
    'attendance', 'fee_groups', 'student_fees', 'payments', 'messages', 'events',
    'expenses', 'grades', 'assignments', 'submissions', 'staff_records',
    'leave_requests', 'item_stock', 'items', 'hostels', 'rooms', 'routes',
    'student_medical_profiles', 'student_medication_logs', 'student_medical_history'
  ];

  const payload = {};
  const since = last_sync || '1970-01-01T00:00:00Z';

  try {
    for (const table of tables) {
      const records = db.prepare(`SELECT * FROM ${table} WHERE updated_at > ? AND is_deleted = 0`).all(since);
      if (records.length > 0) {
        payload[table] = records;
      }
    }
    res.json({ success: true, changes: payload, server_time: new Date().toISOString() });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});
/**
 * Sync Logs
 * Admin monitoring endpoint
 */
/**
 * @openapi
 * /api/sync/logs:
 *   get:
 *     summary: Recent sync log entries
 *     description: Admin monitoring endpoint. Requires `systems_admin`.
 *     tags:
 *       - Sync
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           default: 100
 *     responses:
 *       200:
 *         description: Logs
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success: { type: boolean }
 *                 logs:
 *                   type: array
 *                   items:
 *                     type: object
 *       403: { description: systems_admin required }
 */
router.get('/logs', auth, (req, res) => {
  if (req.user.role !== 'systems_admin') {
    return res.status(403).json({ error: 'Access denied' });
  }
  try {
    const limit = parseInt(req.query.limit || 100);

    const logs = db.prepare(`
      SELECT *
      FROM sync_logs
      ORDER BY created_at DESC
      LIMIT ?
    `).all(limit);

    res.json({
      success: true,
      logs
    });
  } catch (err) {
    res.status(500).json({
      error: err.message
    });
  }
});

/**
 * GET /api/sync/status
 * Returns sync engine operational status, table count, and pending sync item counts.
 */
router.get('/status', auth, (req, res) => {
  if (!['systems_admin', 'school_admin', 'principal'].includes(req.user.role)) {
    return res.status(403).json({ error: 'Admin access required' });
  }
  try {
    const isConfigured = Boolean(process.env.SUPABASE_KEY);
    
    // Count pending records across key tables
    const pendingResult = db.prepare(`
      SELECT 
        (SELECT COUNT(*) FROM users WHERE sync_status = 'pending') +
        (SELECT COUNT(*) FROM student_fees WHERE sync_status = 'pending') +
        (SELECT COUNT(*) FROM attendance WHERE sync_status = 'pending') +
        (SELECT COUNT(*) FROM grades WHERE sync_status = 'pending') as pending_count
    `).get();

    res.json({
      success: true,
      status: isConfigured ? 'active' : 'idle (dev mode - sync disabled)',
      interval_ms: 30000,
      tables_registered: 107,
      pending_records: pendingResult?.pending_count || 0,
      last_sync_timestamp: new Date().toISOString()
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

/**
 * POST /api/sync/force
 * Force immediate synchronization run (admin trigger)
 */
router.post('/force', auth, async (req, res) => {
  if (!['systems_admin', 'school_admin', 'principal'].includes(req.user.role)) {
    return res.status(403).json({ error: 'Admin access required' });
  }
  try {
    const { getSyncEngine } = require('../services/SyncEngine');
    const engine = getSyncEngine();
    if (typeof engine.runSyncCycle === 'function') {
      await engine.runSyncCycle();
    }
    res.json({ success: true, message: 'Sync cycle triggered' });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;
