import { db, adminUserId, makeUser, load } from './setup.mjs';
import test from 'node:test';
import assert from 'node:assert/strict';

const {
  createChangeRequest, findApprovedRequest, findPendingRequest, decideChangeRequest, markRequestExecuted, listChangeRequests
} = await load('../models/changeRequestModel.js');
const { intuneReportingOverview, recordGraphAudit } = await load('../models/graphModel.js');
const { createIntuneDeployment } = await load('../models/psadtModel.js');

const owner = adminUserId();
const other = makeUser({ email: 'gov-other@test.local' });

const deployment = createIntuneDeployment({ name: 'Gov Test App', visibility: 'shared', status: 'ready' }, owner);

test('approval lifecycle: pending -> approved -> executed', () => {
  const request = createChangeRequest({ deploymentId: deployment.id, connectionId: null, action: 'publish', requestedBy: owner, requesterEmail: 'a@b.c' });
  assert.equal(request.status, 'pending');
  assert.equal(findPendingRequest(deployment.id, 'publish').id, request.id);
  assert.equal(findApprovedRequest(deployment.id, 'publish'), null);

  const approved = decideChangeRequest(request.id, { status: 'approved', approverUserId: owner, approverEmail: 'admin@b.c' });
  assert.equal(approved.status, 'approved');
  assert.equal(findApprovedRequest(deployment.id, 'publish').id, request.id);

  markRequestExecuted(request.id);
  assert.equal(findApprovedRequest(deployment.id, 'publish'), null, 'executed request is no longer approved');
});

test('deciding an already-decided request returns null', () => {
  const request = createChangeRequest({ deploymentId: deployment.id, action: 'assign', requestedBy: owner });
  decideChangeRequest(request.id, { status: 'rejected', approverUserId: owner });
  assert.equal(decideChangeRequest(request.id, { status: 'approved', approverUserId: owner }), null);
});

test('change requests are scoped to visible deployments for non-admins', () => {
  createChangeRequest({ deploymentId: deployment.id, action: 'relationships', requestedBy: owner });
  // deployment is shared, so a non-admin still sees its requests
  assert.ok(listChangeRequests(other, { isAdmin: false }).some((r) => r.deploymentId === deployment.id));
});

test('reporting overview aggregates deployment status and install states', () => {
  const ts = new Date().toISOString();
  const insert = db.prepare(`
    INSERT INTO intune_deployment_statuses (id, deployment_id, graph_app_id, scope, install_state, error_code, created_at, updated_at)
    VALUES (?, ?, 'app-1', 'device', ?, ?, ?, ?)
  `);
  insert.run('ids_1', deployment.id, 'failed', '0x87D1041C', ts, ts);
  insert.run('ids_2', deployment.id, 'installed', '', ts, ts);
  insert.run('ids_3', deployment.id, 'failed', '0x87D1041C', ts, ts);

  const overview = intuneReportingOverview(owner, false);
  const failed = overview.installStateCounts.find((row) => row.state === 'failed');
  assert.equal(failed.count, 2);
  assert.ok(overview.deploymentsByStatus.some((row) => row.status === 'ready'));
  assert.equal(overview.topErrors[0].code, '0x87D1041C');
  assert.equal(overview.topErrors[0].count, 2);
});

test('reporting overview includes soak timers and a write-action trend', () => {
  const soaking = createIntuneDeployment({ name: 'Soak Report', visibility: 'shared', autoPromoteAfterHours: 24, autoPromoteRing: 'Broad' }, owner);
  db.prepare('UPDATE intune_deployments SET soak_started_at = ? WHERE id = ?')
    .run(new Date(Date.now() - 3_600_000).toISOString(), soaking.id);
  recordGraphAudit({ deploymentId: soaking.id, actorUserId: owner, action: 'win32app.publish', status: 'success', message: 'ok' });

  const overview = intuneReportingOverview(owner, false);
  const timer = overview.soakTimers.find((row) => row.id === soaking.id);
  assert.ok(timer, 'soak timer present');
  assert.ok(timer.hoursRemaining > 22 && timer.hoursRemaining <= 24, `~23h remaining, got ${timer.hoursRemaining}`);
  assert.ok(overview.auditTrend.some((row) => row.status === 'success' && row.count >= 1));
});
