// --- WALL-E Listening Skill: Speaker Diarization ---
// Matches speaker labels from transcription to known profiles,
// handles enrollment, and applies confirmed label mappings.

const { randomUUID: uuidv4 } = require('node:crypto');
const brain = require('../brain');

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function _validateId(id) {
  if (!id || !UUID_RE.test(id)) throw new Error('Invalid recording ID');
}

/**
 * Get unique speaker labels from a recording's latest transcript.
 * @param {string} recordingId
 * @returns {Array<{label: string, segmentCount: number, totalMs: number}>}
 */
function getRecordingSpeakers(recordingId) {
  _validateId(recordingId);
  const db = brain.getDb();
  const transcript = db.prepare(
    'SELECT id FROM transcripts WHERE recording_id = ? ORDER BY created_at DESC LIMIT 1'
  ).get(recordingId);
  if (!transcript) return [];

  const rows = db.prepare(`
    SELECT speaker_label AS label, COUNT(*) AS segmentCount,
           SUM(end_ms - start_ms) AS totalMs
    FROM transcript_segments
    WHERE transcript_id = ? AND speaker_label IS NOT NULL
    GROUP BY speaker_label
    ORDER BY MIN(seq_order)
  `).all(transcript.id);

  return rows;
}

/**
 * Suggest speaker profile matches for a recording's speakers.
 * Uses calendar attendees (if linked) and past profile frequency.
 * @param {string} recordingId
 * @returns {Array<{label: string, suggestions: Array<{profileId: string, name: string, confidence: number}>}>}
 */
function suggestMatches(recordingId) {
  _validateId(recordingId);
  const db = brain.getDb();
  const speakers = getRecordingSpeakers(recordingId);
  if (speakers.length === 0) return [];

  // Get existing profiles ordered by frequency
  const profiles = db.prepare(
    'SELECT id, name, person_id, times_identified FROM speaker_profiles ORDER BY times_identified DESC'
  ).all();

  // Get calendar attendees if recording is linked to a meeting
  const rec = db.prepare('SELECT calendar_event_id FROM recordings WHERE id = ?').get(recordingId);
  let attendeeNames = [];
  if (rec && rec.calendar_event_id) {
    const meeting = db.prepare('SELECT attendees FROM calendar_meetings WHERE id = ?').get(rec.calendar_event_id);
    if (meeting && meeting.attendees) {
      try {
        const attendees = JSON.parse(meeting.attendees);
        attendeeNames = attendees.map(a => a.name || a.email || '').filter(Boolean);
      } catch {}
    }
  }

  return speakers.map((sp, idx) => {
    const suggestions = [];

    // If we have attendees and roughly match speaker count, suggest by position
    if (attendeeNames.length > 0 && idx < attendeeNames.length) {
      const name = attendeeNames[idx];
      const matchingProfile = profiles.find(p => p.name.toLowerCase() === name.toLowerCase());
      suggestions.push({
        profileId: matchingProfile ? matchingProfile.id : null,
        name: name,
        confidence: 0.3
      });
    }

    // Add frequent profiles as lower-confidence suggestions
    for (const p of profiles.slice(0, 3)) {
      if (!suggestions.find(s => s.profileId === p.id)) {
        suggestions.push({
          profileId: p.id,
          name: p.name,
          confidence: Math.min(0.5, p.times_identified / 20)
        });
      }
    }

    return { label: sp.label, suggestions };
  });
}

/**
 * Create or update a speaker profile.
 * @param {string} name
 * @param {string} recordingId - Recording the speaker was identified in
 * @returns {{id: string, person_id: string|null, isNew: boolean}}
 */
function enrollSpeaker(name, recordingId) {
  const db = brain.getDb();

  // Check if profile with this name already exists
  const existing = db.prepare(
    'SELECT * FROM speaker_profiles WHERE name = ? COLLATE NOCASE'
  ).get(name);

  if (existing) {
    // Update existing profile
    let sampleIds = [];
    try { sampleIds = JSON.parse(existing.sample_recording_ids || '[]'); } catch {}
    if (!sampleIds.includes(recordingId)) sampleIds.push(recordingId);

    db.prepare(`
      UPDATE speaker_profiles SET
        times_identified = times_identified + 1,
        last_identified_at = datetime('now'),
        sample_recording_ids = ?
      WHERE id = ?
    `).run(JSON.stringify(sampleIds), existing.id);

    return { id: existing.id, person_id: existing.person_id, isNew: false };
  }

  // Create new profile
  const id = uuidv4();
  // Try to link to people table
  const person = db.prepare('SELECT id FROM people WHERE name = ? COLLATE NOCASE').get(name);
  const personId = person ? person.id : null;

  db.prepare(`
    INSERT INTO speaker_profiles (id, person_id, name, sample_recording_ids, times_identified, last_identified_at)
    VALUES (?, ?, ?, ?, 1, datetime('now'))
  `).run(id, personId, name, JSON.stringify([recordingId]));

  return { id, person_id: personId, isNew: true };
}

/**
 * Apply confirmed speaker label mappings to a recording's transcript.
 * Updates segment labels and enrolls/updates speaker profiles.
 * @param {string} recordingId
 * @param {Object<string, {profileId?: string, name: string}>} labelMap
 * @returns {{updated: number, profilesCreated: number, profilesUpdated: number}}
 */
function applyLabels(recordingId, labelMap) {
  _validateId(recordingId);
  const db = brain.getDb();

  const transcript = db.prepare(
    'SELECT id FROM transcripts WHERE recording_id = ? ORDER BY created_at DESC LIMIT 1'
  ).get(recordingId);
  if (!transcript) throw new Error('No transcript found for recording');

  let updated = 0;
  let profilesCreated = 0;
  let profilesUpdated = 0;

  const updateSegment = db.prepare(
    'UPDATE transcript_segments SET speaker_id = ?, speaker_label = ? WHERE transcript_id = ? AND speaker_label = ?'
  );

  const applyAll = db.transaction(() => {
    for (const [originalLabel, mapping] of Object.entries(labelMap)) {
      if (!mapping.name) continue;

      // Enroll or update speaker profile
      const result = enrollSpeaker(mapping.name, recordingId);
      if (result.isNew) profilesCreated++;
      else profilesUpdated++;

      // Update segments: change label to real name and set speaker_id
      const changes = updateSegment.run(result.id, mapping.name, transcript.id, originalLabel);
      updated += changes.changes;
    }
  });

  applyAll();

  // Update recording status to 'done' if it was 'transcribing'
  db.prepare("UPDATE recordings SET status = 'done' WHERE id = ? AND status IN ('transcribing', 'stopped')").run(recordingId);

  return { updated, profilesCreated, profilesUpdated };
}

/**
 * List all speaker profiles.
 * @param {object} [opts] - {limit, offset}
 * @returns {Array<speaker_profile>}
 */
function listProfiles(opts) {
  opts = opts || {};
  const limit = opts.limit || 100;
  const offset = opts.offset || 0;
  return brain.getDb().prepare(
    'SELECT * FROM speaker_profiles ORDER BY times_identified DESC, name ASC LIMIT ? OFFSET ?'
  ).all(limit, offset);
}

module.exports = {
  getRecordingSpeakers,
  suggestMatches,
  enrollSpeaker,
  applyLabels,
  listProfiles
};
