const mysql = require('mysql2/promise');
const fs = require('fs');
const path = require('path');

const NODE_ENV = process.env.NODE_ENV || 'development';

function getSSLConfig() {
  const useSSL = String(process.env.DB_SSL || '').toLowerCase() === 'true';
  const caPath = process.env.DB_SSL_CA_PATH;
  if (!useSSL) return undefined;
  try {
    if (caPath) {
      const resolved = path.resolve(caPath);
      if (fs.existsSync(resolved)) {
        return { ca: fs.readFileSync(resolved), rejectUnauthorized: false };
      }
    }
  } catch (_) {}
  return { rejectUnauthorized: false };
}

let dbConfig;
if (NODE_ENV === 'development') {
  dbConfig = {
    host: process.env.DEV_DB_HOST || 'localhost',
    port: Number(process.env.DEV_DB_PORT) || 3306,
    user: process.env.DEV_DB_USER || 'root',
    password: process.env.DEV_DB_PASSWORD || '',
    database: process.env.DEV_DB_NAME || 'profitplanet_centralserver',
    ssl: undefined
  };
} else {
  dbConfig = {
    host: process.env.DB_HOST,
    port: Number(process.env.DB_PORT) || 3306,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    ssl: getSSLConfig()
  };
}

const pool = mysql.createPool({
  ...dbConfig,
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

async function ensureUserExistsByEmail(conn, email) {
  const [rows] = await conn.query('SELECT id, email FROM users WHERE LOWER(email) = LOWER(?) LIMIT 1', [email]);
  if (!rows.length) {
    const err = new Error('Top node user not found');
    err.status = 404;
    throw err;
  }
  return rows[0];
}

async function createMatrix({ name, topNodeEmail, maxDepth }) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();

    // Ensure columns for multi-instance (idempotent)
    try { await conn.query(`ALTER TABLE user_tree_edges ADD COLUMN matrix_instance_id INT NULL`); } catch (_) {}
    try { await conn.query(`ALTER TABLE user_tree_edges ADD COLUMN rogue_user BOOLEAN DEFAULT FALSE`); } catch (_) {}
    try { await conn.query(`ALTER TABLE user_tree_closure ADD COLUMN matrix_instance_id INT NOT NULL`); } catch (_) {}
    try { await conn.query(`ALTER TABLE user_matrix_metadata ADD COLUMN matrix_instance_id INT NOT NULL`); } catch (_) {}

    const topUser = await ensureUserExistsByEmail(conn, topNodeEmail);

    // Create new matrix instance
    const [instRes] = await conn.query(
      `INSERT INTO matrix_instances
        (root_user_id, name, is_active, max_depth, ego_activated_at, immediate_children_count, first_free_position)
       VALUES (?, ?, TRUE, ?, NOW(), 0, NULL)`,
      [topUser.id, name, (typeof maxDepth === 'number' && Number.isFinite(maxDepth)) ? maxDepth : null]
    );
    const matrixInstanceId = instRes.insertId;

    // Insert metadata row (primary keyed by matrix_instance_id)
    await conn.query(
      `INSERT INTO user_matrix_metadata
        (matrix_instance_id, root_user_id, ego_activated_at, last_bfs_fill_at, immediate_children_count,
         first_free_position, name, is_active, max_depth)
       VALUES (?, ?, NOW(), NULL, 0, 1, ?, TRUE, ?)`,
      [matrixInstanceId, topUser.id, name, (typeof maxDepth === 'number' && Number.isFinite(maxDepth)) ? maxDepth : null]
    );

    // Self closure scoped to instance
    await conn.query(
      `INSERT IGNORE INTO user_tree_closure
        (matrix_instance_id, ancestor_user_id, descendant_user_id, depth)
       VALUES (?, ?, ?, 0)`,
      [matrixInstanceId, topUser.id, topUser.id]
    );

    await conn.commit();
    return {
      name,
      matrixInstanceId,
      masterTopUserId: topUser.id,
      masterTopUserEmail: topUser.email,
      rootUserId: topUser.id
    };
  } catch (e) {
    await conn.rollback();
    throw e;
  } finally {
    conn.release();
  }
}

async function ensureUserExists(conn, userId) {
  const [rows] = await conn.query('SELECT id FROM users WHERE id = ?', [userId]);
  if (!rows.length) {
    const err = new Error('User not found');
    err.status = 404;
    throw err;
  }
}

