/**
 * dist/agent/council.js — the AI Council (مجلس المؤسس).
 *
 * Convenes 6–8 role-diverse council members to deliberate a question, debate to
 * a consensus, surface bottlenecks, then have the Founder (Moasis) give an
 * unbiased verdict. Maintains live in-memory meeting state so the Office tab can
 * visualise members walking in, sitting, and speaking. The final report is
 * returned to the caller (the brain) and posted back across all channels.
 *
 * Lightweight by design: each member is one bounded LLM call via the same
 * resilient failover path the chat brain uses (honours the per-tenant model).
 */
import { randomUUID } from 'node:crypto';
import { createChildLogger } from '../utils/logger.js';
import { callWithFailover } from '../models/failover.js';

const log = createChildLogger('agent:council');

// Live meetings keyed by tenantId (one active meeting per tenant at a time —
// the office shows the latest). Read by /api/council/active.
const activeMeetings = new Map();

export function getActiveMeeting(tenantId) {
  return activeMeetings.get(tenantId) || null;
}

// The council roster. Each has a distinct lens so the debate is genuinely
// multi-perspective rather than seven echoes.
const ROSTER = [
  { key: 'strategist', ar: 'الاستراتيجي', emoji: '🧭', lens: 'الصورة الكبيرة والأثر بعيد المدى والمواءمة مع أهداف الشركة' },
  { key: 'skeptic',    ar: 'محلّل المخاطر', emoji: '🛡️', lens: 'ما الذي قد يفشل؟ المخاطر، الافتراضات الهشّة، الأسوأ المحتمل' },
  { key: 'finance',    ar: 'المالي', emoji: '📊', lens: 'التكلفة، العائد، التدفق النقدي، هل يستحق الاستثمار؟' },
  { key: 'marketing',  ar: 'التسويق', emoji: '📣', lens: 'النمو، التموضع، كيف يُقدَّم للسوق، الميزة التنافسية' },
  { key: 'customer',   ar: 'صوت العميل', emoji: '🙋', lens: 'أثر القرار على العميل الفعلي ورضاه واحتياجه الحقيقي' },
  { key: 'operations', ar: 'العمليات', emoji: '⚙️', lens: 'قابلية التنفيذ، الموارد، الجدول الزمني، أين تختنق العملية' },
  { key: 'innovator',  ar: 'المبتكر', emoji: '💡', lens: 'الزاوية الإبداعية غير المتوقعة، حلّ يكسر القالب' },
];

async function ask(tenantId, system, user, maxTokens = 420) {
  const r = await callWithFailover('flash',
    [{ role: 'system', content: system }, { role: 'user', content: user }],
    { temperature: 0.5, maxTokens }, tenantId);
  return (r && r.content ? String(r.content) : '').trim();
}

/**
 * Run a full council meeting. Returns a structured report:
 *   { id, question, positions[], consensus, bottlenecks, agreement, founderVerdict, durationMs }
 * Updates activeMeetings(tenantId) live through every phase for the office.
 */
