-- HR-only access model (spec §2)
--
-- Rationale: the app previously had two profile roles (hr_admin / manager),
-- with "manager" intended as a read-only, salary-masked role. The revised
-- scope is HR-only: nobody except an active, explicitly-provisioned HR user
-- may open the app at all. This migration:
--   1. Collapses profiles.role to the single allowed value 'hr'.
--   2. Adds profiles.is_active (default false — new users get zero access
--      until an existing HR user explicitly activates them; see §2.3).
--   3. Renames the authorization gate from is_hr_admin() to is_hr_user()
--      (checks role='hr' AND is_active=true) to match §2.4's naming and
--      semantics, and repoints every RLS policy at it.
--   4. Fixes a real gap: several tables (divisions/departments/teams/
--      locations, employee_history, positions, audit_log, reorg_scenarios/
--      reorg_moves) had read policies scoped to `auth.role() = 'authenticated'`
--      — i.e. ANY signed-in Supabase Auth user, not just HR. Every one of
--      those is tightened to is_hr_user().
--   5. Drops the employees_directory salary-masking view: with the
--      "manager" role gone and salary out of MVP scope (see the salary
--      deprecation migration), there is nothing left to mask and no second
--      role to mask it from. All reads now go through the base `employees`
--      table, gated by the same is_hr_user()-only policy as writes.
--
-- The application layer (app/(app)/layout.tsx) previously let any
-- authenticated Supabase user reach the shell (it only checked for a
-- session, not profiles.role/is_active) and merely hid the edit UI for
-- non-admins. That check is being replaced in the app code alongside this
-- migration — this migration is what makes that enforceable at the data
-- layer regardless of what the UI does.

-- ── profiles: single role, explicit activation ──────────────────────
alter table profiles drop constraint if exists profiles_role_check;
update profiles set role = 'hr' where role <> 'hr';
alter table profiles add constraint profiles_role_check check (role = 'hr');
alter table profiles alter column role set default 'hr';

alter table profiles add column if not exists is_active boolean not null default false;
alter table profiles add column if not exists created_by uuid references auth.users(id);
alter table profiles add column if not exists updated_at timestamptz not null default now();

-- Any *existing* profile row (i.e. someone already explicitly provisioned
-- before this migration) keeps working — activation is only "off by
-- default" for rows created from here on.
update profiles set is_active = true where is_active = false;

create or replace function fn_touch_profiles_updated_at()
returns trigger language plpgsql as $$
begin
  new.updated_at = now();
  return new;
end;
$$;

drop trigger if exists trg_profiles_touch_updated_at on profiles;
create trigger trg_profiles_touch_updated_at
before update on profiles
for each row execute function fn_touch_profiles_updated_at();

-- ── Authorization gate: is_hr_user() replaces is_hr_admin() ─────────
create or replace function is_hr_user()
returns boolean
language sql
security definer
set search_path = public
stable
as $$
  select exists (
    select 1 from profiles p where p.id = auth.uid() and p.role = 'hr' and p.is_active = true
  );
$$;

create or replace function current_hr_user_id()
returns uuid
language sql
security definer
set search_path = public
stable
as $$
  select p.id from profiles p where p.id = auth.uid() and p.role = 'hr' and p.is_active = true;
$$;

-- Back-compat shim so any not-yet-migrated call site (or a function defined
-- in an older addendum file not touched by this migration) keeps working;
-- new code should call is_hr_user() directly. Safe to drop once nothing
-- references is_hr_admin() anymore (tracked in docs/decisions).
create or replace function is_hr_admin()
returns boolean
language sql
stable
as $$
  select is_hr_user();
$$;

create or replace function require_hr_admin()
returns void language plpgsql as $$
begin
  if not is_hr_user() then
    raise exception 'Nicht berechtigt: nur aktive HR-Benutzer:innen dürfen diese Aktion ausführen.';
  end if;
end;
$$;

