-- Cruce de Cuentas GLM
-- Equivalencias validadas para falsos positivos de "Banco sin BambooHR"
-- Ejecutar en el SQL Editor del Supabase empresarial.
-- Script idempotente: puede ejecutarse nuevamente sin borrar datos existentes.

begin;

create extension if not exists pgcrypto;

create or replace function public.cruce_cuentas_normalizar_nombre(p_value text)
returns text
language sql
immutable
strict
as $$
  select trim(
    regexp_replace(
      translate(
        lower(coalesce(p_value, '')),
        'áàäâãåéèëêíìïîóòöôõúùüûñçÁÀÄÂÃÅÉÈËÊÍÌÏÎÓÒÖÔÕÚÙÜÛÑÇ',
        'aaaaaaeeeeiiiiooooouuuuncAAAAAAEEEEIIIIOOOOOUUUUNC'
      ),
      '[^a-z0-9]+',
      ' ',
      'g'
    )
  );
$$;

create table if not exists public.cruce_cuentas_bamboo_aliases (
  id uuid primary key default gen_random_uuid(),
  pais text not null check (pais in ('GT', 'TT')),
  nombre_origen text not null,
  nombre_origen_normalizado text not null,
  cuenta_bancaria text not null default '',
  bamboo_employee_number text,
  bamboo_employee_id text,
  bamboo_nombre text not null,
  motivo text,
  comentario text,
  activo boolean not null default true,
  validado_por_email text not null,
  validado_por_nombre text,
  last_reported_at timestamptz not null default now(),
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

-- Compatibilidad si una versión anterior del script creó la tabla sin cuenta.
alter table public.cruce_cuentas_bamboo_aliases
  add column if not exists cuenta_bancaria text not null default '';

drop index if exists public.ux_cruce_cuentas_bamboo_aliases_pais_nombre;

create unique index if not exists ux_cruce_cuentas_bamboo_aliases_pais_nombre_cuenta
  on public.cruce_cuentas_bamboo_aliases (pais, nombre_origen_normalizado, cuenta_bancaria);

create index if not exists ix_cruce_cuentas_bamboo_aliases_employee_number
  on public.cruce_cuentas_bamboo_aliases (pais, bamboo_employee_number)
  where activo = true and bamboo_employee_number is not null;

create table if not exists public.cruce_cuentas_bamboo_correcciones (
  id uuid primary key default gen_random_uuid(),
  pais text not null check (pais in ('GT', 'TT')),
  nombre_origen_principal text not null,
  nombres_origen text[] not null default array[]::text[],
  cuenta_bancaria text,
  monto_banco numeric(18,2),
  moneda text,
  bamboo_employee_number text,
  bamboo_employee_id text,
  bamboo_nombre text not null,
  motivo text not null,
  comentario text,
  execution_id text,
  report_url text,
  period_label text,
  period_start date,
  period_end date,
  solicitado_por_email text not null,
  solicitado_por_nombre text,
  destinatarios_rrhh text[] not null default array[]::text[],
  alias_ids uuid[] not null default array[]::uuid[],
  estado text not null default 'pendiente_notificacion'
    check (estado in ('pendiente_notificacion', 'notificado_rrhh', 'corregido_bamboo', 'cancelado')),
  correo_enviado boolean not null default false,
  correo_enviado_at timestamptz,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

create index if not exists ix_cruce_cuentas_bamboo_correcciones_pais_created
  on public.cruce_cuentas_bamboo_correcciones (pais, created_at desc);

create index if not exists ix_cruce_cuentas_bamboo_correcciones_estado
  on public.cruce_cuentas_bamboo_correcciones (estado, created_at desc);

create or replace function public.cruce_cuentas_bamboo_set_normalized()
returns trigger
language plpgsql
set search_path = public
as $$
begin
  new.pais := upper(trim(new.pais));
  new.nombre_origen := trim(new.nombre_origen);
  new.nombre_origen_normalizado := public.cruce_cuentas_normalizar_nombre(new.nombre_origen);
  new.cuenta_bancaria := regexp_replace(coalesce(new.cuenta_bancaria, ''), '[^0-9A-Za-z]+', '', 'g');
  new.validado_por_email := lower(trim(new.validado_por_email));
  new.updated_at := now();
  return new;
end;
$$;

drop trigger if exists trg_cruce_cuentas_bamboo_alias_normalized
  on public.cruce_cuentas_bamboo_aliases;

create trigger trg_cruce_cuentas_bamboo_alias_normalized
before insert or update on public.cruce_cuentas_bamboo_aliases
for each row execute function public.cruce_cuentas_bamboo_set_normalized();

create or replace function public.cruce_cuentas_bamboo_touch_updated_at()
returns trigger
language plpgsql
set search_path = public
as $$
begin
  new.updated_at := now();
  return new;
end;
$$;

drop trigger if exists trg_cruce_cuentas_bamboo_correcciones_updated_at
  on public.cruce_cuentas_bamboo_correcciones;

create trigger trg_cruce_cuentas_bamboo_correcciones_updated_at
before update on public.cruce_cuentas_bamboo_correcciones
for each row execute function public.cruce_cuentas_bamboo_touch_updated_at();

-- RPC consumida únicamente por los workflows generadores de reportes.
-- Siempre devuelve un objeto con aliases=[], incluso cuando no existen registros.
create or replace function public.cruce_cuentas_bamboo_aliases_activos(p_pais text)
returns jsonb
language sql
stable
security definer
set search_path = public
as $$
  select jsonb_build_object(
    'ok', true,
    'pais', upper(trim(coalesce(p_pais, ''))),
    'aliases', coalesce(
      jsonb_agg(
        jsonb_build_object(
          'id', a.id,
          'pais', a.pais,
          'nombre_origen', a.nombre_origen,
          'nombre_origen_normalizado', a.nombre_origen_normalizado,
          'cuenta_bancaria', a.cuenta_bancaria,
          'bamboo_employee_number', a.bamboo_employee_number,
          'bamboo_employee_id', a.bamboo_employee_id,
          'bamboo_nombre', a.bamboo_nombre,
          'motivo', a.motivo,
          'activo', a.activo
        )
        order by a.updated_at desc
      ) filter (where a.id is not null),
      '[]'::jsonb
    )
  )
  from public.cruce_cuentas_bamboo_aliases a
  where a.activo = true
    and a.pais = upper(trim(coalesce(p_pais, '')))
    and upper(trim(coalesce(p_pais, ''))) in ('GT', 'TT');
$$;

alter table public.cruce_cuentas_bamboo_aliases enable row level security;
alter table public.cruce_cuentas_bamboo_correcciones enable row level security;

-- El navegador nunca escribe ni lee estas tablas directamente.
-- Toda operación pasa por n8n con service_role.
revoke all on table public.cruce_cuentas_bamboo_aliases from anon, authenticated;
revoke all on table public.cruce_cuentas_bamboo_correcciones from anon, authenticated;
grant all on table public.cruce_cuentas_bamboo_aliases to service_role;
grant all on table public.cruce_cuentas_bamboo_correcciones to service_role;

revoke all on function public.cruce_cuentas_bamboo_aliases_activos(text) from public, anon, authenticated;
grant execute on function public.cruce_cuentas_bamboo_aliases_activos(text) to service_role;

-- ============================================================
-- HISTÓRICOS: conservar y recuperar Banco sin Bamboo por reporte
-- ============================================================

-- Los workflows ya envían el detalle de cada caso. Esta columna hace que el
-- detalle quede persistido y pueda abrirse días después desde Históricos.
alter table public.cruces_cuentas_gt_reportes
  add column if not exists detalle_banco_sin_bamboo jsonb not null default '[]'::jsonb;

-- Identificador generado por el navegador para trazabilidad de una ejecución.
-- No se usa como llave única porque no se reintenta automáticamente un POST
-- de conciliación (evita duplicar reportes, correos o Sheets).
alter table public.cruces_cuentas_gt_reportes
  add column if not exists client_request_id text;

update public.cruces_cuentas_gt_reportes
set detalle_banco_sin_bamboo = '[]'::jsonb
where detalle_banco_sin_bamboo is null;

create index if not exists ix_cruces_cuentas_reportes_client_request
  on public.cruces_cuentas_gt_reportes (client_request_id)
  where client_request_id is not null;

create index if not exists ix_cruce_cuentas_bamboo_correcciones_execution
  on public.cruce_cuentas_bamboo_correcciones (execution_id, created_at desc)
  where execution_id is not null;

-- Reemplaza la RPC histórica manteniendo exactamente la misma firma y
-- estructura pública, pero excluye detalle_banco_sin_bamboo de la lista para
-- no descargar todos los casos cada vez que se abre Históricos. El detalle se
-- solicita únicamente cuando el usuario pulsa Banco sin Bamboo.
create or replace function public.cruce_cuentas_get_historicos(
  p_country text,
  p_page integer default 1,
  p_page_size integer default 10,
  p_search text default null,
  p_status text default null
)
returns jsonb
language plpgsql
security definer
set search_path = public
as $$
declare
  v_country text := upper(trim(coalesce(p_country, ''));
  v_page integer := greatest(coalesce(p_page, 1), 1);
  v_page_size integer := least(greatest(coalesce(p_page_size, 10), 1), 100);
  v_offset integer;
  v_search text := nullif(trim(coalesce(p_search, '')), '');
  v_status text := nullif(trim(coalesce(p_status, '')), '');
  v_total bigint;
  v_filtered_total bigint;
  v_reports jsonb;
begin
  if v_country not in ('GT', 'TT') then
    raise exception 'País no permitido: %', v_country;
  end if;

  v_offset := (v_page - 1) * v_page_size;

  select count(*)
    into v_total
  from public.cruces_cuentas_gt_reportes r
  where upper(coalesce(r.country, 'GT')) = v_country;

  select count(*)
    into v_filtered_total
  from public.cruces_cuentas_gt_reportes r
  where upper(coalesce(r.country, 'GT')) = v_country
    and (v_status is null or r.estado = v_status)
    and (
      v_search is null
      or coalesce(r.period_label, '') ilike '%' || v_search || '%'
      or coalesce(r.payroll_file_name, '') ilike '%' || v_search || '%'
      or coalesce(r.ejecutado_por_nombre, '') ilike '%' || v_search || '%'
      or coalesce(r.resuelto_por_nombre, '') ilike '%' || v_search || '%'
      or r.id::text ilike '%' || v_search || '%'
    );

  select coalesce(
      jsonb_agg(
        (to_jsonb(q) - 'detalle_banco_sin_bamboo')
        order by q.created_at desc
      ),
      '[]'::jsonb
    )
    into v_reports
  from (
    select r.*
    from public.cruces_cuentas_gt_reportes r
    where upper(coalesce(r.country, 'GT')) = v_country
      and (v_status is null or r.estado = v_status)
      and (
        v_search is null
        or coalesce(r.period_label, '') ilike '%' || v_search || '%'
        or coalesce(r.payroll_file_name, '') ilike '%' || v_search || '%'
        or coalesce(r.ejecutado_por_nombre, '') ilike '%' || v_search || '%'
        or coalesce(r.resuelto_por_nombre, '') ilike '%' || v_search || '%'
        or r.id::text ilike '%' || v_search || '%'
      )
    order by r.created_at desc
    limit v_page_size
    offset v_offset
  ) q;

  return jsonb_build_object(
    'ok', true,
    'country', v_country,
    'page', v_page,
    'page_size', v_page_size,
    'total', v_total,
    'filtered_total', v_filtered_total,
    'total_pages', greatest(ceil(v_filtered_total::numeric / v_page_size)::integer, 1),
    'reports', v_reports
  );
end;
$$;

revoke all on function public.cruce_cuentas_get_historicos(text, integer, integer, text, text) from public, anon;
grant execute on function public.cruce_cuentas_get_historicos(text, integer, integer, text, text) to authenticated;

-- RPC de detalle utilizada por Históricos al abrir un reporte específico.
-- No otorga SELECT directo sobre las tablas de aliases/correcciones.
create or replace function public.cruce_cuentas_get_banco_sin_bamboo_reporte(
  p_report_id text,
  p_country text
)
returns jsonb
language plpgsql
stable
security definer
set search_path = public
as $$
declare
  v_country text := upper(trim(coalesce(p_country, ''));
  v_report public.cruces_cuentas_gt_reportes%rowtype;
  v_corrections jsonb := '[]'::jsonb;
begin
  if auth.uid() is null then
    raise exception 'No autenticado.';
  end if;

  if v_country not in ('GT', 'TT') then
    raise exception 'País no permitido: %', v_country;
  end if;

  select r.*
    into v_report
  from public.cruces_cuentas_gt_reportes r
  where r.id::text = trim(coalesce(p_report_id, ''))
    and upper(coalesce(r.country, 'GT')) = v_country
  limit 1;

  if not found then
    return jsonb_build_object(
      'ok', false,
      'message', 'No se encontró el reporte solicitado.'
    );
  end if;

  select coalesce(
      jsonb_agg(
        jsonb_build_object(
          'id', c.id,
          'pais', c.pais,
          'nombre_origen_principal', c.nombre_origen_principal,
          'nombres_origen', c.nombres_origen,
          'cuenta_bancaria', c.cuenta_bancaria,
          'bamboo_employee_number', c.bamboo_employee_number,
          'bamboo_nombre', c.bamboo_nombre,
          'estado', c.estado,
          'correo_enviado', c.correo_enviado,
          'correo_enviado_at', c.correo_enviado_at,
          'created_at', c.created_at
        )
        order by c.created_at desc
      ),
      '[]'::jsonb
    )
    into v_corrections
  from public.cruce_cuentas_bamboo_correcciones c
  where c.pais = v_country
    and (
      c.execution_id = v_report.id::text
      or (
        nullif(trim(coalesce(c.execution_id, '')), '') is null
        and nullif(trim(coalesce(c.report_url, '')), '') is not null
        and c.report_url = v_report.report_url
      )
    );

  return jsonb_build_object(
    'ok', true,
    'report', jsonb_build_object(
      'id', v_report.id,
      'country', v_report.country,
      'period_label', v_report.period_label,
      'period_start', v_report.period_start,
      'period_end', v_report.period_end,
      'report_url', v_report.report_url,
      'banco_sin_bamboo', v_report.banco_sin_bamboo
    ),
    'cases', coalesce(v_report.detalle_banco_sin_bamboo, '[]'::jsonb),
    'corrections', v_corrections
  );
end;
$$;

revoke all on function public.cruce_cuentas_get_banco_sin_bamboo_reporte(text, text) from public, anon;
grant execute on function public.cruce_cuentas_get_banco_sin_bamboo_reporte(text, text) to authenticated;

commit;

-- Validaciones opcionales después de ejecutar:
-- select public.cruce_cuentas_bamboo_aliases_activos('GT');
-- select public.cruce_cuentas_bamboo_aliases_activos('TT');
-- select column_name, data_type from information_schema.columns
--   where table_schema='public' and table_name='cruces_cuentas_gt_reportes'
--   and column_name in ('detalle_banco_sin_bamboo', 'client_request_id');
