import crypto from 'crypto';
import xml2js from 'xml2js';
import { log, fmtDuration, fmtStatus, fmtMethod, fmtReturncode, sanitizeBBBParams } from './logger.js';
import { t } from './emaili18n.js';
import { isTranscriptionEnabled } from './transcription.js';

const BBB_URL = process.env.BBB_URL || 'https://your-bbb-server.com/bigbluebutton/api/';
const BBB_SECRET = process.env.BBB_SECRET || '';

if (!BBB_SECRET) {
  log.bbb.warn('WARNING: BBB_SECRET is not set. BBB API calls will use an empty secret.');
}

// HTML-escape for safe embedding in BBB welcome messages
function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function getChecksum(apiCall, params) {
  const queryString = new URLSearchParams(params).toString();
  const raw = apiCall + queryString + BBB_SECRET;
  return crypto.createHash('sha256').update(raw).digest('hex');
}

function buildUrl(apiCall, params = {}) {
  const checksum = getChecksum(apiCall, params);
  const queryString = new URLSearchParams({ ...params, checksum }).toString();
  return `${BBB_URL}${apiCall}?${queryString}`;
}

async function apiCall(apiCallName, params = {}, xmlBody = null) {
  const url = buildUrl(apiCallName, params);
  const method = xmlBody ? 'POST' : 'GET';

  const start = Date.now();
  try {
    const fetchOptions = xmlBody
      ? { method: 'POST', headers: { 'Content-Type': 'application/xml' }, body: xmlBody }
      : {};
    const response = await fetch(url, fetchOptions);
    const duration = Date.now() - start;
    const xml = await response.text();
    const result = await xml2js.parseStringPromise(xml, {
      explicitArray: false,
      trim: true,
    });

    const returncode = result?.response?.returncode || '-';
    const paramStr = sanitizeBBBParams(params);

    // Greenlight-style: method action → status returncode (duration) params
    log.bbb.info(
      `${fmtMethod(method)} ${apiCallName} → ${fmtStatus(response.status)} ${fmtReturncode(returncode)} (${fmtDuration(duration)})  ${paramStr}`
    );

    return result.response;
  } catch (error) {
    const duration = Date.now() - start;
    log.bbb.error(
      `${fmtMethod(method)} ${apiCallName} ✗ FAILED (${fmtDuration(duration)})  ${error.message}`
    );
    throw error;
  }
}

// Generate deterministic passwords from room UID
function getRoomPasswords(uid) {
  const modPw = crypto.createHash('sha256').update(uid + '_mod_' + BBB_SECRET).digest('hex').substring(0, 16);
  const attPw = crypto.createHash('sha256').update(uid + '_att_' + BBB_SECRET).digest('hex').substring(0, 16);
  return { moderatorPW: modPw, attendeePW: attPw };
}

