/**
 * Sender canonicalization regression tests.
 *
 * Verifies that when a message arrives in a group with only
 * participant (LID-based) and no participantAlt, the sender is
 * resolved through the alias table to the canonical real
 * WhatsApp number.
 *
 * This prevents "no la tenías asignada" errors when a user
 * tries to soltar a task assigned to their real number but
 * their sender is resolved as a LID-based ID.
 */
import { describe, test, expect } from 'bun:test';
import { WebhookServer } from '../../src/server';
import { TaskService } from '../../src/tasks/service';
import { IdentityService } from '../../src/services/identity';
import { GroupSyncService } from '../../src/services/group-sync';
import { SimulatedResponseQueue } from '../helpers/queue';
import { createTestRequest, registerServerTestLifecycle } from '../helpers/server-test-harness';
import { ensureUserExists } from '../../src/db';

const testDb = registerServerTestLifecycle();

// ── Helpers ────────────────────────────────────────────────────────────

/** Create a task with given assignments. Returns the display_code. */
function createTaskWithAssignees(opts: {
  description: string;
  groupId?: string;
  creator: string;
  assignees: string[];
  dueDate?: string;
}): { id: number; displayCode: number } {
  const creator = ensureUserExists(opts.creator, testDb)!;
  const assigneeIds = opts.assignees
    .map(uid => ensureUserExists(uid, testDb)!)
    .filter(Boolean);

  const taskId = TaskService.createTask(
    {
      description: opts.description,
      due_date: opts.dueDate ?? '2026-12-31',
      group_id: opts.groupId || null,
      created_by: creator,
    },
    assigneeIds.map(uid => ({ user_id: uid, assigned_by: creator })),
  );

  const row = testDb
    .prepare('SELECT display_code FROM tasks WHERE id = ?')
    .get(taskId) as { display_code: number } | undefined;
  const dc = row?.display_code ?? 1;

  return { id: taskId, displayCode: dc };
}

/** Code-pad a display_code for use in command messages. */
function code4(n: number): string {
  return String(n).padStart(4, '0');
}

/** Seed a LID → real-number alias in the identity system. */
function seedAlias(lidId: string, realNumber: string): void {
  IdentityService.upsertAlias(lidId, `${realNumber}@s.whatsapp.net`, 'test-seed');
}

// ── Tests ──────────────────────────────────────────────────────────────