async function activateEgoMatrix(rootUserId) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();
    await ensureUserExists(conn, rootUserId);
    await conn.query(
      'INSERT IGNORE INTO user_tree_closure (ancestor_user_id, descendant_user_id, depth) VALUES (?, ?, 0)',
      [rootUserId, rootUserId]
    );
    const [childRows] = await conn.query(
      'SELECT position FROM user_tree_edges WHERE parent_user_id = ? ORDER BY position ASC',
      [rootUserId]
    );
    const positions = new Set(childRows.map(r => r.position));
    let firstFreePosition = null;
    for (let i = 1; i <= 5; i++) {
      if (!positions.has(i)) { firstFreePosition = i; break; }
    }
    const immediateChildrenCount = childRows.length;
    await conn.query(
      `
      INSERT INTO user_matrix_metadata
        (root_user_id, ego_activated_at, last_bfs_fill_at, immediate_children_count, first_free_position, max_depth)
      VALUES
        (?, NOW(), NULL, ?, ?, 5)
      ON DUPLICATE KEY UPDATE
        ego_activated_at = COALESCE(ego_activated_at, VALUES(ego_activated_at)),
        immediate_children_count = VALUES(immediate_children_count),
        first_free_position = VALUES(first_free_position),
        max_depth = 5,
        updated_at = CURRENT_TIMESTAMP
      `,
      [rootUserId, immediateChildrenCount, firstFreePosition]
    );
    const [metaRows] = await conn.query(
      'SELECT ego_activated_at, last_bfs_fill_at, immediate_children_count, first_free_position FROM user_matrix_metadata WHERE root_user_id = ?',
      [rootUserId]
    );
    const meta = metaRows[0] || {
      ego_activated_at: null,
      last_bfs_fill_at: null,
      immediate_children_count: immediateChildrenCount,
      first_free_position: firstFreePosition
    };
    await conn.commit();
    return {
      rootUserId,
      egoActivatedAt: meta.ego_activated_at,
      lastBfsFillAt: meta.last_bfs_fill_at,
      immediateChildrenCount: meta.immediate_children_count,
      firstFreePosition: meta.first_free_position
    };
  } catch (err) {
    await conn.rollback();
    throw err;
  } finally {
    conn.release();
  }
}

async function getMatrixStats() {
  const conn = await pool.getConnection();
  try {
    const [matRows] = await conn.query(`
      SELECT mi.id AS matrixInstanceId,
             mi.root_user_id,
             mi.name,
             mi.is_active,
             mi.ego_activated_at,
             mi.max_depth,
             u.email
      FROM matrix_instances mi
      JOIN users u ON u.id = mi.root_user_id
      ORDER BY mi.id ASC
    `);
    if (!matRows.length) return {
      activeMatrices: 0,
      totalMatrices: 0,
      totalUsersSubscribed: 0,
      matrices: []
    };

    const instanceIds = matRows.map(r => r.matrixInstanceId);

    // Count users per instance honoring max_depth
    const [cntRows] = await conn.query(`
      SELECT c.matrix_instance_id AS mid, COUNT(*) AS cnt
      FROM user_tree_closure c
      JOIN matrix_instances mi ON mi.id = c.matrix_instance_id
      WHERE c.matrix_instance_id IN (?)
        AND c.depth BETWEEN 0 AND COALESCE(mi.max_depth, 2147483647)
      GROUP BY c.matrix_instance_id
    `, [instanceIds]);

    const countsMap = new Map(cntRows.map(r => [Number(r.mid), Number(r.cnt)]));

    // Rogue counts
    const [rogueRows] = await conn.query(`
      SELECT matrix_instance_id AS mid, COUNT(*) AS rogueCnt
      FROM user_tree_edges
      WHERE matrix_instance_id IN (?)
        AND rogue_user = TRUE
      GROUP BY matrix_instance_id
    `, [instanceIds]);
    const rogueMap = new Map(rogueRows.map(r => [Number(r.mid), Number(r.rogueCnt)]));

    // Distinct subscribed users across active matrices
    const [distinctRows] = await conn.query(`
      SELECT COUNT(DISTINCT c.descendant_user_id) AS total
      FROM user_tree_closure c
      JOIN matrix_instances mi ON mi.id = c.matrix_instance_id
      WHERE mi.is_active = TRUE
        AND c.matrix_instance_id IN (?)
        AND c.depth BETWEEN 0 AND COALESCE(mi.max_depth, 2147483647)
    `, [instanceIds]);

    const totalDistinct = Number(distinctRows[0]?.total || 0);
    const activeMatrices = matRows.filter(r => !!r.is_active).length;

    const matrices = matRows.map(r => ({
      matrixInstanceId: Number(r.matrixInstanceId),
      rootUserId: Number(r.root_user_id),
      name: r.name || null,
      isActive: !!r.is_active,
      usersCount: countsMap.get(Number(r.matrixInstanceId)) || 0,
      rogueUsersCount: rogueMap.get(Number(r.matrixInstanceId)) || 0,
      createdAt: r.ego_activated_at || null,
      topNodeEmail: r.email
    }));

    return {
      activeMatrices,
      totalMatrices: matRows.length,
      totalUsersSubscribed: totalDistinct,
      matrices
    };
  } finally {
    conn.release();
  }
}

