'use strict';
// @ts-check
// ==================================================================================
// users.js
// ----------------------------------------------------------------------------------
// Description:   System Information - library
//                for Node.js
// Copyright:     (c) 2014 - 2026
// Author:        Sebastian Hildebrandt
// ----------------------------------------------------------------------------------
// License:       MIT
// ==================================================================================
// 11. Users/Sessions
// ----------------------------------------------------------------------------------

const exec = require('child_process').exec;
const util = require('./util');

const _platform = process.platform;

const _linux = _platform === 'linux' || _platform === 'android';
const _darwin = _platform === 'darwin';
const _windows = _platform === 'win32';
const _freebsd = _platform === 'freebsd';
const _openbsd = _platform === 'openbsd';
const _netbsd = _platform === 'netbsd';
const _sunos = _platform === 'sunos';

function parseDate(dtMon, dtDay) {
  let dt = new Date().toISOString().slice(0, 10);
  try {
    dt = '' + new Date().getFullYear() + '-' + ('0' + ('JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC'.indexOf(dtMon.toUpperCase()) / 3 + 1)).slice(-2) + '-' + ('0' + dtDay).slice(-2);
    if (new Date(dt) > new Date()) {
      dt = '' + (new Date().getFullYear() - 1) + '-' + ('0' + ('JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC'.indexOf(dtMon.toUpperCase()) / 3 + 1)).slice(-2) + '-' + ('0' + dtDay).slice(-2);
    }
  } catch {
    util.noop();
  }
  return dt;
}

function parseUsersLinux(lines, phase) {
  const result = [];
  let result_who = [];
  const result_w = {};
  let w_first = true;
  let w_header = [];
  const w_pos = [];
  let who_line = {};

  let is_whopart = true;
  let is_whoerror = false;
  lines.forEach((line) => {
    if (line === '---') {
      is_whopart = false;
    } else {
      const l = line.replace(/ +/g, ' ').split(' ');
      // who part
      if (is_whopart) {
        if (line.toLowerCase().indexOf('unexpected') >= 0 || line.toLowerCase().indexOf('unrecognized') >= 0) {
          is_whoerror = true;
          result_who = [];
        }
        if (!is_whoerror) {
          const timePos = l && l.length > 4 && l[4].indexOf(':') > 0 ? 4 : 3;
          result_who.push({
            user: l[0],
            tty: l[1],
            date: timePos === 4 ? parseDate(l[2], l[3]) : l[2],
            time: l[timePos],
            ip: l && l.length > timePos + 1 ? l[timePos + 1].replace(/\(/g, '').replace(/\)/g, '') : '',
            command: ''
          });
        }
      } else {
        // w part
        if (w_first) {
          // header
          if (line[0] !== ' ') {
            w_header = l;
            w_header.forEach((item) => {
              w_pos.push(line.indexOf(item));
            });
            w_first = false;
          }
        } else {
          // split by w_pos
          result_w.user = line.substring(w_pos[0], w_pos[1] - 1).trim();
          result_w.tty = line.substring(w_pos[1], w_pos[2] - 1).trim();
          result_w.ip = line
            .substring(w_pos[2], w_pos[3] - 1)
            .replace(/\(/g, '')
            .replace(/\)/g, '')
            .trim();
          result_w.command = line.substring(w_pos[7], 1000).trim();
          // find corresponding 'who' line
          if (result_who.length || phase === 1) {
            who_line = result_who.filter((obj) => {
              return obj.user.substring(0, 8).trim() === result_w.user && obj.tty === result_w.tty;
            });
          } else {
            who_line = [{ user: result_w.user, tty: result_w.tty, date: '', time: '', ip: '' }];
          }
          if (who_line.length === 1 && who_line[0].user !== '') {
            result.push({
              user: who_line[0].user,
              tty: who_line[0].tty,
              date: who_line[0].date,
              time: who_line[0].time,
              ip: who_line[0].ip,
              command: result_w.command
            });
          }
        }
      }
    }
  });
  if (result.length === 0 && phase === 2) {
    return result_who;
  } else {
    return result;
  }
}