-- ── Re-scope every "any authenticated user" read policy to HR-only ──
drop policy if exists "org_read" on divisions;
create policy "org_read" on divisions for select using (is_hr_user());
drop policy if exists "org_write" on divisions;
create policy "org_write" on divisions for all using (is_hr_user()) with check (is_hr_user());

drop policy if exists "org_read" on departments;
create policy "org_read" on departments for select using (is_hr_user());
drop policy if exists "org_write" on departments;
create policy "org_write" on departments for all using (is_hr_user()) with check (is_hr_user());

drop policy if exists "org_read" on teams;
create policy "org_read" on teams for select using (is_hr_user());
drop policy if exists "org_write" on teams;
create policy "org_write" on teams for all using (is_hr_user()) with check (is_hr_user());

drop policy if exists "org_read" on locations;
create policy "org_read" on locations for select using (is_hr_user());
drop policy if exists "org_write" on locations;
create policy "org_write" on locations for all using (is_hr_user()) with check (is_hr_user());

drop policy if exists "history_read" on employee_history;
create policy "history_read" on employee_history for select using (is_hr_user());
drop policy if exists "history_insert_admin" on employee_history;
create policy "history_insert_admin" on employee_history for insert with check (is_hr_user());

drop policy if exists "positions_read" on positions;
create policy "positions_read" on positions for select using (is_hr_user());
drop policy if exists "positions_write_admin" on positions;
create policy "positions_write_admin" on positions for all using (is_hr_user()) with check (is_hr_user());

drop policy if exists "audit_read" on audit_log;
create policy "audit_read" on audit_log for select using (is_hr_user());
drop policy if exists "audit_insert_admin" on audit_log;
create policy "audit_insert_admin" on audit_log for insert with check (is_hr_user());

drop policy if exists "reorg_scenarios_read" on reorg_scenarios;
create policy "reorg_scenarios_read" on reorg_scenarios for select using (is_hr_user());
drop policy if exists "reorg_scenarios_write_admin" on reorg_scenarios;
create policy "reorg_scenarios_write_admin" on reorg_scenarios for all using (is_hr_user()) with check (is_hr_user());

drop policy if exists "reorg_moves_read" on reorg_moves;
create policy "reorg_moves_read" on reorg_moves for select using (is_hr_user());
drop policy if exists "reorg_moves_write_admin" on reorg_moves;
create policy "reorg_moves_write_admin" on reorg_moves for all using (is_hr_user()) with check (is_hr_user());

drop policy if exists "employees_admin_all" on employees;
create policy "employees_hr_all" on employees for all using (is_hr_user()) with check (is_hr_user());

-- profiles: users may always read their own row (needed to determine their
-- own HR status before is_hr_user() would otherwise apply); HR manages all.
drop policy if exists "profiles_select_admin" on profiles;
create policy "profiles_select_admin" on profiles for select using (is_hr_user());
drop policy if exists "profiles_write_admin" on profiles;
create policy "profiles_write_admin" on profiles for insert with check (is_hr_user());
drop policy if exists "profiles_update_admin" on profiles;
create policy "profiles_update_admin" on profiles for update using (is_hr_user()) with check (is_hr_user());

-- hire_drafts / saved_reports stay owner-scoped (unchanged) — but an owner
-- who is no longer an active HR user should not retain access either.
drop policy if exists "hire_drafts_owner" on hire_drafts;
create policy "hire_drafts_owner" on hire_drafts for all
  using (created_by = auth.uid() and is_hr_user()) with check (created_by = auth.uid() and is_hr_user());

drop policy if exists "saved_reports_owner" on saved_reports;
create policy "saved_reports_owner" on saved_reports for all
  using (created_by = auth.uid() and is_hr_user()) with check (created_by = auth.uid() and is_hr_user());

-- ── Drop the salary-masking view: no second role left to mask from ──
drop view if exists employees_directory;
