const assert = require("node:assert/strict");
const crypto = require("node:crypto");
const jwt = require("jsonwebtoken");
const db = require("../src/db/models");
const config = require("../src/config");

db.sequelize.options.logging = false;

const ids = {
  coachA: crypto.randomUUID(),
  coachB: crypto.randomUUID(),
  clientUserA: crypto.randomUUID(),
  clientUserB: crypto.randomUUID(),
  disabledClient: crypto.randomUUID(),
  clientA: crypto.randomUUID(),
  clientB: crypto.randomUUID(),
  sessionA: crypto.randomUUID(),
  sessionB: crypto.randomUUID(),
};

function tokenFor(user) {
  return jwt.sign({ user: { id: user.id, email: user.email } }, config.secret_key, { expiresIn: "5m" });
}

async function api(path, user) {
  return fetch(`http://127.0.0.1:3000/api/coaching${path}`, {
    headers: { Authorization: `Bearer ${tokenFor(user)}` },
  });
}

async function cleanup() {
  await db.sessions.destroy({ where: { id: [ids.sessionA, ids.sessionB] }, force: true });
  await db.clients.destroy({ where: { id: [ids.clientA, ids.clientB] }, force: true });
  await db.users.destroy({
    where: { id: [ids.coachA, ids.coachB, ids.clientUserA, ids.clientUserB, ids.disabledClient] },
    force: true,
  });
}

async function run() {
  const coachRole = await db.roles.findOne({ where: { name: "Coach" } });
  const clientRole = await db.roles.findOne({ where: { name: "Client" } });
  const adminRole = await db.roles.findOne({ where: { name: "Administrator" } });
  assert.ok(coachRole && clientRole && adminRole, "Required roles must exist");

  const admin = await db.users.findOne({ where: { app_roleId: adminRole.id, disabled: false } });
  assert.ok(admin, "An enabled Administrator fixture must exist");

  const [coachA, coachB, clientUserA, clientUserB, disabledClient] = await Promise.all([
    db.users.create({ id: ids.coachA, email: `fixture-coach-a-${ids.coachA}@example.invalid`, app_roleId: coachRole.id, emailVerified: true }),
    db.users.create({ id: ids.coachB, email: `fixture-coach-b-${ids.coachB}@example.invalid`, app_roleId: coachRole.id, emailVerified: true }),
    db.users.create({ id: ids.clientUserA, email: `fixture-client-a-${ids.clientUserA}@example.invalid`, app_roleId: clientRole.id, emailVerified: true }),
    db.users.create({ id: ids.clientUserB, email: `fixture-client-b-${ids.clientUserB}@example.invalid`, app_roleId: clientRole.id, emailVerified: true }),
    db.users.create({ id: ids.disabledClient, email: `fixture-disabled-${ids.disabledClient}@example.invalid`, app_roleId: clientRole.id, emailVerified: true, disabled: true }),
  ]);

  await db.clients.bulkCreate([
    { id: ids.clientA, name: "Synthetic Client A", email: clientUserA.email, ownerId: coachA.id, portalUserId: clientUserA.id, notes: "private-a" },
    { id: ids.clientB, name: "Synthetic Client B", email: clientUserB.email, ownerId: coachB.id, portalUserId: clientUserB.id, notes: "private-b" },
  ]);
  await db.sessions.bulkCreate([
    { id: ids.sessionA, clientId: ids.clientA, title: "Synthetic Session A", status: "shared", shared_client_notes: "safe-a", private_coach_notes: "private-a" },
    { id: ids.sessionB, clientId: ids.clientB, title: "Synthetic Session B", status: "shared", shared_client_notes: "safe-b", private_coach_notes: "private-b" },
  ]);

  const coachAListResponse = await api("/clients", coachA);
  assert.equal(coachAListResponse.status, 200);
  const coachAList = await coachAListResponse.json();
  assert.equal(coachAList.some((client) => client.id === ids.clientA), true);
  assert.equal(coachAList.some((client) => client.id === ids.clientB), false);

  assert.equal((await api(`/clients/${ids.clientB}`, coachA)).status, 404);
  assert.equal((await api(`/sessions/${ids.sessionB}`, coachA)).status, 404);
  assert.equal((await api(`/sessions/${ids.sessionB}/audio`, coachA)).status, 404);

  const portalAResponse = await api("/client-portal/me", clientUserA);
  assert.equal(portalAResponse.status, 200);
  const portalA = await portalAResponse.json();
  assert.equal(portalA.id, ids.clientA);
  assert.equal(portalA.sessions.some((session) => session.id === ids.sessionB), false);
  assert.equal(Object.prototype.hasOwnProperty.call(portalA, "notes"), false);
  assert.equal((await api(`/client-portal/${ids.clientB}`, clientUserA)).status, 404);
  assert.equal((await api(`/clients/${ids.clientA}`, clientUserA)).status, 403);

  assert.equal((await api(`/clients/${ids.clientB}`, admin)).status, 200);
  assert.equal((await api("/client-portal/me", disabledClient)).status, 401);

  const loggedOut = await fetch("http://127.0.0.1:3000/api/coaching/clients");
  assert.equal(loggedOut.status, 401);

  console.log("live_isolation_matrix=passed");
}

run()
  .finally(cleanup)
  .then(() => db.sequelize.close())
  .catch(async (error) => {
    console.error(error);
    await db.sequelize.close();
    process.exitCode = 1;
  });