function parseUsersDarwin(lines) {
  const result = [];
  const result_who = [];
  const result_w = {};
  let who_line = {};

  let is_whopart = true;
  lines.forEach((line) => {
    if (line === '---') {
      is_whopart = false;
    } else {
      const l = line.replace(/ +/g, ' ').split(' ');

      // who part
      if (is_whopart) {
        result_who.push({
          user: l[0],
          tty: l[1],
          date: parseDate(l[2], l[3]),
          time: l[4]
        });
      } else {
        // w part
        // split by w_pos
        result_w.user = l[0];
        result_w.tty = l[1];
        result_w.ip = l[2] !== '-' ? l[2] : '';
        result_w.command = l.slice(5, 1000).join(' ');
        // find corresponding 'who' line
        who_line = result_who.filter((obj) => obj.user.substring(0, 10) === result_w.user.substring(0, 10) && (obj.tty.substring(3, 1000) === result_w.tty || obj.tty === result_w.tty));
        if (who_line.length === 1) {
          result.push({
            user: who_line[0].user,
            tty: who_line[0].tty,
            date: who_line[0].date,
            time: who_line[0].time,
            ip: result_w.ip,
            command: result_w.command
          });
        }
      }
    }
  });
  return result;
}

function users(callback) {
  return new Promise((resolve) => {
    process.nextTick(() => {
      let result = [];

      // linux
      if (_linux) {
        exec('export LC_ALL=C; who --ips; echo "---"; w; unset LC_ALL | tail -n +2', (error, stdout) => {
          if (!error) {
            // lines / split
            let lines = stdout.toString().split('\n');
            result = parseUsersLinux(lines, 1);
            if (result.length === 0) {
              exec('who; echo "---"; w | tail -n +2', (error, stdout) => {
                if (!error) {
                  // lines / split
                  lines = stdout.toString().split('\n');
                  result = parseUsersLinux(lines, 2);
                }
                if (callback) {
                  callback(result);
                }
                resolve(result);
              });
            } else {
              if (callback) {
                callback(result);
              }
              resolve(result);
            }
          } else {
            if (callback) {
              callback(result);
            }
            resolve(result);
          }
        });
      }
      if (_freebsd || _openbsd || _netbsd) {
        exec('who; echo "---"; w -ih', (error, stdout) => {
          if (!error) {
            // lines / split
            const lines = stdout.toString().split('\n');
            result = parseUsersDarwin(lines);
          }
          if (callback) {
            callback(result);
          }
          resolve(result);
        });
      }
      if (_sunos) {
        exec('who; echo "---"; w -h', (error, stdout) => {
          if (!error) {
            // lines / split
            const lines = stdout.toString().split('\n');
            result = parseUsersDarwin(lines);
          }
          if (callback) {
            callback(result);
          }
          resolve(result);
        });
      }

      if (_darwin) {
        exec('export LC_ALL=C; who; echo "---"; w -ih; unset LC_ALL', (error, stdout) => {
          if (!error) {
            // lines / split
            const lines = stdout.toString().split('\n');
            result = parseUsersDarwin(lines);
          }
          if (callback) {
            callback(result);
          }
          resolve(result);
        });
      }
      if (_windows) {
        try {
          let cmd = 'Get-CimInstance Win32_LogonSession | select LogonId,@{n="StartTime";e={$_.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}} | fl' + "; echo '#-#-#-#';";
          cmd += 'Get-CimInstance Win32_LoggedOnUser | select antecedent,dependent | fl ' + "; echo '#-#-#-#';";
          cmd +=
            "$process = (Get-CimInstance Win32_Process -Filter \"name = 'explorer.exe'\"); Invoke-CimMethod -InputObject $process[0] -MethodName GetOwner | select user, domain | fl; get-process -name explorer | select-object sessionid | fl; echo '#-#-#-#';";
          cmd += 'query user';
          util.powerShell(cmd).then((data) => {
            if (data) {
              data = data.split('#-#-#-#');
              const sessions = parseWinSessions((data[0] || '').split(/\n\s*\n/));
              const loggedons = parseWinLoggedOn((data[1] || '').split(/\n\s*\n/));
              const queryUser = parseWinUsersQuery((data[3] || '').split('\r\n'));
              const users = parseWinUsers((data[2] || '').split(/\n\s*\n/), queryUser);
              for (let id in loggedons) {
                if ({}.hasOwnProperty.call(loggedons, id)) {
                  loggedons[id].dateTime = {}.hasOwnProperty.call(sessions, id) ? sessions[id] : '';
                }
              }
              users.forEach((user) => {
                let dateTime = '';
                for (let id in loggedons) {
                  if ({}.hasOwnProperty.call(loggedons, id)) {
                    if (loggedons[id].user === user.user && (!dateTime || dateTime < loggedons[id].dateTime)) {
                      dateTime = loggedons[id].dateTime;
                    }
                  }
                }

                result.push({
                  user: user.user,
                  tty: user.tty,
                  date: `${dateTime.substring(0, 10)}`,
                  time: `${dateTime.substring(11, 19)}`,
                  ip: '',
                  command: ''
                });
              });
            }
            if (callback) {
              callback(result);
            }
            resolve(result);
          });
        } catch {
          if (callback) {
            callback(result);
          }
          resolve(result);
        }
      }
    });
  });
}