export async function createMeeting(room, logoutURL, loginURL = null, presentationUrl = null, analyticsCallbackURL = null, lang = 'en') {
  const { moderatorPW, attendeePW } = getRoomPasswords(room.uid);

  // Build welcome message with guest invite link
  // HTML-escape user-controlled content to prevent stored XSS via BBB
  let welcome = room.welcome_message ? escapeHtml(room.welcome_message) : escapeHtml(t(lang, 'bbb.defaultWelcome'));
  if (logoutURL) {
    const guestLink = `${logoutURL}/join/${room.uid}`;
    welcome += `<br><br>${escapeHtml(t(lang, 'bbb.inviteHint'))}<br><a href="${escapeHtml(guestLink)}">${escapeHtml(guestLink)}</a>`;
    // Access code is intentionally NOT shown in the welcome message to prevent
    // leaking it to all meeting participants.
  }

  const params = {
    meetingID: room.uid,
    name: room.name.length >= 2 ? room.name : room.name.padEnd(2, ' '),
    attendeePW,
    moderatorPW,
    welcome,
    record: room.record_meeting ? 'true' : 'false',
    autoStartRecording: 'false',
    allowStartStopRecording: 'true',
    muteOnStart: room.mute_on_join ? 'true' : 'false',
    'meta_bbb-origin': 'Redlight',
    'meta_bbb-origin-server-name': 'Redlight',
  };
  if (logoutURL) {
    params.logoutURL = logoutURL;
  }
  if (loginURL) {
    params.loginURL = loginURL;
  }
  if (room.max_participants > 0) {
    params.maxParticipants = room.max_participants.toString();
  }
  if (room.access_code) {
    params.lockSettingsLockOnJoin = 'true';
  }
  if (analyticsCallbackURL) {
    params['meta_analytics-callback-url'] = analyticsCallbackURL;
  }
  if (room.recording_transcript && isTranscriptionEnabled()) {
    // BBB posts a signed recording-ready callback there once recording formats
    // are processed; used to trigger the AI summary/transcript integration.
    if (logoutURL) {
      params['meta_bbb-recording-ready-url'] = `${logoutURL}/api/recordings/recording-ready`;
    }
  } else {
    // Transcription/summary is off for this room — tell BBB to skip processing
    // and publishing those recording formats entirely (BBB PR #25277).
    params['meta_bbb-disable-recording-formats'] = 'transcript,summary';
  }

  // Build optional presentation XML body - escape URL to prevent XML injection
  let xmlBody = null;
  if (presentationUrl) {
    const safeUrl = presentationUrl
      .replace(/&/g, '&amp;')
      .replace(/"/g, '&quot;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;');
    xmlBody = `<modules><module name="presentation"><document url="${safeUrl}" /></module></modules>`;
  }

  return apiCall('create', params, xmlBody);
}

export async function joinMeeting(uid, name, isModerator = false, avatarURL = null, darkMode = false) {
  const { moderatorPW, attendeePW } = getRoomPasswords(uid);
  const params = {
    meetingID: uid,
    fullName: name,
    password: isModerator ? moderatorPW : attendeePW,
    redirect: 'true',
  };
  if (avatarURL) {
    params.avatarURL = avatarURL;
  }
  if (darkMode) {
    // Per-user userdata override: start the client in dark theme for users who
    // have a dark theme active in Redlight. Read by BBB as bbb_prefer_dark_theme.
    params['userdata-bbb_prefer_dark_theme'] = 'true';
  }
  return buildUrl('join', params);
}

export async function endMeeting(uid) {
  const { moderatorPW } = getRoomPasswords(uid);
  return apiCall('end', { meetingID: uid, password: moderatorPW });
}

export async function getMeetingInfo(uid) {
  return apiCall('getMeetingInfo', { meetingID: uid });
}

export async function isMeetingRunning(uid) {
  const result = await apiCall('isMeetingRunning', { meetingID: uid });
  return result.running === 'true';
}

export async function getMeetings() {
  return apiCall('getMeetings', {});
}

export async function getRecordings(meetingID) {
  const params = meetingID ? { meetingID } : {};
  const result = await apiCall('getRecordings', params);
  if (result.returncode !== 'SUCCESS' || !result.recordings) {
    return [];
  }
  const recordings = result.recordings.recording;
  if (!recordings) return [];
  return Array.isArray(recordings) ? recordings : [recordings];
}

export async function getRecordingByRecordId(recordID) {
  const result = await apiCall('getRecordings', { recordID });
  if (result.returncode !== 'SUCCESS' || !result.recordings) {
    return null;
  }
  const recordings = result.recordings.recording;
  if (!recordings) return null;
  const arr = Array.isArray(recordings) ? recordings : [recordings];
  return arr[0] || null;
}

export async function deleteRecording(recordID) {
  return apiCall('deleteRecordings', { recordID });
}

export async function publishRecording(recordID, publish) {
  return apiCall('publishRecordings', { recordID, publish: publish ? 'true' : 'false' });
}

export function getAnalyticsToken(uid) {
  return crypto.createHmac('sha256', BBB_SECRET).update('analytics_' + uid).digest('hex');
}

export { getRoomPasswords };