async function resolveRootUserId({ rootUserId, matrixId, matrixInstanceId, topNodeEmail }) {
  const conn = await pool.getConnection();
  try {
    // Prefer explicit matrixInstanceId (matrixId backward compatible)
    const midRaw = matrixInstanceId || matrixId;
    const mid = Number.parseInt(midRaw, 10);
    if (Number.isFinite(mid) && mid > 0) {
      const [rows] = await conn.query(
        `SELECT id, root_user_id FROM matrix_instances WHERE id = ? LIMIT 1`,
        [mid]
      );
      if (!rows.length) {
        const err = new Error('Matrix instance not found');
        err.status = 404;
        throw err;
      }
      return { rootUserId: Number(rows[0].root_user_id), matrixInstanceId: Number(rows[0].id) };
    }

    // Direct root user id (find first instance with that root)
    const rid = Number.parseInt(rootUserId, 10);
    if (Number.isFinite(rid) && rid > 0) {
      const [rows] = await conn.query(
        `SELECT id FROM matrix_instances WHERE root_user_id = ? ORDER BY id ASC LIMIT 1`,
        [rid]
      );
      if (rows.length) {
        return { rootUserId: rid, matrixInstanceId: Number(rows[0].id) };
      }
    }

    // Resolve by email
    const email = (topNodeEmail || '').trim();
    if (email) {
      const [uRows] = await conn.query(
        `SELECT id FROM users WHERE LOWER(email)=LOWER(?) LIMIT 1`,
        [email]
      );
      if (!uRows.length) {
        const err = new Error('Top node user not found');
        err.status = 404;
        throw err;
      }
      const uid = Number(uRows[0].id);
      const [instRows] = await conn.query(
        `SELECT id FROM matrix_instances WHERE root_user_id = ? ORDER BY id ASC LIMIT 1`,
        [uid]
      );
      if (instRows.length) {
        return { rootUserId: uid, matrixInstanceId: Number(instRows[0].id) };
      }
    }

    // Fallback: pick first existing instance
    const [firstInst] = await conn.query(`SELECT id, root_user_id FROM matrix_instances ORDER BY id ASC LIMIT 1`);
    if (firstInst.length) {
      return { rootUserId: Number(firstInst[0].root_user_id), matrixInstanceId: Number(firstInst[0].id) };
    }

    const err = new Error('Matrix resolver parameters required');
    err.status = 400;
    throw err;
  } finally {
    conn.release();
  }
}

// NEW: search user candidates to add to a matrix
async function getUserSearchCandidates({ q, type = 'all', rootUserId, matrixInstanceId, limit = 20, offset = 0 }) {
  const conn = await pool.getConnection();
  try {
    const whereParts = [];
    const params = [];

    // Exclude users already in selected matrix instance
    whereParts.push(`u.id NOT IN (
      SELECT descendant_user_id
      FROM user_tree_closure
      WHERE matrix_instance_id = ?
        AND ancestor_user_id = ?
    )`);
    params.push(Number(matrixInstanceId), Number(rootUserId));

    whereParts.push(`u.role = 'user'`);

    if (type === 'personal' || type === 'company') {
      whereParts.push(`u.user_type = ?`);
      params.push(type);
    }

    const like = `%${q.toLowerCase()}%`;
    whereParts.push(`(
      LOWER(u.email) LIKE ?
      OR LOWER(CONCAT(COALESCE(p.first_name,''),' ',COALESCE(p.last_name,''))) LIKE ?
      OR LOWER(COALESCE(c.company_name,'')) LIKE ?
    )`);
    params.push(like, like, like);

    const whereSql = `WHERE ${whereParts.join(' AND ')}`;

    const [countRows] = await conn.query(`
      SELECT COUNT(*) AS total
      FROM users u
      LEFT JOIN personal_profiles p ON p.user_id = u.id
      LEFT JOIN company_profiles c ON c.user_id = u.id
      ${whereSql}
    `, params);
    const total = Number(countRows[0]?.total || 0);

    const [rows] = await conn.query(`
      SELECT u.id AS userId, u.email, u.user_type AS userType,
        CASE
          WHEN u.user_type='personal' THEN TRIM(CONCAT(COALESCE(p.first_name,''),' ',COALESCE(p.last_name,'')))
          WHEN u.user_type='company' THEN COALESCE(c.company_name,'')
          ELSE ''
        END AS name
      FROM users u
      LEFT JOIN personal_profiles p ON p.user_id = u.id
      LEFT JOIN company_profiles c ON c.user_id = u.id
      ${whereSql}
      ORDER BY u.created_at DESC, u.email ASC
      LIMIT ? OFFSET ?
    `, params.concat([Number(limit), Number(offset)]));

    return {
      total,
      items: rows.map(r => ({
        userId: Number(r.userId),
        email: r.email,
        userType: r.userType,
        name: r.name || ''
      }))
    };
  } finally {
    conn.release();
  }
}

