/**
 * Space Email Alias Service — Mailcow forwarding alias management per space.
 *
 * Provisions {space}@rspace.online aliases that forward to opted-in members'
 * personal emails. Admins/moderators are opted in by default.
 */

import {
  isMailcowConfigured,
  createAlias,
  deleteAlias,
  updateAlias,
  findAliasId,
  createMailbox,
  deleteMailbox,
  mailboxExists,
} from './mailcow.js';
import {
  listSpaceMembers,
  getSpaceEmailAlias,
  setSpaceEmailAlias,
  deleteSpaceEmailAlias,
  upsertSpaceEmailForwarding,
  getOptedInDids,
  getProfileEmailsByDids,
  getAgentMailbox,
  setAgentMailbox,
  deleteAgentMailbox,
} from './db.js';

const TAG = '[SpaceAlias]';

/** Compute comma-separated goto from opted-in members with emails */
async function computeGoto(spaceSlug: string): Promise<string> {
  const dids = await getOptedInDids(spaceSlug);
  if (dids.length === 0) return `noreply+${spaceSlug}@rspace.online`;

  const emailMap = await getProfileEmailsByDids(dids);
  const emails = [...emailMap.values()].filter(Boolean);
  if (emails.length === 0) return `noreply+${spaceSlug}@rspace.online`;

  return emails.join(',');
}

/**
 * Provision a Mailcow forwarding alias for a space.
 * Seeds opt-in rows for all current members (admin/mod = opted in).
 * Idempotent — skips if alias already exists.
 */
export async function provisionSpaceAlias(spaceSlug: string): Promise<void> {
  if (!isMailcowConfigured()) {
    console.warn(`${TAG} Mailcow not configured, skipping alias for ${spaceSlug}`);
    return;
  }

  // Check if we already have it in DB
  const existing = await getSpaceEmailAlias(spaceSlug);
  if (existing) {
    console.log(`${TAG} Alias already exists for ${spaceSlug} (id: ${existing.mailcowId})`);
    return;
  }

  // Check if Mailcow already has it (e.g. manually created)
  const address = `${spaceSlug}@rspace.online`;
  const existingMailcowId = await findAliasId(address);
  if (existingMailcowId) {
    await setSpaceEmailAlias(spaceSlug, existingMailcowId);
    console.log(`${TAG} Adopted existing Mailcow alias for ${spaceSlug} (id: ${existingMailcowId})`);
  }

  // Seed opt-in rows for all members
  const members = await listSpaceMembers(spaceSlug);
  for (const m of members) {
    const optIn = m.role === 'admin' || m.role === 'moderator';
    await upsertSpaceEmailForwarding(spaceSlug, m.userDID, optIn);
  }

  const goto = await computeGoto(spaceSlug);

  if (existingMailcowId) {
    // Update the existing alias with current goto
    await updateAlias(existingMailcowId, goto);
    return;
  }

  // Create new alias
  const mailcowId = await createAlias(spaceSlug, goto);
  await setSpaceEmailAlias(spaceSlug, mailcowId);
  console.log(`${TAG} Provisioned alias for ${spaceSlug} → ${goto} (id: ${mailcowId})`);
}

/**
 * Recompute and update the forwarding destinations for a space alias.
 * If the alias doesn't exist yet, delegates to provisionSpaceAlias.
 */
export async function syncSpaceAlias(spaceSlug: string): Promise<void> {
  if (!isMailcowConfigured()) return;

  const alias = await getSpaceEmailAlias(spaceSlug);
  if (!alias) {
    await provisionSpaceAlias(spaceSlug);
    return;
  }

  const goto = await computeGoto(spaceSlug);
  await updateAlias(alias.mailcowId, goto);
  console.log(`${TAG} Synced alias for ${spaceSlug} → ${goto}`);
}

/**
 * Remove the Mailcow alias and DB records for a space.
 */
export async function deprovisionSpaceAlias(spaceSlug: string): Promise<void> {
  if (!isMailcowConfigured()) return;

  const alias = await getSpaceEmailAlias(spaceSlug);
  if (alias) {
    try {
      await deleteAlias(alias.mailcowId);
    } catch (e) {
      console.error(`${TAG} Failed to delete Mailcow alias for ${spaceSlug}:`, e);
    }
    await deleteSpaceEmailAlias(spaceSlug);
  }
  console.log(`${TAG} Deprovisioned alias for ${spaceSlug}`);
}

/** Generate a random password for agent mailboxes */
function generateAgentPassword(): string {
  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%';
  let pw = '';
  const bytes = new Uint8Array(32);
  crypto.getRandomValues(bytes);
  for (const b of bytes) pw += chars[b % chars.length];
  return pw;
}

/**
 * Provision a real Mailcow mailbox for {space}-agent@rspace.online.
 * Generates a password and stores credentials in the DB.
 * Idempotent — skips if mailbox already exists.
 */
export async function provisionAgentMailbox(spaceSlug: string): Promise<void> {
  if (!isMailcowConfigured()) {
    console.warn(`${TAG} Mailcow not configured, skipping agent mailbox for ${spaceSlug}`);
    return;
  }

  const existing = await getAgentMailbox(spaceSlug);
  if (existing) {
    console.log(`${TAG} Agent mailbox already exists for ${spaceSlug}`);
    return;
  }

  const localPart = `${spaceSlug}-agent`;
  const email = `${localPart}@rspace.online`;

  // Check if Mailcow already has this mailbox (e.g. manually created)
  const alreadyExists = await mailboxExists(email);
  if (alreadyExists) {
    // We don't know the password — store a placeholder, admin must update
    console.warn(`${TAG} Mailbox ${email} exists in Mailcow but has no stored password — agent IMAP sync will fail until password is set`);
    await setAgentMailbox(spaceSlug, email, '');
    return;
  }

  const password = generateAgentPassword();

  await createMailbox(localPart, password);
  await setAgentMailbox(spaceSlug, email, password);
  console.log(`${TAG} Provisioned agent mailbox ${email}`);
}

/**
 * Remove the Mailcow agent mailbox and DB records for a space.
 */
export async function deprovisionAgentMailbox(spaceSlug: string): Promise<void> {
  if (!isMailcowConfigured()) return;

  const existing = await getAgentMailbox(spaceSlug);
  if (existing) {
    try {
      await deleteMailbox(existing.email);
    } catch (e) {
      console.error(`${TAG} Failed to delete agent mailbox for ${spaceSlug}:`, e);
    }
    await deleteAgentMailbox(spaceSlug);
  }
  console.log(`${TAG} Deprovisioned agent mailbox for ${spaceSlug}`);
}
