const express = require('express');
const router = express.Router();
const db = require('../config/database');
const { authenticateToken, authorizeRole } = require('../middleware/auth');

const MANAGEMENT_ROLES = ['super_admin', 'admin', 'school'];
const ADMIN_ROLES = ['super_admin', 'admin'];

const formatDate = (date) => date.toISOString().split('T')[0];

const parseDateInput = (value, label) => {
  if (!value) {
    return null;
  }
  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) {
    throw new Error(`Invalid ${label} supplied`);
  }
  return parsed;
};

const parsePlanFeatureCodes = (raw) => {
  if (!raw) {
    return [];
  }

  let parsed;
  try {
    parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
  } catch (error) {
    return [];
  }

  if (Array.isArray(parsed)) {
    return parsed;
  }

  if (parsed && typeof parsed === 'object') {
    return Object.entries(parsed)
      .filter(([, value]) => value === true || value === 'true' || value === 1)
      .map(([key]) => key);
  }

  return [];
};

const canViewParentModules = (user, parentId) => {
  if (!user) {
    return false;
  }
  if (MANAGEMENT_ROLES.includes(user.role)) {
    return true;
  }
  return user.role === 'parent' && Number(user.id) === Number(parentId);
};

const isManagementUser = (user) => user && MANAGEMENT_ROLES.includes(user.role);

const fetchParentModules = async (parentId) => {
  const [rows] = await db.execute(
    `SELECT 
       pms.id,
       pms.parent_id,
       pms.module_id,
       pms.subscription_plan_id,
       pms.grant_source,
       pms.start_date,
       pms.end_date,
       pms.status,
       pms.notes,
       m.code,
       m.name,
       m.description,
       m.is_toggleable,
       m.is_premium,
       m.default_status
     FROM parent_module_subscriptions pms
     JOIN modules m ON pms.module_id = m.id
     WHERE pms.parent_id = ?
     ORDER BY m.name ASC`,
    [parentId]
  );

  return rows;
};

router.get(
  '/modules',
  authenticateToken,
  authorizeRole(MANAGEMENT_ROLES),
  async (req, res) => {
    try {
      const [rows] = await db.execute(
        `SELECT id, code, name, description, is_toggleable, is_premium, default_status, status, created_at, updated_at
         FROM modules
         ORDER BY name ASC`
      );
      res.json(rows);
    } catch (error) {
      res.status(500).json({ message: 'Server error: ' + error.message });
    }
  }
);