// NEW: fetch matrix users (descendants) under a root
async function getMatrixUsers({ rootUserId, matrixInstanceId, maxDepth = 5, limit = 100, offset = 0, includeRoot = false, rogueOnly = false }) {
  const conn = await pool.getConnection();
  try {
    // Determine policy: root unlimited, ego capped at 5
    let startDepth = includeRoot ? 0 : 1;
    const [instRows] = await conn.query(`SELECT root_user_id FROM matrix_instances WHERE id = ? LIMIT 1`, [matrixInstanceId]);
    const isRootAnchor = Number(instRows[0]?.root_user_id) === Number(rootUserId);
    let requestedDepth = Number(maxDepth);
    if (!Number.isFinite(requestedDepth) || requestedDepth < 0) requestedDepth = isRootAnchor ? 20 : 5;
    const depthLimit = isRootAnchor ? requestedDepth : Math.min(requestedDepth, 5);
    if (startDepth > depthLimit) return [];

    const rogueClause = rogueOnly ? 'AND (e.rogue_user = TRUE)' : '';

    const [rows] = await conn.query(`
      SELECT
        c.descendant_user_id AS userId,
        c.depth,
        u.email,
        u.user_type AS userType,
        u.role,
        u.created_at AS createdAt,
        e.parent_user_id AS parentUserId,
        e.position,
        e.rogue_user AS rogueUser,
        CASE
          WHEN u.user_type='personal' THEN TRIM(CONCAT(COALESCE(pp.first_name,''),' ',COALESCE(pp.last_name,'')))
          WHEN u.user_type='company' THEN COALESCE(cp.company_name,'')
          ELSE ''
        END AS name
      FROM user_tree_closure c
      JOIN users u ON u.id = c.descendant_user_id
      LEFT JOIN user_tree_edges e
        ON e.child_user_id = c.descendant_user_id
       AND e.matrix_instance_id = c.matrix_instance_id
       AND e.parent_user_id != e.child_user_id
      LEFT JOIN personal_profiles pp ON pp.user_id = u.id
      LEFT JOIN company_profiles cp ON cp.user_id = u.id
      WHERE c.matrix_instance_id = ?
        AND c.ancestor_user_id = ?
        AND c.depth BETWEEN ? AND ?
        ${rogueClause}
      ORDER BY c.depth ASC, u.created_at ASC, u.id ASC
      LIMIT ? OFFSET ?
    `, [matrixInstanceId, rootUserId, startDepth, depthLimit, Number(limit), Number(offset)]);

    return rows.map(r => ({
      userId: Number(r.userId),
      email: r.email,
      userType: r.userType,
      role: r.role,
      depth: Number(r.depth),
      level: Number(r.depth),
      parentUserId: r.parentUserId ? Number(r.parentUserId) : null,
      position: r.position != null ? Number(r.position) : null,
      rogueUser: !!r.rogueUser,
      name: r.name || r.email,
      createdAt: r.createdAt
    }));
  } finally {
    conn.release();
  }
}

