// Pure drift computation: compares the POSHManager plan (expected) against what
// Microsoft Graph currently reports (live), for the win32LobApp metadata, its
// assignments, and its supersedence/dependency relationships. No I/O — the
// controller fetches live state and feeds it here, so this is fully unit-tested.

// Fields on the win32LobApp we treat as authoritative from the plan.
const APP_DRIFT_FIELDS = [
  'displayName',
  'publisher',
  'installCommandLine',
  'uninstallCommandLine',
  'applicableArchitectures'
];

function fieldDrift(expected = {}, actual = {}, fields = APP_DRIFT_FIELDS) {
  const drift = [];
  for (const field of fields) {
    const want = expected[field] ?? '';
    const got = actual[field] ?? '';
    if (String(want) !== String(got)) drift.push({ field, expected: want, actual: got });
  }
  return drift;
}

function assignmentKey(assignment = {}) {
  const target = assignment.target || {};
  const type = (target['@odata.type'] || '').replace('#microsoft.graph.', '');
  return `${assignment.intent || ''}|${type}|${target.groupId || ''}`;
}

function relationshipKey(rel = {}) {
  const type = (rel['@odata.type'] || '').replace('#microsoft.graph.', '');
  return `${type}|${rel.targetId || ''}`;
}

// Generic set diff by key: returns items present only in expected (missing in
// tenant) and only in actual (extra in tenant).
function setDiff(expected, actual, keyFn) {
  const expectedKeys = new Map(expected.map((item) => [keyFn(item), item]));
  const actualKeys = new Map(actual.map((item) => [keyFn(item), item]));
  const missing = [...expectedKeys.keys()].filter((k) => !actualKeys.has(k));
  const extra = [...actualKeys.keys()].filter((k) => !expectedKeys.has(k));
  return { missing, extra };
}

export function computeDrift({
  expectedApp = {},
  liveApp = {},
  expectedAssignments = [],
  liveAssignments = [],
  expectedRelationships = [],
  liveRelationships = []
} = {}) {
  const appDrift = fieldDrift(expectedApp, liveApp);
  const assignmentDrift = setDiff(expectedAssignments, liveAssignments, assignmentKey);
  const relationshipDrift = setDiff(expectedRelationships, liveRelationships, relationshipKey);

  const inSync =
    appDrift.length === 0 &&
    assignmentDrift.missing.length === 0 &&
    assignmentDrift.extra.length === 0 &&
    relationshipDrift.missing.length === 0 &&
    relationshipDrift.extra.length === 0;

  return { inSync, appDrift, assignmentDrift, relationshipDrift };
}
