/**
 * lib/org.js — Organization domain layer.
 *
 * All functions call getDb() directly. IDs are generated via newId().
 * Every write is audited via appendAudit() from lib/runtime.js.
 *
 * Role values: 'owner' | 'admin' | 'member' | 'viewer'
 */

import { getDb, nowIso, newId } from './db.js';

// appendAudit is imported lazily to avoid circular dependency at module load.
// lib/runtime.js imports lib/db.js; lib/org.js also imports lib/db.js — fine.
// lib/runtime.js does NOT import lib/org.js, so the lazy import below is safe.
async function _audit(entry) {
  try {
    const { appendAudit } = await import('./runtime.js');
    appendAudit(entry);
  } catch {
    // Non-fatal — audit failure must never block the primary operation.
  }
}

// ── Org CRUD ──────────────────────────────────────────────────────────────────

/**
 * Creates a new organization.
 * @param {{ slug: string, name: string, plan?: string }} opts
 * @returns {object} The newly created organization row.
 */
export function createOrg({ slug, name, plan = 'free' }) {
  if (!slug || !name) throw new Error('slug and name are required');
  const db = getDb();
  const id = newId('org');
  const now = nowIso();
  db.prepare(
    `INSERT INTO organizations (id, slug, name, plan, created_at, updated_at)
     VALUES (?, ?, ?, ?, ?, ?)`
  ).run(id, slug, name, plan, now, now);
  _audit({ type: 'org.create', summary: `Created org "${name}" (${slug})`, meta: { id, slug } });
  return db.prepare('SELECT * FROM organizations WHERE id = ?').get(id);
}

/**
 * Returns an org by slug, or null.
 */
export function getOrgBySlug(slug) {
  return getDb().prepare('SELECT * FROM organizations WHERE slug = ?').get(slug) || null;
}

/**
 * Returns an org by id, or null.
 */
export function getOrgById(id) {
  return getDb().prepare('SELECT * FROM organizations WHERE id = ?').get(id) || null;
}

/**
 * Returns all orgs the given user is a member of.
 * @param {string} userId
 * @returns {object[]}
 */
export function listOrgsForUser(userId) {
  return getDb()
    .prepare(
      `SELECT o.*
       FROM organizations o
       JOIN org_memberships m ON m.org_id = o.id
       WHERE m.user_id = ?
       ORDER BY o.created_at ASC`
    )
    .all(userId);
}

// ── Membership management ──────────────────────────────────────────────────────

/**
 * Ensures a users row exists for `userId` so the org_memberships.user_id FK is
 * satisfiable. The invite-redeem and email-claim paths call addMember with a
 * Supabase user_id that may not yet have a local users row (the login-time
 * upsert can race or be skipped on a fresh mirror) — without this guard the
 * membership INSERT throws a FK violation that surfaces as a generic
 * "Failed to join organization" 500. Idempotent; a placeholder is created only
 * when no row exists. Optional email/displayName hydrate the placeholder.
 */
function ensureUserRow(db, userId, { email = null, displayName = null } = {}) {
  if (!userId) return;
  const existing = db.prepare('SELECT id FROM users WHERE id = ?').get(userId);
  if (existing) return;
  db.prepare(
    `INSERT INTO users (id, email, display_name, auth_provider, created_at)
     VALUES (?, ?, ?, 'invited', ?)`
  ).run(userId, email || '', displayName || null, nowIso());
}

/**
 * Adds a user to an org with the given role.
 * Idempotent: if the membership already exists, updates the role.
 */