export async function conveneCouncil(tenantId, customerId, question, opts = {}) {
  const id = randomUUID();
  const startedAt = Date.now();
  const wanted = Math.max(6, Math.min(opts.memberCount || 7, ROSTER.length));
  const roster = ROSTER.slice(0, wanted);

  const meeting = {
    id, tenantId, customerId: customerId || null, question: String(question || '').slice(0, 600),
    phase: 'arriving', startedAt, concludedAt: null,
    members: roster.map((m, i) => ({
      id: `${id}-${m.key}`, key: m.key, role: m.ar, emoji: m.emoji,
      seat: i, status: 'walking', bubble: 'يدخل غرفة الاجتماع…',
    })),
    transcript: [], report: null,
  };
  activeMeetings.set(tenantId, meeting);
  const setBubble = (key, status, bubble) => {
    const mm = meeting.members.find((x) => x.key === key);
    if (mm) { mm.status = status; if (bubble != null) mm.bubble = bubble; }
  };

  try {
    // Phase 1 — members take their seats, then each gives an opening position.
    meeting.phase = 'gathering';
    meeting.members.forEach((m) => { m.status = 'seated'; m.bubble = 'يجهّز رأيه…'; });

    const positions = await Promise.all(roster.map(async (m) => {
      setBubble(m.key, 'thinking', 'يفكّر…');
      const sys = `أنت "${m.ar}" في مجلس شركة سعودية صغيرة. مهمتك تقييم السؤال من زاويتك حصراً: ${m.lens}. أجب بالعربية، بإيجاز (٢-٤ جُمل)، برأي واضح وقابل للتنفيذ، لا تهرب من اتخاذ موقف.`;
      const txt = await ask(tenantId, sys, `السؤال المطروح على المجلس:\n${question}`).catch(() => '');
      const position = txt || 'لا رأي واضح.';
      setBubble(m.key, 'speaking', position.slice(0, 60));
      meeting.transcript.push({ role: m.ar, emoji: m.emoji, text: position, ts: Date.now() });
      return { role: m.ar, key: m.key, position };
    }));

    // Phase 2 — debate & synthesis into a consensus + the real disagreements.
    meeting.phase = 'debating';
    meeting.members.forEach((m) => { m.status = 'seated'; m.bubble = 'يناقش…'; });
    const positionsBlock = positions.map((p) => `• ${p.role}: ${p.position}`).join('\n');
    const synthSys = `أنت أمين سرّ مجلس الشركة. لديك آراء أعضاء المجلس. ادمجها في مخرجٍ صادق: أين اتفقوا، أين اختلفوا (المعضلات/الاختناقات الحقيقية)، والتوصية المتّفق عليها. لا تُجامل. أعد JSON فقط بهذا الشكل:
{"consensus":"الخلاصة المتفق عليها","bottlenecks":["معضلة/اختناق",...],"agreement":"التوصية النهائية القابلة للتنفيذ"}`;
    const synthRaw = await ask(tenantId, synthSys, `السؤال:\n${question}\n\nالآراء:\n${positionsBlock}`, 700).catch(() => '');
    // Robust parse: parseJson handles fenced ```json, but if the model wrapped or
    // appended prose the fallback must NOT dump a raw "```json {...}" blob into the
    // report (that's the garbled-consensus bug). Strip all fences and retry, then
    // fall back to clean prose.
    const synthCleaned = String(synthRaw).replace(/```(?:json)?/gi, '').replace(/```/g, '').trim();
    const synth = parseJson(synthRaw) || parseJson(synthCleaned) || {
      consensus: synthCleaned.slice(0, 400) || 'لم يكتمل التوافق.',
      bottlenecks: [], agreement: 'يُنصح بمراجعة بشرية.',
    };

    // Phase 3 — the Founder (Moasis) evaluates the council's work, unbiased.
    meeting.phase = 'founder_review';
    meeting.members.forEach((m) => { m.status = 'seated'; m.bubble = 'ينتظر حكم المؤسس…'; });
    const founderSys = `أنت "المؤسس" — العقل المدبّر والمدير التنفيذي للشركة. راجع نتيجة مجلسك بحياديةٍ تامّة: هل توافق؟ ما الذي أغفله المجلس؟ ما قرارك النهائي؟ كن مباشراً وذكياً وعملياً. ٣-٥ جُمل بالعربية.`;
    const founderVerdict = await ask(tenantId, founderSys,
      `السؤال:\n${question}\n\nتوافق المجلس: ${synth.consensus}\nالاختناقات: ${(synth.bottlenecks||[]).join('، ') || 'لا يوجد'}\nالتوصية: ${synth.agreement}`,
      500).catch(() => '') || 'أوافق على توصية المجلس مع متابعة التنفيذ.';

    meeting.phase = 'concluded';
    meeting.concludedAt = Date.now();
    meeting.members.forEach((m) => { m.status = 'seated'; m.bubble = 'انتهى الاجتماع ✅'; });

    const report = {
      id, question: meeting.question, positions,
      consensus: synth.consensus, bottlenecks: synth.bottlenecks || [],
      agreement: synth.agreement, founderVerdict,
      memberCount: roster.length, durationMs: Date.now() - startedAt,
    };
    meeting.report = report;
    log.info({ tenantId, id, members: roster.length, ms: report.durationMs }, 'council meeting concluded');

    // Keep the concluded meeting visible for 90s so the office can show the result.
    setTimeout(() => { if (activeMeetings.get(tenantId)?.id === id) activeMeetings.delete(tenantId); }, 90_000);
    return report;
  } catch (err) {
    meeting.phase = 'failed';
    log.error({ tenantId, id, error: err.message }, 'council meeting failed');
    setTimeout(() => { if (activeMeetings.get(tenantId)?.id === id) activeMeetings.delete(tenantId); }, 30_000);
    throw err;
  }
}

/** Render a council report as a chat-ready Arabic message. */
export function formatCouncilReport(r) {
  const lines = [];
  lines.push(`🏛️ **تقرير مجلس المؤسس** — ${r.memberCount} أعضاء`);
  lines.push(`\n**السؤال:** ${r.question}`);
  lines.push(`\n**ما حدث في الاجتماع:**`);
  for (const p of r.positions) lines.push(`• *${p.role}:* ${p.position}`);
  lines.push(`\n**ما اتّفقوا عليه:** ${r.consensus}`);
  if (r.bottlenecks && r.bottlenecks.length) {
    lines.push(`\n**الاختناقات / نقاط الخلاف:**`);
    for (const b of r.bottlenecks) lines.push(`⚠️ ${b}`);
  }
  lines.push(`\n**التوصية النهائية:** ${r.agreement}`);
  lines.push(`\n🧠 **رأي المؤسس (محايد):** ${r.founderVerdict}`);
  return lines.join('\n');
}

function parseJson(s) {
  if (!s) return null;
  let c = String(s).trim();
  if (c.startsWith('```')) c = c.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '').trim();
  try { return JSON.parse(c); } catch { /* try slice */ }
  const a = c.indexOf('{'), b = c.lastIndexOf('}');
  if (a !== -1 && b > a) { try { return JSON.parse(c.slice(a, b + 1)); } catch { /* nope */ } }
  return null;
}
