-- =====================================================
-- Member Merge / Deduplication
--
-- Atomic function to merge two member records:
-- picks field values, moves all references, archives secondary.
-- =====================================================

-- Merge log table for audit trail and potential undo
CREATE TABLE IF NOT EXISTS public.member_merges (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
  primary_member_id uuid NOT NULL,
  secondary_member_id uuid NOT NULL,
  secondary_snapshot jsonb NOT NULL,
  field_choices jsonb NOT NULL,
  references_moved jsonb NOT NULL,
  performed_by uuid NOT NULL REFERENCES auth.users(id) ON DELETE SET NULL,
  performed_at timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX ix_member_merges_account ON public.member_merges(account_id);

ALTER TABLE public.member_merges ENABLE ROW LEVEL SECURITY;
REVOKE ALL ON public.member_merges FROM authenticated, service_role;
GRANT SELECT ON public.member_merges TO authenticated;
GRANT ALL ON public.member_merges TO service_role;

CREATE POLICY member_merges_select
  ON public.member_merges FOR SELECT TO authenticated
  USING (public.has_role_on_account(account_id));

-- Atomic merge function
CREATE OR REPLACE FUNCTION public.merge_members(
  p_primary_id uuid,
  p_secondary_id uuid,
  p_field_choices jsonb DEFAULT '{}',
  p_performed_by uuid DEFAULT NULL
)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
  v_primary record;
  v_secondary record;
  v_account_id uuid;
  v_user_id uuid;
  v_refs_moved jsonb := '{}'::jsonb;
  v_count int;
  v_field text;
  v_choice text;
  v_update jsonb := '{}'::jsonb;
BEGIN
  v_user_id := COALESCE(p_performed_by, auth.uid());

  -- 1. Fetch both members
  SELECT * INTO v_primary FROM public.members WHERE id = p_primary_id;
  SELECT * INTO v_secondary FROM public.members WHERE id = p_secondary_id;

  IF v_primary IS NULL THEN RAISE EXCEPTION 'Primary member not found'; END IF;
  IF v_secondary IS NULL THEN RAISE EXCEPTION 'Secondary member not found'; END IF;

  IF v_primary.account_id != v_secondary.account_id THEN
    RAISE EXCEPTION 'Members must belong to the same account';
  END IF;

  v_account_id := v_primary.account_id;

  -- Verify caller access
  IF NOT public.has_permission(auth.uid(), v_account_id, 'members.write'::public.app_permissions) THEN
    RAISE EXCEPTION 'Access denied' USING ERRCODE = '42501';
  END IF;

  -- 2. Apply field choices: for each conflicting field, pick primary or secondary value
  FOR v_field, v_choice IN SELECT * FROM jsonb_each_text(p_field_choices)
  LOOP
    -- Validate choice value
    IF v_choice NOT IN ('primary', 'secondary') THEN
      RAISE EXCEPTION 'Invalid choice "%" for field "%". Must be "primary" or "secondary"', v_choice, v_field;
    END IF;

    -- Whitelist of mergeable fields (no IDs, FKs, or system columns)
    IF v_field NOT IN (
      'first_name', 'last_name', 'email', 'phone', 'mobile', 'phone2', 'fax',
      'street', 'house_number', 'street2', 'postal_code', 'city', 'country',
      'date_of_birth', 'gender', 'title', 'salutation', 'birthplace', 'birth_country',
      'notes', 'guardian_name', 'guardian_phone', 'guardian_email'
    ) THEN
      RAISE EXCEPTION 'Field "%" cannot be merged', v_field;
    END IF;

    IF v_choice = 'secondary' THEN
      v_update := v_update || jsonb_build_object(v_field, to_jsonb(v_secondary) -> v_field);
    END IF;
  END LOOP;

  -- Apply chosen fields to primary
  IF v_update != '{}'::jsonb THEN
    -- Build dynamic UPDATE
    EXECUTE format(
      'UPDATE public.members SET %s WHERE id = $1',
      (SELECT string_agg(format('%I = %L', key, value #>> '{}'), ', ')
       FROM jsonb_each(v_update))
    ) USING p_primary_id;
  END IF;

  -- 3. Move references from secondary to primary

  -- Department assignments
  SELECT count(*) INTO v_count FROM public.member_department_assignments WHERE member_id = p_secondary_id;
  INSERT INTO public.member_department_assignments (member_id, department_id)
    SELECT p_primary_id, department_id
    FROM public.member_department_assignments
    WHERE member_id = p_secondary_id
    ON CONFLICT (member_id, department_id) DO NOTHING;
  DELETE FROM public.member_department_assignments WHERE member_id = p_secondary_id;
  v_refs_moved := v_refs_moved || jsonb_build_object('departments', v_count);

  -- Roles
  SELECT count(*) INTO v_count FROM public.member_roles WHERE member_id = p_secondary_id;
  UPDATE public.member_roles SET member_id = p_primary_id WHERE member_id = p_secondary_id;
  v_refs_moved := v_refs_moved || jsonb_build_object('roles', v_count);

  -- Honors
  SELECT count(*) INTO v_count FROM public.member_honors WHERE member_id = p_secondary_id;
  UPDATE public.member_honors SET member_id = p_primary_id WHERE member_id = p_secondary_id;
  v_refs_moved := v_refs_moved || jsonb_build_object('honors', v_count);

  -- SEPA mandates
  SELECT count(*) INTO v_count FROM public.sepa_mandates WHERE member_id = p_secondary_id;
  UPDATE public.sepa_mandates SET member_id = p_primary_id, is_primary = false WHERE member_id = p_secondary_id;
  v_refs_moved := v_refs_moved || jsonb_build_object('mandates', v_count);

  -- Member cards
  SELECT count(*) INTO v_count FROM public.member_cards WHERE member_id = p_secondary_id;
  UPDATE public.member_cards SET member_id = p_primary_id WHERE member_id = p_secondary_id;
  v_refs_moved := v_refs_moved || jsonb_build_object('cards', v_count);

  -- Portal invitations
  SELECT count(*) INTO v_count FROM public.member_portal_invitations WHERE member_id = p_secondary_id;
  UPDATE public.member_portal_invitations SET member_id = p_primary_id WHERE member_id = p_secondary_id;
  v_refs_moved := v_refs_moved || jsonb_build_object('invitations', v_count);

  -- Tag assignments
  BEGIN
    SELECT count(*) INTO v_count FROM public.member_tag_assignments WHERE member_id = p_secondary_id;
    INSERT INTO public.member_tag_assignments (member_id, tag_id, assigned_by)
      SELECT p_primary_id, tag_id, assigned_by
      FROM public.member_tag_assignments
      WHERE member_id = p_secondary_id
      ON CONFLICT (member_id, tag_id) DO NOTHING;
    DELETE FROM public.member_tag_assignments WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('tags', v_count);
  EXCEPTION WHEN undefined_table THEN NULL; -- tags table may not exist yet
  END;

  -- Event registrations (if member_id column exists)
  BEGIN
    SELECT count(*) INTO v_count FROM public.event_registrations WHERE member_id = p_secondary_id;
    UPDATE public.event_registrations SET member_id = p_primary_id WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('events', v_count);
  EXCEPTION WHEN undefined_column THEN NULL;
  END;

  -- Communications
  BEGIN
    SELECT count(*) INTO v_count FROM public.member_communications WHERE member_id = p_secondary_id;
    UPDATE public.member_communications SET member_id = p_primary_id WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('communications', v_count);
  EXCEPTION WHEN undefined_table THEN NULL;
  END;

  -- Course participants
  BEGIN
    SELECT count(*) INTO v_count FROM public.course_participants WHERE member_id = p_secondary_id;
    UPDATE public.course_participants SET member_id = p_primary_id WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('courses', v_count);
  EXCEPTION WHEN undefined_table THEN NULL;
  END;

  -- Catch books (Fischerei)
  BEGIN
    SELECT count(*) INTO v_count FROM public.catch_books WHERE member_id = p_secondary_id;
    UPDATE public.catch_books SET member_id = p_primary_id WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('catch_books', v_count);
  EXCEPTION WHEN undefined_table THEN NULL;
  END;

  -- Catches
  BEGIN
    SELECT count(*) INTO v_count FROM public.catches WHERE member_id = p_secondary_id;
    UPDATE public.catches SET member_id = p_primary_id WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('catches', v_count);
  EXCEPTION WHEN undefined_table THEN NULL;
  END;

  -- Water leases
  BEGIN
    SELECT count(*) INTO v_count FROM public.water_leases WHERE member_id = p_secondary_id;
    UPDATE public.water_leases SET member_id = p_primary_id WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('water_leases', v_count);
  EXCEPTION WHEN undefined_table THEN NULL;
  END;

  -- Competition participants
  BEGIN
    SELECT count(*) INTO v_count FROM public.competition_participants WHERE member_id = p_secondary_id;
    UPDATE public.competition_participants SET member_id = p_primary_id WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('competitions', v_count);
  EXCEPTION WHEN undefined_table THEN NULL;
  END;

  -- Invoices
  BEGIN
    SELECT count(*) INTO v_count FROM public.invoices WHERE member_id = p_secondary_id;
    UPDATE public.invoices SET member_id = p_primary_id WHERE member_id = p_secondary_id;
    v_refs_moved := v_refs_moved || jsonb_build_object('invoices', v_count);
  EXCEPTION WHEN undefined_table THEN NULL;
  END;

  -- Audit log entries
  UPDATE public.member_audit_log SET member_id = p_primary_id WHERE member_id = p_secondary_id;

  -- 4. Merge custom_data (union of keys, primary wins on conflicts)
  UPDATE public.members
  SET custom_data = v_secondary.custom_data || v_primary.custom_data
  WHERE id = p_primary_id;

  -- 5. Append merge note
  UPDATE public.members
  SET notes = COALESCE(notes, '') ||
    E'\n[Zusammenführung ' || to_char(now(), 'YYYY-MM-DD') || '] ' ||
    'Zusammengeführt mit ' || v_secondary.first_name || ' ' || v_secondary.last_name ||
    COALESCE(' (Nr. ' || v_secondary.member_number || ')', '')
  WHERE id = p_primary_id;

  -- 6. Archive the secondary member
  UPDATE public.members
  SET status = 'resigned', is_archived = true,
      exit_date = current_date, exit_reason = 'Zusammenführung mit Mitglied ' || p_primary_id::text,
      notes = COALESCE(notes, '') || E'\n[Zusammenführung] Archiviert zugunsten von ' || v_primary.first_name || ' ' || v_primary.last_name
  WHERE id = p_secondary_id;

  -- 7. Create merge log entry
  INSERT INTO public.member_merges (
    account_id, primary_member_id, secondary_member_id,
    secondary_snapshot, field_choices, references_moved, performed_by
  ) VALUES (
    v_account_id, p_primary_id, p_secondary_id,
    to_jsonb(v_secondary), p_field_choices, v_refs_moved, v_user_id
  );

  -- 8. Audit log
  INSERT INTO public.member_audit_log (member_id, account_id, user_id, action, metadata)
  VALUES (p_primary_id, v_account_id, v_user_id, 'merged',
    jsonb_build_object(
      'secondary_member_id', p_secondary_id,
      'secondary_name', v_secondary.first_name || ' ' || v_secondary.last_name,
      'references_moved', v_refs_moved,
      'field_choices', p_field_choices
    )
  );

  RETURN jsonb_build_object(
    'primary_id', p_primary_id,
    'secondary_id', p_secondary_id,
    'references_moved', v_refs_moved
  );
END;
$$;

GRANT EXECUTE ON FUNCTION public.merge_members(uuid, uuid, jsonb, uuid)
  TO authenticated, service_role;