export function addMember(orgId, userId, role = 'member', userInfo = {}) {
  const db = getDb();
  ensureUserRow(db, userId, userInfo);
  const existing = db
    .prepare('SELECT id FROM org_memberships WHERE org_id = ? AND user_id = ?')
    .get(orgId, userId);

  if (existing) {
    db.prepare(
      `UPDATE org_memberships SET role = ? WHERE org_id = ? AND user_id = ?`
    ).run(role, orgId, userId);
    _audit({
      type: 'org.member.role.changed',
      summary: `User ${userId} role changed to ${role} in org ${orgId}`,
      meta: { org_id: orgId, user_id: userId, role },
    });
    return existing;
  }

  const id = newId('mem');
  const now = nowIso();
  db.prepare(
    `INSERT INTO org_memberships (id, org_id, user_id, role, accepted_at)
     VALUES (?, ?, ?, ?, ?)`
  ).run(id, orgId, userId, role, now);
  _audit({
    type: 'org.member.invited',
    summary: `User ${userId} added to org ${orgId} as ${role}`,
    meta: { id, org_id: orgId, user_id: userId, role },
  });
  return { id, org_id: orgId, user_id: userId, role, accepted_at: now };
}

/**
 * Removes a user from an org.
 * Throws if the user is the last owner.
 */
export function removeMember(orgId, userId) {
  const db = getDb();

  // Protect: do not remove the last owner.
  const membership = db
    .prepare("SELECT role FROM org_memberships WHERE org_id = ? AND user_id = ?")
    .get(orgId, userId);
  if (!membership) return; // already gone

  if (membership.role === 'owner') {
    const ownerCount = db
      .prepare("SELECT COUNT(*) AS c FROM org_memberships WHERE org_id = ? AND role = 'owner'")
      .get(orgId).c;
    if (ownerCount <= 1) {
      throw new Error('Cannot remove the last owner of an organization.');
    }
  }

  db.prepare('DELETE FROM org_memberships WHERE org_id = ? AND user_id = ?').run(orgId, userId);
  _audit({
    type: 'org.member.removed',
    summary: `User ${userId} removed from org ${orgId}`,
    meta: { org_id: orgId, user_id: userId },
  });
}

/**
 * Returns the membership row for a user in an org, or null.
 */
export function getMembership(userId, orgId) {
  return (
    getDb()
      .prepare('SELECT * FROM org_memberships WHERE user_id = ? AND org_id = ?')
      .get(userId, orgId) || null
  );
}

/**
 * Returns all members of an org with their user info.
 */
export function listMembers(orgId) {
  return getDb()
    .prepare(
      `SELECT m.*, u.email, u.display_name, u.avatar_url, u.last_sign_in_at
       FROM org_memberships m
       JOIN users u ON u.id = m.user_id
       WHERE m.org_id = ?
       ORDER BY CASE m.role
         WHEN 'owner'  THEN 0
         WHEN 'admin'  THEN 1
         WHEN 'member' THEN 2
         WHEN 'viewer' THEN 3
         ELSE 4
       END, m.accepted_at ASC`
    )
    .all(orgId);
}

/**
 * Changes a user's role within an org.
 * Only 'owner' callers should call this (enforced at the API route layer).
 */
export function changeRole(orgId, userId, newRole) {
  const VALID_ROLES = new Set(['owner', 'admin', 'member', 'viewer']);
  if (!VALID_ROLES.has(newRole)) {
    throw new Error(`Invalid role: ${newRole}. Must be one of: owner, admin, member, viewer`);
  }

  const db = getDb();
  const existing = db
    .prepare('SELECT id FROM org_memberships WHERE org_id = ? AND user_id = ?')
    .get(orgId, userId);
  if (!existing) {
    throw new Error(`User ${userId} is not a member of org ${orgId}`);
  }

  // Protect last owner.
  const current = db
    .prepare('SELECT role FROM org_memberships WHERE org_id = ? AND user_id = ?')
    .get(orgId, userId);
  if (current?.role === 'owner' && newRole !== 'owner') {
    const ownerCount = db
      .prepare("SELECT COUNT(*) AS c FROM org_memberships WHERE org_id = ? AND role = 'owner'")
      .get(orgId).c;
    if (ownerCount <= 1) {
      throw new Error('Cannot demote the last owner of an organization.');
    }
  }

  db.prepare(
    `UPDATE org_memberships SET role = ? WHERE org_id = ? AND user_id = ?`
  ).run(newRole, orgId, userId);

  _audit({
    type: 'org.member.role.changed',
    summary: `User ${userId} role changed to ${newRole} in org ${orgId}`,
    meta: { org_id: orgId, user_id: userId, new_role: newRole },
  });
}