async function addUserToMatrix({
  rootUserId,
  matrixInstanceId,
  matrixId,
  topNodeEmail,
  childUserId,
  forceParentFallback = false,
  parentUserId,
  actorUserId
}) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();

    // Resolve (matrixId backward compatible)
    const resolved = await resolveRootUserId({
      rootUserId,
      matrixId: matrixId || matrixInstanceId,
      matrixInstanceId,
      topNodeEmail
    });
    const rid = resolved.rootUserId;
    const mid = resolved.matrixInstanceId;

    // PREVENT additions to inactive instances
    const [miRows] = await conn.query(
      `SELECT is_active FROM matrix_instances WHERE id = ? LIMIT 1`,
      [mid]
    );
    if (!miRows.length) {
      const err = new Error('Matrix instance not found');
      err.status = 404;
      throw err;
    }
    if (!miRows[0].is_active) {
      const err = new Error('Matrix instance is inactive');
      err.status = 409;
      throw err;
    }

    const cId = Number(childUserId);
    if (!Number.isFinite(cId) || cId <= 0) {
      const err = new Error('Invalid childUserId');
      err.status = 400;
      throw err;
    }
    const [childRows] = await conn.query(`SELECT id FROM users WHERE id = ? LIMIT 1`, [cId]);
    if (!childRows.length) {
      const err = new Error('Child user not found');
      err.status = 404;
      throw err;
    }

    let parentId = Number(parentUserId) > 0 ? Number(parentUserId) : null;
    let explicitRootChoice = false;
    if (parentId === rid) {
      explicitRootChoice = true;                                  // admin explicitly chose root
    }

    if (!parentId) {
      // referral parent discovery
      const [refRows] = await conn.query(
        `
        SELECT t.created_by_user_id AS parent_user_id
        FROM referral_token_usage u
        JOIN referral_tokens t ON t.id = u.referral_token_id
        WHERE u.used_by_user_id = ?
        ORDER BY u.id ASC
        LIMIT 1
        `,
        [cId]
      );
      if (refRows.length) parentId = Number(refRows[0].parent_user_id);
    }

    let fallback_reason = null;
    let rogueFlag = false;

    // Check parent within matrix instance (when parentId exists and is not root)
    if (parentId && parentId !== rid) {
      const [inMatrixRows] = await conn.query(
        `
        SELECT depth FROM user_tree_closure
        WHERE matrix_instance_id = ? AND ancestor_user_id = ? AND descendant_user_id = ?
        LIMIT 1
        `,
        [mid, rid, parentId]
      );
      if (!inMatrixRows.length) {
        // Referrer not in matrix -> fallback decision
        if (forceParentFallback) {
          fallback_reason = 'referrer_not_in_matrix';
          parentId = rid;
          rogueFlag = true;
        } else {
          const err = new Error('Referrer is not part of this matrix');
          err.status = 400;
          throw err;
        }
      } else {
        // Depth-policy check: placing under non-root referrer must not exceed max depth
        if (maxDepthPolicy != null) {
          const parentDepthFromRoot = Number(inMatrixRows[0].depth || 0);
          if (parentDepthFromRoot + 1 > maxDepthPolicy) {
            if (forceParentFallback) {
              fallback_reason = 'referrer_depth_limit';
              parentId = rid;
              rogueFlag = true;
            } else {
              const err = new Error('Referrer depth limit would be exceeded');
              err.status = 400;
              throw err;
            }
          }
        }
      }
    }

    // Duplicate check (already in matrix)
    const [dupRows] = await conn.query(
      `
      SELECT 1 FROM user_tree_closure
      WHERE matrix_instance_id = ? AND ancestor_user_id = ? AND descendant_user_id = ?
      LIMIT 1
      `,
      [mid, rid, cId]
    );
    if (dupRows.length) {
      const err = new Error('User already in matrix');
      err.status = 409;
      throw err;
    }

    // Determine policy depth
    let maxDepthPolicy = null;
    try {
      const [pRows] = await conn.query(`SELECT max_depth FROM matrix_instances WHERE id = ? LIMIT 1`, [mid]);
      if (pRows.length) maxDepthPolicy = pRows[0].max_depth == null ? null : Number(pRows[0].max_depth);
    } catch (_) {}

    let assignPos = null;

    if (parentId === rid) {
      // ROOT: unlimited children, sequential position
      // Do not mark rogue if admin explicitly chose root
      if (explicitRootChoice) {
        rogueFlag = false;
        fallback_reason = null;
      }
      const [rootPosRows] = await conn.query(
        `SELECT MAX(position) AS maxPos FROM user_tree_edges WHERE matrix_instance_id = ? AND parent_user_id = ?`,
        [mid, rid]
      );
      const maxPos = Number(rootPosRows[0]?.maxPos || 0);
      assignPos = maxPos + 1;                                    // sequential position under root
    } else {
      // NON-ROOT: enforce 1–5
      const [childPosRows] = await conn.query(
        `SELECT position FROM user_tree_edges WHERE matrix_instance_id = ? AND parent_user_id = ? ORDER BY position ASC`,
        [mid, parentId]
      );
      const used = new Set(childPosRows.map(r => Number(r.position)));
      for (let i = 1; i <= 5; i++) if (!used.has(i)) { assignPos = i; break; }

      if (!assignPos) {
        // BFS within the chosen parent subtree only
        const [candRows] = await conn.query(
          `
          SELECT d.descendant_user_id AS candidate_id
          FROM user_tree_closure AS d
          WHERE d.matrix_instance_id = ?
            AND d.ancestor_user_id = ?
            AND d.descendant_user_id != ?
          ORDER BY d.depth ASC, d.descendant_user_id ASC
          `,
          [mid, parentId, parentId]
        );

        for (const row of candRows) {
          const candidateParentId = Number(row.candidate_id);
          // skip root in BFS
          if (candidateParentId === rid) continue;
          // ensure candidate within max depth policy (parent depth + 1)
          if (maxDepthPolicy != null) {
            const [depthRows] = await conn.query(
              `SELECT depth FROM user_tree_closure WHERE matrix_instance_id = ? AND ancestor_user_id = ? AND descendant_user_id = ? LIMIT 1`,
              [mid, rid, candidateParentId]
            );
            const candDepthFromRoot = Number(depthRows[0]?.depth ?? 0);
            if (candDepthFromRoot + 1 > maxDepthPolicy) continue;
          }
          const [candPosRows] = await conn.query(
            `SELECT position FROM user_tree_edges WHERE matrix_instance_id = ? AND parent_user_id = ? ORDER BY position ASC`,
            [mid, candidateParentId]
          );
          const candUsed = new Set(candPosRows.map(r => Number(r.position)));
          for (let i = 1; i <= 5; i++) if (!candUsed.has(i)) { assignPos = i; break; }
          if (assignPos) {
            parentId = candidateParentId;
            break;
          }
        }

        if (!assignPos) {
          // no slot in subtree -> decide fallback
          if (forceParentFallback) {
            // If maxDepthPolicy prevented placement anywhere under referrer subtree, mark depth-limit reason when applicable
            fallback_reason = (maxDepthPolicy != null) ? 'referrer_depth_limit' : 'referrer_full';
            parentId = rid;
            rogueFlag = true;
            // root sequential
            const [rootPosRows] = await conn.query(
              `SELECT MAX(position) AS maxPos FROM user_tree_edges WHERE matrix_instance_id = ? AND parent_user_id = ?`,
              [mid, rid]
            );
            const maxPos = Number(rootPosRows[0]?.maxPos || 0);
            assignPos = maxPos + 1;
          } else {
            const err = new Error('Parent subtree is full (5-ary) or depth limit reached; no available position');
            err.status = 409;
            throw err;
          }
        }
      }
    }

    // Insert edge
    await conn.query(
      `INSERT INTO user_tree_edges
        (matrix_instance_id, parent_user_id, child_user_id, position, rogue_user)
       VALUES (?,?,?,?,?)`,
      [mid, parentId, cId, assignPos, rogueFlag]
    );

    // Self closure for child
    await conn.query(
      `INSERT IGNORE INTO user_tree_closure
        (matrix_instance_id, ancestor_user_id, descendant_user_id, depth)
       VALUES (?,?,?,0)`,
      [mid, cId, cId]
    );

    // Ancestor closure rows
    const [ancestorRows] = await conn.query(
      `SELECT ancestor_user_id, depth
       FROM user_tree_closure
       WHERE matrix_instance_id = ? AND descendant_user_id = ?
       ORDER BY depth ASC`,
      [mid, parentId]
    );
    if (ancestorRows.length) {
      const values = ancestorRows
        .map(r => `(${mid}, ${Number(r.ancestor_user_id)}, ${cId}, ${Number(r.depth) + 1})`)
        .join(',');
      await conn.query(
        `INSERT IGNORE INTO user_tree_closure
          (matrix_instance_id, ancestor_user_id, descendant_user_id, depth)
         VALUES ${values}`
      );
    }

    // Update instance metadata: immediate children count for root; do not compute first_free_position for root
    if (parentId === rid) {
      const [rootChildrenRows] = await conn.query(
        `SELECT COUNT(*) AS cnt FROM user_tree_edges WHERE matrix_instance_id = ? AND parent_user_id = ?`,
        [mid, rid]
      );
      await conn.query(
        `UPDATE matrix_instances
         SET immediate_children_count = ?
         WHERE id = ?`,
        [Number(rootChildrenRows[0]?.cnt || 0), mid]
      );
    }

    // Log
    try {
      await conn.query(
        `
        INSERT INTO user_action_logs
          (action, performed_by_user_id, affected_user_id, details, created_at)
        VALUES
          ('matrix_add_user', ?, ?, ?, NOW())
        `,
        [
          Number(actorUserId) || null,
          cId,
          JSON.stringify({
            matrixInstanceId: mid,
            rootUserId: rid,
            parentUserId: parentId,
            position: assignPos,
            rogue_user: rogueFlag,
            fallback_reason
          })
        ]
      );
    } catch (_) {}

    await conn.commit();
    return {
      matrixInstanceId: mid,
      rootUserId: rid,
      parentUserId: parentId,
      childUserId: cId,
      position: assignPos,
      rogue_user: rogueFlag,
      fallback_reason
    };
  } catch (e) {
    try { await conn.rollback(); } catch (_) {}
    throw e;
  } finally {
    conn.release();
  }
}

