import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'bun:test';
import { Database } from 'bun:sqlite';
import { initializeDatabase, ensureUserExists } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service';
import { setDb, resetDb } from '../../../src/db/locator';

let memDb: Database;

beforeAll(() => {
  memDb = new Database(':memory:');
  initializeDatabase(memDb);
  setDb(memDb);
});

afterAll(() => {
  try { resetDb(); memDb.close(); } catch {}
});

beforeEach(() => {
  // Reafirmar el locator y limpiar tablas relevantes entre tests
  setDb(memDb);
  try { memDb.exec('DELETE FROM task_assignments'); } catch {}
  try { memDb.exec('DELETE FROM tasks'); } catch {}
  try { memDb.exec('DELETE FROM users'); } catch {}
  try { memDb.exec('DELETE FROM groups'); } catch {}
});

describe('TaskService.listGroupUnassigned / countGroupUnassigned', () => {
  it('devuelve solo tareas sin dueño del grupo, en orden por fecha (NULL al final) y respeta el límite', () => {
    // Sembrar grupos para FK
    memDb.prepare(`INSERT OR IGNORE INTO groups (id, community_id, name, active) VALUES (?, ?, ?, 1)`)
      .run('g1@g.us', 'c1', 'G1');
    memDb.prepare(`INSERT OR IGNORE INTO groups (id, community_id, name, active) VALUES (?, ?, ?, 1)`)
      .run('g2@g.us', 'c1', 'G2');

    // En g1: 3 sin dueño con distintas fechas, 1 asignada (excluir), 1 sin dueño completada (excluir)
    const tEarly = TaskService.createTask({
      description: 'Sin dueño pronto',
      due_date: '2025-08-01',
      group_id: 'g1@g.us',
      created_by: '1000000000',
    });
    const tLater = TaskService.createTask({
      description: 'Sin dueño tarde',
      due_date: '2025-10-01',
      group_id: 'g1@g.us',
      created_by: '1000000000',
    });
    const tNull = TaskService.createTask({
      description: 'Sin dueño sin fecha',
      due_date: null,
      group_id: 'g1@g.us',
      created_by: '1000000000',
    });
    TaskService.createTask({
      description: 'Asignada (no debe salir)',
      due_date: '2025-09-01',
      group_id: 'g1@g.us',
      created_by: '1000000000',
    }, [{ user_id: '2000000000', assigned_by: '1000000000' }]);

    // Completar una sin dueño para que no aparezca
    memDb.prepare(`UPDATE tasks SET completed = 1, completed_at = CURRENT_TIMESTAMP WHERE id = ?`).run(tEarly);

    // En g2: sin dueño (no deben aparecer al listar g1)
    for (let i = 1; i <= 3; i++) {
      TaskService.createTask({
        description: `G2 ${i}`,
        due_date: '2025-12-31',
        group_id: 'g2@g.us',
        created_by: '1000000000',
      });
    }

    const list = TaskService.listGroupUnassigned('g1@g.us', 10);
    expect(Array.isArray(list)).toBe(true);
    // Deben quedar 2: 'Sin dueño tarde' (fecha) y 'Sin dueño sin fecha' (NULL al final)
    expect(list.length).toBe(2);
    expect(list[0].description).toBe('Sin dueño tarde');
    expect(list[1].description).toBe('Sin dueño sin fecha');
    expect(list[0].assignees).toEqual([]);
    expect(list[1].assignees).toEqual([]);
    expect(list[0].group_id).toBe('g1@g.us');
    expect(list[1].group_id).toBe('g1@g.us');

    // Límite
    // Crear más en g1 para forzar límite 1
    TaskService.createTask({
      description: 'Otra sin dueño',
      due_date: '2025-11-01',
      group_id: 'g1@g.us',
      created_by: '1000000000',
    });
    const limited = TaskService.listGroupUnassigned('g1@g.us', 1);
    expect(limited.length).toBe(1);
  });

  it('countGroupUnassigned cuenta solo sin dueño pendientes (excluye completadas y asignadas)', () => {
    // Limpiar por si acaso
    memDb.exec('DELETE FROM tasks');
    memDb.exec('DELETE FROM task_assignments');

    // Sembrar grupo
    memDb.prepare(`INSERT OR IGNORE INTO groups (id, community_id, name, active) VALUES (?, ?, ?, 1)`)
      .run('g1@g.us', 'c1', 'G1');

    const a = TaskService.createTask({
      description: 'Sin dueño 1',
      due_date: '2025-09-10',
      group_id: 'g1@g.us',
      created_by: '3333333333',
    });
    const b = TaskService.createTask({
      description: 'Sin dueño 2',
      due_date: null,
      group_id: 'g1@g.us',
      created_by: '3333333333',
    });
    // Asignada (no cuenta como sin dueño)
    TaskService.createTask({
      description: 'Asignada',
      due_date: '2025-09-12',
      group_id: 'g1@g.us',
      created_by: '3333333333',
    }, [{ user_id: '4444444444', assigned_by: '3333333333' }]);

    // Completar una sin dueño
    memDb.prepare(`UPDATE tasks SET completed = 1, completed_at = CURRENT_TIMESTAMP WHERE id = ?`).run(a);

    const cnt = TaskService.countGroupUnassigned('g1@g.us');
    expect(cnt).toBe(1); // sólo queda b
  });
});