router.post(
  '/modules',
  authenticateToken,
  authorizeRole(ADMIN_ROLES),
  async (req, res) => {
    try {
      const {
        code,
        name,
        description = null,
        is_toggleable = 1,
        is_premium = 0,
        default_status = 'enabled',
        config = null,
        status = 'active'
      } = req.body;

      if (!code || !name) {
        return res.status(400).json({ message: 'code and name are required' });
      }

      const payload = [
        code,
        name,
        description,
        is_toggleable ? 1 : 0,
        is_premium ? 1 : 0,
        default_status,
        config ? JSON.stringify(config) : null,
        status
      ];

      const [result] = await db.execute(
        `INSERT INTO modules (code, name, description, is_toggleable, is_premium, default_status, config, status)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
        payload
      );

      const [rows] = await db.execute('SELECT * FROM modules WHERE id = ?', [result.insertId]);
      res.status(201).json(rows[0]);
    } catch (error) {
      res.status(500).json({ message: 'Server error: ' + error.message });
    }
  }
);

router.put(
  '/modules/:id',
  authenticateToken,
  authorizeRole(ADMIN_ROLES),
  async (req, res) => {
    try {
      const moduleId = parseInt(req.params.id, 10);
      if (Number.isNaN(moduleId)) {
        return res.status(400).json({ message: 'Invalid module id' });
      }

      const allowedFields = [
        'code',
        'name',
        'description',
        'is_toggleable',
        'is_premium',
        'default_status',
        'config',
        'status'
      ];

      const updates = [];
      const params = [];

      allowedFields.forEach((field) => {
        if (Object.prototype.hasOwnProperty.call(req.body, field)) {
          let value = req.body[field];
          if (field === 'config' && value) {
            value = JSON.stringify(value);
          }
          if ((field === 'is_toggleable' || field === 'is_premium') && value !== null) {
            value = value ? 1 : 0;
          }
          updates.push(`${field} = ?`);
          params.push(value);
        }
      });

      if (updates.length === 0) {
        return res.status(400).json({ message: 'No valid fields to update' });
      }

      updates.push('updated_at = NOW()');
      params.push(moduleId);

      const [result] = await db.execute(
        `UPDATE modules SET ${updates.join(', ')} WHERE id = ?`,
        params
      );

      if (result.affectedRows === 0) {
        return res.status(404).json({ message: 'Module not found' });
      }

      const [rows] = await db.execute('SELECT * FROM modules WHERE id = ?', [moduleId]);
      res.json(rows[0]);
    } catch (error) {
      res.status(500).json({ message: 'Server error: ' + error.message });
    }
  }
);

router.get('/parents/:parentId/modules', authenticateToken, async (req, res) => {
  try {
    const parentId = parseInt(req.params.parentId, 10);
    if (Number.isNaN(parentId)) {
      return res.status(400).json({ message: 'Invalid parent id' });
    }

    if (!canViewParentModules(req.user, parentId)) {
      return res.status(403).json({ message: 'Access denied' });
    }

    const modules = await fetchParentModules(parentId);
    res.json(modules);
  } catch (error) {
    res.status(500).json({ message: 'Server error: ' + error.message });
  }
});

router.post('/parents/:parentId/modules', authenticateToken, async (req, res) => {
  try {
    const parentId = parseInt(req.params.parentId, 10);
    if (Number.isNaN(parentId)) {
      return res.status(400).json({ message: 'Invalid parent id' });
    }

    if (!isManagementUser(req.user)) {
      return res.status(403).json({ message: 'Access denied' });
    }

    const {
      module_id,
      action = 'grant',
      duration_days,
      start_date,
      end_date,
      subscription_plan_id = null,
      notes = null
    } = req.body;

    if (!module_id) {
      return res.status(400).json({ message: 'module_id is required' });
    }

    if (!['grant', 'revoke'].includes(action)) {
      return res.status(400).json({ message: 'action must be grant or revoke' });
    }

    if (action === 'grant') {
      const startDateObj = start_date ? parseDateInput(start_date, 'start_date') : new Date();
      const endDateObj = end_date ? parseDateInput(end_date, 'end_date') : null;

      if (startDateObj && endDateObj && endDateObj < startDateObj) {
        return res.status(400).json({ message: 'end_date cannot be before start_date' });
      }

      let computedEndDate = endDateObj;
      if (!computedEndDate && duration_days) {
        const duration = parseInt(duration_days, 10);
        if (!Number.isNaN(duration) && duration > 0) {
          computedEndDate = new Date(startDateObj);
          computedEndDate.setDate(computedEndDate.getDate() + duration);
        }
      }

      const currentStatus = startDateObj > new Date() ? 'scheduled' : 'active';

      await db.execute(
        `INSERT INTO parent_module_subscriptions 
          (parent_id, module_id, subscription_plan_id, granted_by, grant_source, start_date, end_date, status, notes)
         VALUES (?, ?, ?, ?, 'manual', ?, ?, ?, ?)
         ON DUPLICATE KEY UPDATE
          subscription_plan_id = VALUES(subscription_plan_id),
          granted_by = VALUES(granted_by),
          grant_source = 'manual',
          start_date = VALUES(start_date),
          end_date = VALUES(end_date),
          status = VALUES(status),
          notes = VALUES(notes),
          updated_at = NOW()`,
        [
          parentId,
          module_id,
          subscription_plan_id,
          req.user.id || null,
          formatDate(startDateObj),
          computedEndDate ? formatDate(computedEndDate) : null,
          currentStatus,
          notes
        ]
      );

      const modules = await fetchParentModules(parentId);
      return res.json({ message: 'Module granted', modules });
    }

    const [result] = await db.execute(
      `UPDATE parent_module_subscriptions 
       SET status = 'revoked', end_date = IF(end_date IS NULL OR end_date > CURDATE(), CURDATE(), end_date), updated_at = NOW()
       WHERE parent_id = ? AND module_id = ?`,
      [parentId, module_id]
    );

    if (result.affectedRows === 0) {
      return res.status(404).json({ message: 'Module subscription not found' });
    }

    const modules = await fetchParentModules(parentId);
    return res.json({ message: 'Module revoked', modules });
  } catch (error) {
    if (error.message.startsWith('Invalid')) {
      return res.status(400).json({ message: error.message });
    }
    res.status(500).json({ message: 'Server error: ' + error.message });
  }
});

router.patch('/parents/:parentId/modules/:moduleId', authenticateToken, async (req, res) => {
  try {
    const parentId = parseInt(req.params.parentId, 10);
    const moduleId = parseInt(req.params.moduleId, 10);
    if (Number.isNaN(parentId) || Number.isNaN(moduleId)) {
      return res.status(400).json({ message: 'Invalid parent or module id' });
    }

    if (!isManagementUser(req.user)) {
      return res.status(403).json({ message: 'Access denied' });
    }

    const updates = [];
    const params = [];

    if (Object.prototype.hasOwnProperty.call(req.body, 'start_date')) {
      const parsed = parseDateInput(req.body.start_date, 'start_date');
      updates.push('start_date = ?');
      params.push(parsed ? formatDate(parsed) : null);
    }

    if (Object.prototype.hasOwnProperty.call(req.body, 'end_date')) {
      const parsed = parseDateInput(req.body.end_date, 'end_date');
      updates.push('end_date = ?');
      params.push(parsed ? formatDate(parsed) : null);
    }

    if (Object.prototype.hasOwnProperty.call(req.body, 'status')) {
      updates.push('status = ?');
      params.push(req.body.status);
    }

    if (Object.prototype.hasOwnProperty.call(req.body, 'notes')) {
      updates.push('notes = ?');
      params.push(req.body.notes);
    }

    if (updates.length === 0) {
      return res.status(400).json({ message: 'No valid fields to update' });
    }

    updates.push('updated_at = NOW()');
    params.push(parentId, moduleId);

    const [result] = await db.execute(
      `UPDATE parent_module_subscriptions 
       SET ${updates.join(', ')}
       WHERE parent_id = ? AND module_id = ?`,
      params
    );

    if (result.affectedRows === 0) {
      return res.status(404).json({ message: 'Module subscription not found' });
    }

    const modules = await fetchParentModules(parentId);
    res.json({ message: 'Module subscription updated', modules });
  } catch (error) {
    if (error.message.startsWith('Invalid')) {
      return res.status(400).json({ message: error.message });
    }
    res.status(500).json({ message: 'Server error: ' + error.message });
  }
});

router.get('/me/modules', authenticateToken, async (req, res) => {
  try {
    if (req.user.role !== 'parent') {
      return res.status(403).json({ message: 'Only parents can access this endpoint' });
    }

    const [rows] = await db.execute(
      `SELECT 
         m.id,
         m.code,
         m.name,
         pms.status,
         pms.start_date,
         pms.end_date
       FROM parent_module_subscriptions pms
       JOIN modules m ON pms.module_id = m.id
       WHERE pms.parent_id = ?
         AND pms.status IN ('active', 'scheduled')
         AND pms.start_date <= CURDATE()
         AND (pms.end_date IS NULL OR pms.end_date >= CURDATE())`,
      [req.user.id]
    );

    res.json(rows);
  } catch (error) {
    res.status(500).json({ message: 'Server error: ' + error.message });
  }
});

router.post(
  '/subscriptions/:subscriptionId/modules-sync',
  authenticateToken,
  authorizeRole(MANAGEMENT_ROLES),
  async (req, res) => {
    const subscriptionId = parseInt(req.params.subscriptionId, 10);

    if (Number.isNaN(subscriptionId)) {
      return res.status(400).json({ message: 'Invalid subscription id' });
    }

    const [subscriptions] = await db.execute(
      `SELECT ds.id, ds.user_id, ds.plan_id, ds.status, sp.features
       FROM device_subscriptions ds
       JOIN subscription_plans sp ON ds.plan_id = sp.id
       WHERE ds.id = ?
       LIMIT 1`,
      [subscriptionId]
    );

    if (subscriptions.length === 0) {
      return res.status(404).json({ message: 'Subscription not found' });
    }

    const subscription = subscriptions[0];
    const featureCodes = parsePlanFeatureCodes(subscription.features);

    const connection = await db.getConnection();
    try {
      await connection.beginTransaction();

      const parentId = subscription.user_id;
      const planId = subscription.plan_id;
      const todayStr = formatDate(new Date());

      let granted = 0;
      let revoked = 0;

      if (subscription.status !== 'active' || featureCodes.length === 0) {
        const [revokeResult] = await connection.execute(
          `UPDATE parent_module_subscriptions 
           SET status = 'revoked', end_date = IF(end_date IS NULL OR end_date > CURDATE(), CURDATE(), end_date), updated_at = NOW()
           WHERE parent_id = ? AND subscription_plan_id = ? AND grant_source = 'plan'`,
          [parentId, planId]
        );
        revoked = revokeResult.affectedRows;
        await connection.commit();
        connection.release();
        return res.json({
          message: 'Subscription inactive or no modules configured; existing plan modules revoked',
          granted,
          revoked
        });
      }

      const placeholders = featureCodes.map(() => '?').join(',');
      const [moduleRows] = await connection.execute(
        `SELECT id, code FROM modules WHERE code IN (${placeholders})`,
        featureCodes
      );

      if (moduleRows.length === 0) {
        const [revokeResult] = await connection.execute(
          `UPDATE parent_module_subscriptions 
           SET status = 'revoked', end_date = IF(end_date IS NULL OR end_date > CURDATE(), CURDATE(), end_date), updated_at = NOW()
           WHERE parent_id = ? AND subscription_plan_id = ? AND grant_source = 'plan'`,
          [parentId, planId]
        );
        revoked = revokeResult.affectedRows;
        await connection.commit();
        connection.release();
        return res.json({
          message: 'No matching modules defined for plan features; existing plan modules revoked',
          granted,
          revoked
        });
      }

      for (const moduleRow of moduleRows) {
        await connection.execute(
          `INSERT INTO parent_module_subscriptions 
             (parent_id, module_id, subscription_plan_id, granted_by, grant_source, start_date, end_date, status)
           VALUES (?, ?, ?, NULL, 'plan', ?, NULL, 'active')
           ON DUPLICATE KEY UPDATE
             subscription_plan_id = VALUES(subscription_plan_id),
             grant_source = 'plan',
             start_date = VALUES(start_date),
             end_date = NULL,
             status = 'active',
             updated_at = NOW()`,
          [parentId, moduleRow.id, planId, todayStr]
        );
        granted += 1;
      }

      const moduleIds = moduleRows.map((row) => row.id);
      const revokeParams = [parentId, planId];
      let revokeSql = `UPDATE parent_module_subscriptions 
        SET status = 'revoked', end_date = IF(end_date IS NULL OR end_date > CURDATE(), CURDATE(), end_date), updated_at = NOW()
        WHERE parent_id = ? AND subscription_plan_id = ? AND grant_source = 'plan'`;

      if (moduleIds.length > 0) {
        revokeSql += ` AND module_id NOT IN (${moduleIds.map(() => '?').join(',')})`;
        revokeParams.push(...moduleIds);
      }

      const [revokeResult] = await connection.execute(revokeSql, revokeParams);
      revoked = revokeResult.affectedRows;

      await connection.commit();
      connection.release();

      return res.json({ message: 'Subscription modules synced', granted, revoked });
    } catch (error) {
      await connection.rollback();
      connection.release();
      res.status(500).json({ message: 'Server error: ' + error.message });
    }
  }
);

module.exports = router;