// NEW: resolve instance containing a given user via closure
async function resolveInstanceForUser(userId) {
  const conn = await pool.getConnection();
  try {
    const [rows] = await conn.query(
      `SELECT matrix_instance_id AS mid
       FROM user_tree_closure
       WHERE descendant_user_id = ?
       ORDER BY matrix_instance_id ASC
       LIMIT 1`,
      [Number(userId)]
    );
    if (!rows.length) return null;
    return { matrixInstanceId: Number(rows[0].mid) };
  } finally {
    conn.release();
  }
}

// NEW: fetch instance basic info
async function getInstanceInfo(instanceId) {
  const conn = await pool.getConnection();
  try {
    const [rows] = await conn.query(
      `SELECT id, root_user_id, max_depth
       FROM matrix_instances
       WHERE id = ? LIMIT 1`,
      [Number(instanceId)]
    );
    return rows.length ? rows[0] : null;
  } finally {
    conn.release();
  }
}

// NEW: list distinct matrix instances a user belongs to
async function listInstancesForUser(userId) {
  const conn = await pool.getConnection();
  try {
    const [rows] = await conn.query(
      `
      SELECT DISTINCT mi.id AS id, mi.name
      FROM matrix_instances mi
      JOIN user_tree_closure c
        ON c.matrix_instance_id = mi.id
      WHERE c.descendant_user_id = ?
      ORDER BY mi.id ASC
      `,
      [Number(userId)]
    );
    return rows.map(r => ({ id: Number(r.id), name: r.name || null }));
  } finally {
    conn.release();
  }
}

// NEW: check if user belongs to a specific instance
async function userBelongsToInstance(userId, instanceId) {
  const conn = await pool.getConnection();
  try {
    const [rows] = await conn.query(
      `
      SELECT 1
      FROM user_tree_closure
      WHERE matrix_instance_id = ? AND descendant_user_id = ?
      LIMIT 1
      `,
      [Number(instanceId), Number(userId)]
    );
    return rows.length > 0;
  } finally {
    conn.release();
  }
}

// NEW: deactivate a matrix instance by id (idempotent)
async function deactivateInstance(instanceId) {
  const conn = await pool.getConnection();
  try {
    const id = Number(instanceId);
    if (!Number.isFinite(id) || id <= 0) {
      const err = new Error('Invalid matrix instance id');
      err.status = 400;
      throw err;
    }

    const [rows] = await conn.query(
      `SELECT id, is_active FROM matrix_instances WHERE id = ? LIMIT 1`,
      [id]
    );
    if (!rows.length) {
      const err = new Error('Matrix instance not found');
      err.status = 404;
      throw err;
    }
    const wasActive = !!rows[0].is_active;

    // Try to set deactivated_at if column exists; fallback otherwise
    try {
      await conn.query(
        `UPDATE matrix_instances
         SET is_active = FALSE,
             deactivated_at = COALESCE(deactivated_at, NOW()),
             updated_at = CURRENT_TIMESTAMP
         WHERE id = ?`,
        [id]
      );
    } catch (e) {
      if (e && e.code === 'ER_BAD_FIELD_ERROR') {
        await conn.query(
          `UPDATE matrix_instances
           SET is_active = FALSE,
               updated_at = CURRENT_TIMESTAMP
           WHERE id = ?`,
          [id]
        );
      } else {
        throw e;
      }
    }

    return {
      matrixInstanceId: id,
      wasActive,
      isActive: false,
      status: wasActive ? 'deactivated' : 'already_inactive'
    };
  } finally {
    conn.release();
  }
}