describe('TaskService.createTask', () => {
  it('crea una tarea mínima con created_by y sin due_date ni group_id', () => {
    const creatorRaw = '555111222@s.whatsapp.net';
    const createdBy = ensureUserExists(creatorRaw, memDb);
    expect(createdBy).toBeTruthy();

    const id = TaskService.createTask(
      {
        description: 'Comprar agua',
        created_by: createdBy!,
        due_date: null,
        group_id: null,
      },
      [] // sin asignaciones
    );

    expect(typeof id).toBe('number');

    const task = memDb
      .prepare(
        `SELECT id, description, due_date, group_id, created_by, completed 
         FROM tasks WHERE id = ?`
      )
      .get(id) as any;

    expect(task).toBeTruthy();
    expect(task.description).toBe('Comprar agua');
    expect(task.due_date).toBeNull();
    expect(task.group_id).toBeNull();
    expect(task.created_by).toBe(createdBy);
    expect(task.completed).toBe(0); // BOOLEAN en SQLite como 0/1
  });

  it('guarda due_date como cadena YYYY-MM-DD y group_id como JID completo', () => {
    const creatorRaw = '555333444@s.whatsapp.net';
    const createdBy = ensureUserExists(creatorRaw, memDb)!;

    const due = '2025-09-10';
    const groupId = '12345@g.us';

    // Sembrar el grupo para satisfacer la FK de tasks.group_id -> groups.id
    memDb
      .prepare(`INSERT INTO groups (id, community_id, name, active) VALUES (?, ?, ?, TRUE)`)
      .run(groupId, 'test-community', 'Grupo de prueba');

    const id = TaskService.createTask(
      {
        description: 'Pagar servicios',
        created_by: createdBy,
        due_date: due,
        group_id: groupId,
      },
      []
    );

    const row = memDb
      .prepare(
        `SELECT description, due_date, group_id, created_by 
         FROM tasks WHERE id = ?`
      )
      .get(id) as any;

    expect(row.due_date).toBe(due);
    expect(row.group_id).toBe(groupId);
    expect(row.created_by).toBe(createdBy);
  });

  it('inserta asignaciones y evita duplicados por usuario', () => {
    const creator = ensureUserExists('555000001@s.whatsapp.net', memDb)!;
    const assigneeA = ensureUserExists('555000002@s.whatsapp.net', memDb)!;
    const assigneeB = ensureUserExists('555000003@s.whatsapp.net', memDb)!;

    const id = TaskService.createTask(
      {
        description: 'Organizar reunión',
        created_by: creator,
        due_date: null,
        group_id: null,
      },
      [
        { user_id: assigneeA, assigned_by: creator },
        { user_id: assigneeA, assigned_by: creator }, // duplicado
        { user_id: assigneeB, assigned_by: creator },
      ]
    );

    const count = memDb
      .prepare(
        `SELECT COUNT(*) AS c FROM task_assignments WHERE task_id = ?`
      )
      .get(id) as any;

    expect(count.c).toBe(2);

    const users = memDb
      .prepare(
        `SELECT user_id FROM task_assignments WHERE task_id = ? ORDER BY user_id`
      )
      .all(id) as any[];

    expect(users.map(u => u.user_id)).toEqual([assigneeA, assigneeB].sort());
  });

  it('asegura usuarios inexistentes en asignaciones y no viola FK', () => {
    const creator = ensureUserExists('555010101@s.whatsapp.net', memDb)!;

    const id = TaskService.createTask(
      {
        description: 'Tarea con asignado nuevo',
        created_by: creator,
        due_date: null,
        group_id: null,
      },
      [
        // Usuario no existente: TaskService debe asegurar y crear
        { user_id: '555099999', assigned_by: creator },
      ]
    );

    const counts = memDb
      .prepare(
        `SELECT 
           (SELECT COUNT(*) FROM tasks) AS tasks_count,
           (SELECT COUNT(*) FROM task_assignments WHERE task_id = ?) AS assigns_count`
      )
      .get(id) as any;

    expect(counts.tasks_count).toBe(1);
    expect(counts.assigns_count).toBe(1);
  });

  it('lanza error si created_by es inválido (no normalizable) y no inserta la tarea', () => {
    const invalidCreator = 'invalid-id!';

    expect(() =>
      TaskService.createTask(
        {
          description: 'No debería insertarse',
          created_by: invalidCreator,
          due_date: null,
          group_id: null,
        },
        []
      )
    ).toThrow();

    const count = memDb
      .prepare(`SELECT COUNT(*) AS c FROM tasks`)
      .get() as any;

    expect(count.c).toBe(0);
  });
});