describe('Sender canonicalization via alias table', () => {
  test('soltar succeeds when sender is LID-based and assignment uses real number', async () => {
    // 1) Set up alias: LID → real number
    seedAlias('userXYZ@lid', '666777888');

    // 2) Create a task assigned to the real number, with a group_id
    //    so soltar actually unassigns (single-assignee + no-group = forbidden)
    const { displayCode } = createTaskWithAssignees({
      description: 'Tarea de prueba',
      creator: '2222222222',
      assignees: ['666777888'],
      groupId: 'group-id@g.us',
    });

    // 3) User sends t soltar from a group, with ONLY participant (LID),
    //    NO participantAlt — simulating the fallback path that triggers
    //    the canonicalization code.
    const payload = {
      event: 'messages.upsert',
      instance: 'test-instance',
      data: {
        key: {
          remoteJid: 'group-id@g.us',
          participant: 'userXYZ@lid',
          // intentionally omit participantAlt
        },
        message: { conversation: `t soltar ${displayCode}` },
      },
    };

    const response = await WebhookServer.handleRequest(createTestRequest(payload));
    expect(response.status).toBe(200);

    const out = SimulatedResponseQueue.get();
    const msg = out.map(r => r.message).join('\n');

    // Without canonicalization, the sender would be 'userXYZ' (LID) and
    // soltar would fail with "no la tenías asignada" because the assignment
    // uses '666777888' (real number). With canonicalization, the sender is
    // resolved to '666777888' and soltar succeeds.
    expect(msg).toContain('queda sin responsable');
  });

  test('soltar via DM with real sender works end-to-end', async () => {
    // 1) Create alias and task
    seedAlias('userXYZ@lid', '666777888');
    const { displayCode } = createTaskWithAssignees({
      description: 'Tarea DM test',
      creator: '2222222222',
      assignees: ['666777888'],
    });

    // 2) Send from DM — remoteJid is the user's JID, no LID ambiguity
    const payload = {
      event: 'messages.upsert',
      instance: 'test-instance',
      data: {
        key: {
          remoteJid: '666777888@s.whatsapp.net',
        },
        message: { conversation: `t soltar ${displayCode}` },
      },
    };

    const response = await WebhookServer.handleRequest(createTestRequest(payload));
    expect(response.status).toBe(200);

    const out = SimulatedResponseQueue.get();
    const msg = out.map(r => r.message).join('\n');

    // The soltar should succeed — task is personal (single assignee, no group)
    // so it shows "No puedes soltar una tarea personal"
    // OR if created with a group_id, it would show the unassign success message
    expect(msg).toMatch(/No puedes soltar|Has soltado|queda sin responsable/);
  });

  test('canonicalization does not alter already-canonical sender', async () => {
    // When participantAlt (real number) is present, resolveSenderJid uses it
    // directly. The canonicalization resolves the alias table but finds no
    // mapping (real→real is the identity), so it's a no-op.
    const { displayCode } = createTaskWithAssignees({
      description: 'Already canonical',
      creator: '2222222222',
      assignees: ['666777888'],
      groupId: 'group-id@g.us',
    });

    const payload = {
      event: 'messages.upsert',
      instance: 'test-instance',
      data: {
        key: {
          remoteJid: 'group-id@g.us',
          participant: 'userXYZ@lid',
          participantAlt: '666777888@s.whatsapp.net',
        },
        message: { conversation: `t soltar ${displayCode}` },
      },
    };

    const response = await WebhookServer.handleRequest(createTestRequest(payload));
    expect(response.status).toBe(200);

    const out = SimulatedResponseQueue.get();
    const msg = out.map(r => r.message).join('\n');

    // Soltar succeeds because participantAlt provides the canonical real number.
    // The key thing: it didn't crash or produce a wrong-identity error.
    expect(msg).toContain('queda sin responsable');
  });

  test('soltar without alias mapping falls through to raw sender', async () => {
    // No alias seeded — sender stays as-is (whatever normalizeWhatsAppId produces).
    // This tests the non-regression path: the canonicalization gracefully
    // returns null and the raw sender is used unchanged.
    const { displayCode } = createTaskWithAssignees({
      description: 'No alias task',
      creator: '2222222222',
      assignees: ['666777888'],
      groupId: 'group-id@g.us',
    });

    // A different user (LID 'otherLID', no alias) tries to soltar.
    // Without alias mapping, sender stays as 'otherLID'.
    const payload = {
      event: 'messages.upsert',
      instance: 'test-instance',
      data: {
        key: {
          remoteJid: 'group-id@g.us',
          participant: 'otherLID@lid',
          // no participantAlt
        },
        message: { conversation: `t soltar ${displayCode}` },
      },
    };

    const response = await WebhookServer.handleRequest(createTestRequest(payload));
    expect(response.status).toBe(200);

    const out = SimulatedResponseQueue.get();
    const msg = out.map(r => r.message).join('\n');

    // "no la tenías asignada" because 'otherLID' != '666777888'.
    // This confirms the canonicalization gracefully handles missing aliases.
    expect(msg).toContain('no la tenías asignada');
  });

  test('t todas resolves unassigned tasks with canonicalized sender', async () => {
    // 1) Seed alias LID → real number
    seedAlias('userXYZ@lid', '666777888');

    // 2) Add user as active member of a group
    testDb.exec(`
      INSERT OR IGNORE INTO groups (id, name, active, community_id)
      VALUES ('another-group@g.us', 'Another Group', 1, '')
    `);
    ensureUserExists('666777888', testDb);
    testDb.exec(`
      INSERT OR IGNORE INTO group_members (group_id, user_id, is_active, is_admin)
      VALUES ('another-group@g.us', '666777888', 1, 0)
    `);

    // 3) Refresh the active groups cache
    (GroupSyncService as any).cacheActiveGroups();

    // 4) Create two unassigned tasks in that group
    TaskService.createTask(
      { description: 'Sin dueño A', due_date: '2026-12-31', group_id: 'another-group@g.us', created_by: '2222222222' },
      [],
    );
    TaskService.createTask(
      { description: 'Sin dueño B', due_date: '2026-12-31', group_id: 'another-group@g.us', created_by: '2222222222' },
      [],
    );

    // 5) Also create a task assigned to the user
    createTaskWithAssignees({
      description: 'Mi tarea',
      creator: '2222222222',
      assignees: ['666777888'],
      groupId: 'another-group@g.us',
    });

    // 6) Send t todas from DM with canonical (real) sender
    const payload = {
      event: 'messages.upsert',
      instance: 'test-instance',
      data: {
        key: {
          remoteJid: '666777888@s.whatsapp.net',
        },
        message: { conversation: 't todas' },
      },
    };

    const response = await WebhookServer.handleRequest(createTestRequest(payload));
    expect(response.status).toBe(200);

    const out = SimulatedResponseQueue.get();
    const msg = out.map(r => r.message).join('\n');

    // Should show "Tus tareas" section with the assigned task
    expect(msg).toContain('Tus tareas');
    expect(msg).toContain('Mi tarea');

    // Should show the "Sin responsable" section for the other group
    expect(msg).toContain('Sin responsable');
    expect(msg).toContain('Sin dueño A');
    expect(msg).toContain('Sin dueño B');
  });
});