// NEW: activate a matrix instance by id (idempotent)
async function activateInstance(instanceId) {
  const conn = await pool.getConnection();
  try {
    const id = Number(instanceId);
    if (!Number.isFinite(id) || id <= 0) {
      const err = new Error('Invalid matrix instance id');
      err.status = 400;
      throw err;
    }

    const [rows] = await conn.query(
      `SELECT id, is_active FROM matrix_instances WHERE id = ? LIMIT 1`,
      [id]
    );
    if (!rows.length) {
      const err = new Error('Matrix instance not found');
      err.status = 404;
      throw err;
    }
    const wasActive = !!rows[0].is_active;

    if (!wasActive) {
      // Try to clear deactivated_at if column exists
      try {
        await conn.query(
          `UPDATE matrix_instances
           SET is_active = TRUE,
               deactivated_at = NULL,
               updated_at = CURRENT_TIMESTAMP
           WHERE id = ?`,
          [id]
        );
      } catch (e) {
        if (e && e.code === 'ER_BAD_FIELD_ERROR') {
          await conn.query(
            `UPDATE matrix_instances
             SET is_active = TRUE,
                 updated_at = CURRENT_TIMESTAMP
             WHERE id = ?`,
            [id]
          );
        } else {
          throw e;
        }
      }
    }

    return {
      matrixInstanceId: id,
      wasActive,
      isActive: true,
      status: wasActive ? 'already_active' : 'activated'
    };
  } finally {
    conn.release();
  }
}

// NEW: ensure vacancy table exists
async function ensureVacancyTable(conn) {
  await conn.query(`
    CREATE TABLE IF NOT EXISTS matrix_vacancies (
      id INT AUTO_INCREMENT PRIMARY KEY,
      matrix_instance_id INT NOT NULL,
      parent_user_id INT NOT NULL,
      position INT NOT NULL,
      depth INT NOT NULL,
      created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
      UNIQUE KEY uniq_vacancy (matrix_instance_id, parent_user_id, position)
    )
  `);
}

// NEW: remove user and create vacancy
async function removeUserAndCreateVacancy(matrixInstanceId, userId) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();
    const mid = Number(matrixInstanceId);
    const uid = Number(userId);
    if (!Number.isFinite(mid) || mid <= 0 || !Number.isFinite(uid) || uid <= 0) {
      const err = new Error('Invalid parameters');
      err.status = 400;
      throw err;
    }
    const [rootRow] = await conn.query(`SELECT root_user_id FROM matrix_instances WHERE id = ? LIMIT 1`, [mid]);
    if (!rootRow.length) { const err = new Error('Matrix instance not found'); err.status = 404; throw err; }
    if (Number(rootRow[0].root_user_id) === uid) {
      const err = new Error('Cannot remove root user');
      err.status = 400;
      throw err;
    }
    const [edgeRows] = await conn.query(
      `SELECT parent_user_id, position FROM user_tree_edges WHERE matrix_instance_id = ? AND child_user_id = ? LIMIT 1`,
      [mid, uid]
    );
    if (!edgeRows.length) { const err = new Error('User not in matrix'); err.status = 404; throw err; }
    const parentId = Number(edgeRows[0].parent_user_id);
    const pos = Number(edgeRows[0].position);
    await ensureVacancyTable(conn);
    const [depthRows] = await conn.query(
      `SELECT depth FROM user_tree_closure WHERE matrix_instance_id = ? AND ancestor_user_id = ? AND descendant_user_id = ? LIMIT 1`,
      [mid, rootRow[0].root_user_id, parentId]
    );
    const depth = Number(depthRows[0]?.depth || 0) + 1;
    await conn.query(
      `INSERT INTO matrix_vacancies (matrix_instance_id, parent_user_id, position, depth)
       VALUES (?,?,?,?)
       ON DUPLICATE KEY UPDATE depth = VALUES(depth), created_at = CURRENT_TIMESTAMP`,
      [mid, parentId, pos, depth]
    );
    // delete subtree closure and edges
    await conn.query(
      `DELETE c FROM user_tree_closure c
       JOIN user_tree_closure d ON d.matrix_instance_id = c.matrix_instance_id AND d.descendant_user_id = c.descendant_user_id
       WHERE d.matrix_instance_id = ? AND d.ancestor_user_id = ?`,
      [mid, uid]
    );
    await conn.query(
      `DELETE FROM user_tree_edges WHERE matrix_instance_id = ? AND child_user_id IN (
        SELECT descendant_user_id FROM user_tree_closure WHERE matrix_instance_id = ? AND ancestor_user_id = ?
      )`,
      [mid, mid, uid]
    );
    await conn.commit();
    return { matrixInstanceId: mid, vacancy: { parentUserId: parentId, position: pos, depth } };
  } catch (e) {
    try { await conn.rollback(); } catch (_) {}
    throw e;
  } finally {
    conn.release();
  }
}