// ---------------------------------------------------------------------------
// Access control for completeTask
// ---------------------------------------------------------------------------

describe('TaskService.completeTask - access control', () => {
  let memDb: Database;
  const ADMIN = '1000000001';
  const USER_ASSIGNED = '2000000001';
  const USER_NOT_ASSIGNED = '2000000002';
  const USER_GROUP_MEMBER = '2000000003';
  const USER_GROUP_OUTSIDER = '2000000004';
  const GROUP_ID = 'testgroup@g.us';

  beforeAll(() => {
    memDb = new Database(':memory:');
    initializeDatabase(memDb);
    setDb(memDb);
  });

  afterAll(() => {
    try { resetDb(); memDb.close(); } catch {}
  });

  beforeEach(() => {
    process.env.NODE_ENV = 'test';
    setDb(memDb);
    try { memDb.exec('DELETE FROM task_assignments'); } catch {}
    try { memDb.exec('DELETE FROM tasks'); } catch {}
    try { memDb.exec('DELETE FROM group_members'); } catch {}
    try { memDb.exec('DELETE FROM users'); } catch {}
    try { memDb.exec('DELETE FROM groups'); } catch {}
    delete process.env.ADMIN_USERS;
  });

  function setupGroup(): void {
    memDb.prepare(`
      INSERT OR IGNORE INTO groups (id, community_id, name, active)
      VALUES (?, 'comm-1', 'Test Group', 1)
    `).run(GROUP_ID);
  }

  function createTask(opts: { description?: string; groupId?: string | null; createdBy: string; assignees?: string[] }): number {
    const creator = ensureUserExists(opts.createdBy, memDb)!;
    const taskId = TaskService.createTask(
      {
        description: opts.description || 'Test task',
        due_date: null,
        group_id: opts.groupId ?? null,
        created_by: creator,
      },
      (opts.assignees || []).map(uid => ({ user_id: ensureUserExists(uid, memDb)!, assigned_by: creator }))
    );
    return taskId;
  }

  // --- Personal tasks (no group) ---

  it('permite completar tarea personal si el usuario está asignado', () => {
    const taskId = createTask({ createdBy: USER_ASSIGNED, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, USER_ASSIGNED);
    expect(res.status).toBe('updated');
  });

  it('rechaza tarea personal si el usuario no está asignado', () => {
    const taskId = createTask({ createdBy: USER_ASSIGNED, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, USER_NOT_ASSIGNED);
    expect(res.status).toBe('forbidden');
  });

  it('rechaza tarea personal sin asignatarios para no-asignados', () => {
    const taskId = createTask({ createdBy: USER_ASSIGNED });
    const res = TaskService.completeTask(taskId, USER_NOT_ASSIGNED);
    expect(res.status).toBe('forbidden');
  });

  // --- Group tasks ---

  it('permite completar tarea de grupo si el usuario está asignado', () => {
    setupGroup();
    const taskId = createTask({ createdBy: USER_ASSIGNED, groupId: GROUP_ID, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, USER_ASSIGNED);
    expect(res.status).toBe('updated');
  });

  it('permite completar tarea de grupo si el usuario es miembro activo (aunque no esté asignado)', () => {
    setupGroup();
    ensureUserExists(USER_GROUP_MEMBER, memDb);
    memDb.prepare(`INSERT OR REPLACE INTO group_members (group_id, user_id, is_active) VALUES (?, ?, 1)`).run(GROUP_ID, USER_GROUP_MEMBER);
    const taskId = createTask({ createdBy: USER_ASSIGNED, groupId: GROUP_ID, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, USER_GROUP_MEMBER);
    expect(res.status).toBe('updated');
  });

  it('rechaza tarea de grupo si el usuario no está asignado ni es miembro', () => {
    setupGroup();
    const taskId = createTask({ createdBy: USER_ASSIGNED, groupId: GROUP_ID, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, USER_GROUP_OUTSIDER);
    expect(res.status).toBe('forbidden');
  });

  it('rechaza tarea de grupo si el usuario es miembro inactivo y no está asignado', () => {
    setupGroup();
    ensureUserExists(USER_GROUP_MEMBER, memDb);
    memDb.prepare(`INSERT OR REPLACE INTO group_members (group_id, user_id, is_active) VALUES (?, ?, 0)`).run(GROUP_ID, USER_GROUP_MEMBER);
    const taskId = createTask({ createdBy: USER_ASSIGNED, groupId: GROUP_ID, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, USER_GROUP_MEMBER);
    expect(res.status).toBe('forbidden');
  });

  // --- Admin override ---

  it('permite a un admin completar tarea personal sin estar asignado', () => {
    process.env.ADMIN_USERS = ADMIN;
    const taskId = createTask({ createdBy: USER_ASSIGNED, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, ADMIN);
    expect(res.status).toBe('updated');
  });

  it('permite a un admin completar tarea de grupo sin estar asignado ni ser miembro', () => {
    process.env.ADMIN_USERS = ADMIN;
    setupGroup();
    const taskId = createTask({ createdBy: USER_ASSIGNED, groupId: GROUP_ID, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, ADMIN);
    expect(res.status).toBe('updated');
  });

  it('permite a un admin con ID raw (con sufijo @s.whatsapp.net) completar', () => {
    process.env.ADMIN_USERS = ADMIN;
    const taskId = createTask({ createdBy: USER_ASSIGNED, assignees: [USER_ASSIGNED] });
    const res = TaskService.completeTask(taskId, ADMIN + '@s.whatsapp.net');
    expect(res.status).toBe('updated');
  });

  // --- Already completed / not found (unchanged behavior) ---

  it('devuelve already si la tarea ya estaba completada', () => {
    const taskId = createTask({ createdBy: USER_ASSIGNED, assignees: [USER_ASSIGNED] });
    TaskService.completeTask(taskId, USER_ASSIGNED);
    const res = TaskService.completeTask(taskId, USER_ASSIGNED);
    expect(res.status).toBe('already');
  });

  it('devuelve not_found si la tarea no existe', () => {
    const res = TaskService.completeTask(999999, USER_ASSIGNED);
    expect(res.status).toBe('not_found');
  });
});