function parseWinSessions(sessionParts) {
  const sessions = {};
  sessionParts.forEach((session) => {
    const lines = session.split('\r\n');
    const id = util.getValue(lines, 'LogonId');
    const starttime = util.getValue(lines, 'starttime');
    if (id) {
      sessions[id] = starttime;
    }
  });
  return sessions;
}

function fuzzyMatch(name1, name2) {
  name1 = name1.toLowerCase();
  name2 = name2.toLowerCase();
  let eq = 0;
  let len = name1.length;
  if (name2.length > len) {
    len = name2.length;
  }

  for (let i = 0; i < len; i++) {
    const c1 = name1[i] || '';
    const c2 = name2[i] || '';
    if (c1 === c2) {
      eq++;
    }
  }
  return len > 10 ? eq / len > 0.9 : len > 0 ? eq / len > 0.8 : false;
}

function parseWinUsers(userParts, userQuery) {
  const users = [];
  userParts.forEach((user) => {
    const lines = user.split('\r\n');

    const domain = util.getValue(lines, 'domain', ':', true);
    const username = util.getValue(lines, 'user', ':', true);
    const sessionid = util.getValue(lines, 'sessionid', ':', true);

    if (username) {
      const quser = userQuery.filter((item) => fuzzyMatch(item.user, username));
      users.push({
        domain,
        user: username,
        tty: quser && quser[0] && quser[0].tty ? quser[0].tty : sessionid
      });
    }
  });
  return users;
}

function parseWinLoggedOn(loggedonParts) {
  const loggedons = {};
  loggedonParts.forEach((loggedon) => {
    const lines = loggedon.split('\r\n');

    const antecendent = util.getValue(lines, 'antecedent', ':', true);
    let parts = antecendent.split('=');
    const name = parts.length > 2 ? parts[1].split(',')[0].replace(/"/g, '').trim() : '';
    const domain = parts.length > 2 ? parts[2].replace(/"/g, '').replace(/\)/g, '').trim() : '';
    const dependent = util.getValue(lines, 'dependent', ':', true);
    parts = dependent.split('=');
    const id = parts.length > 1 ? parts[1].replace(/"/g, '').replace(/\)/g, '').trim() : '';
    if (id) {
      loggedons[id] = {
        domain,
        user: name
      };
    }
  });
  return loggedons;
}

function parseWinUsersQuery(lines) {
  lines = lines.filter((item) => item);
  let result = [];
  const header = lines[0];
  const headerDelimiter = [];
  if (header) {
    const start = header[0] === ' ' ? 1 : 0;
    headerDelimiter.push(start - 1);
    let nextSpace = 0;
    for (let i = start + 1; i < header.length; i++) {
      if (header[i] === ' ' && (header[i - 1] === ' ' || header[i - 1] === '.')) {
        nextSpace = i;
      } else {
        if (nextSpace) {
          headerDelimiter.push(nextSpace);
          nextSpace = 0;
        }
      }
    }
    for (let i = 1; i < lines.length; i++) {
      if (lines[i].trim()) {
        const user = lines[i].substring(headerDelimiter[0] + 1, headerDelimiter[1]).trim() || '';
        const tty = lines[i].substring(headerDelimiter[1] + 1, headerDelimiter[2] - 2).trim() || '';
        result.push({
          user: user,
          tty: tty
        });
      }
    }
  }
  return result;
}

exports.users = users;