// NEW: list vacancies
async function listVacancies(matrixInstanceId) {
  const conn = await pool.getConnection();
  try {
    const mid = Number(matrixInstanceId);
    if (!Number.isFinite(mid) || mid <= 0) return [];
    await ensureVacancyTable(conn);
    const [rootRow] = await conn.query(`SELECT root_user_id FROM matrix_instances WHERE id = ? LIMIT 1`, [mid]);
    const rootId = Number(rootRow[0]?.root_user_id || 0);
    const [rows] = await conn.query(
      `SELECT v.matrix_instance_id AS matrixInstanceId, v.parent_user_id AS parentUserId, v.position, v.depth, v.created_at AS createdAt
       FROM matrix_vacancies v
       WHERE v.matrix_instance_id = ?
       ORDER BY v.created_at DESC`,
      [mid]
    );
    return rows.map(r => ({
      matrixInstanceId: mid,
      parentUserId: Number(r.parentUserId),
      position: Number(r.position),
      depth: Number(r.depth),
      root: rootId === Number(r.parentUserId),
      createdAt: r.createdAt
    }));
  } finally {
    conn.release();
  }
}

// NEW: assign user to vacancy
async function assignUserToVacancy({ matrixInstanceId, parentUserId, position, userId }) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();
    const mid = Number(matrixInstanceId);
    const pid = Number(parentUserId);
    const pos = Number(position);
    const uid = Number(userId);
    if (![mid, pid, pos, uid].every(n => Number.isFinite(n) && n > 0)) {
      const err = new Error('Invalid parameters');
      err.status = 400;
      throw err;
    }
    await ensureVacancyTable(conn);
    const [vacRows] = await conn.query(
      `SELECT depth FROM matrix_vacancies WHERE matrix_instance_id = ? AND parent_user_id = ? AND position = ? LIMIT 1`,
      [mid, pid, pos]
    );
    if (!vacRows.length) { const err = new Error('Vacancy not found'); err.status = 404; throw err; }
    const depthFromParent = Number(vacRows[0].depth);
    const [rootRow] = await conn.query(`SELECT root_user_id FROM matrix_instances WHERE id = ? LIMIT 1`, [mid]);
    const rootId = Number(rootRow[0]?.root_user_id || 0);
    const [depthRows] = await conn.query(
      `SELECT depth FROM user_tree_closure WHERE matrix_instance_id = ? AND ancestor_user_id = ? AND descendant_user_id = ? LIMIT 1`,
      [mid, rootId, pid]
    );
    const parentDepth = Number(depthRows[0]?.depth || 0);
    if (pid !== rootId && parentDepth + 1 > 5) {
      const err = new Error('Depth exceeds ego policy');
      err.status = 400;
      throw err;
    }
    const [inMatrix] = await conn.query(
      `SELECT 1 FROM user_tree_closure WHERE matrix_instance_id = ? AND descendant_user_id = ? LIMIT 1`,
      [mid, uid]
    );
    if (inMatrix.length) {
      const err = new Error('User already in matrix');
      err.status = 409;
      throw err;
    }
    await conn.query(
      `INSERT INTO user_tree_edges (matrix_instance_id, parent_user_id, child_user_id, position, rogue_user)
       VALUES (?,?,?,?,FALSE)`,
      [mid, pid, uid, pos]
    );
    await conn.query(
      `INSERT IGNORE INTO user_tree_closure (matrix_instance_id, ancestor_user_id, descendant_user_id, depth)
       VALUES (?,?,?,0)`,
      [mid, uid, uid]
    );
    const [ancRows] = await conn.query(
      `SELECT ancestor_user_id, depth FROM user_tree_closure WHERE matrix_instance_id = ? AND descendant_user_id = ?`,
      [mid, pid]
    );
    if (ancRows.length) {
      const values = ancRows.map(r => `(${mid}, ${Number(r.ancestor_user_id)}, ${uid}, ${Number(r.depth) + 1})`).join(',');
      await conn.query(
        `INSERT IGNORE INTO user_tree_closure (matrix_instance_id, ancestor_user_id, descendant_user_id, depth) VALUES ${values}`
      );
    }
    await conn.query(
      `DELETE FROM matrix_vacancies WHERE matrix_instance_id = ? AND parent_user_id = ? AND position = ?`,
      [mid, pid, pos]
    );
    await conn.commit();
    return { matrixInstanceId: mid, parentUserId: pid, position: pos, userId: uid };
  } catch (e) {
    try { await conn.rollback(); } catch (_) {}
    throw e;
  } finally {
    conn.release();
  }
}

module.exports = {
  createMatrix,
  ensureUserExistsByEmail,
  activateEgoMatrix,
  getMatrixStats,
  resolveRootUserId,
  getUserSearchCandidates,
  getMatrixUsers,
  addUserToMatrix,
  resolveInstanceForUser, // NEW
  getInstanceInfo, // NEW
  deactivateInstance, // NEW
  activateInstance, // NEW
  listInstancesForUser, // NEW
  userBelongsToInstance, // NEW
  removeUserAndCreateVacancy, // NEW
  listVacancies, // NEW
  assignUserToVacancy // NEW
};
