-- Hotfix for migration 020: the German→English rename renamed function NAMES
-- via ALTER FUNCTION but never rewrote the function BODIES. On production
-- (which had been seeded from the original German-named migration 018), the
-- bodies still referenced paliad.projekte / paliad.projekt_teams /
-- paliad.fristen / paliad.termine / paliad.projekt_events / the German
-- column names — all of which had been renamed/dropped in 018+020. Any
-- RLS-enforced query against the new English tables exploded with
-- `relation "paliad.projekte" does not exist`, breaking /api/projects,
-- /api/deadlines, /api/appointments, etc.
--
-- The trigger functions paliad.projekte_sync_path() and
-- paliad.projekte_rewrite_subtree() suffered the same fate — kept their
-- German names and German bodies. Triggers on paliad.projects still pointed
-- at them, so any INSERT/UPDATE on projects tripped them too.
--
-- Strategy: DROP FUNCTION ... CASCADE drops the broken function and its
-- dependent RLS policies (whose names are still German on production —
-- projekte_*, projekt_teams_*, fristen_all, termine_*, parteien_all,
-- dokumente_all, projekt_events_all, notizen_all). Recreate the functions
-- with English bodies + English parameter names, then rebuild every
-- dependent policy under its canonical English name (matching migration
-- 018). Trigger functions are dropped+recreated under English names with
-- English-table bodies; triggers are recreated to point at the new names.
--
-- Idempotent: on a fresh DB everything is already English, so the CASCADE
-- drops the same English-named policies and they're recreated identically.

-- ---------------------------------------------------------------------------
-- 1. Drop broken visibility functions (CASCADE drops dependent policies).
-- ---------------------------------------------------------------------------
DROP FUNCTION IF EXISTS paliad.note_is_visible(uuid, uuid, uuid, uuid) CASCADE;
DROP FUNCTION IF EXISTS paliad.can_see_project(uuid)                   CASCADE;

-- ---------------------------------------------------------------------------
-- 2. Recreate can_see_project() with English body + English parameter.
-- ---------------------------------------------------------------------------
CREATE FUNCTION paliad.can_see_project(_project_id uuid)
RETURNS boolean
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = paliad, public
AS $$
    SELECT EXISTS (
        SELECT 1 FROM paliad.users u
         WHERE u.id = auth.uid() AND u.role = 'admin'
    )
    OR EXISTS (
        -- Any team membership (direct or ancestor). `path` always includes
        -- the target's own id as the last label, so this handles direct too.
        SELECT 1
          FROM paliad.projects      target
          JOIN paliad.project_teams pt
            ON pt.user_id = auth.uid()
           AND pt.project_id = ANY(string_to_array(target.path, '.')::uuid[])
         WHERE target.id = _project_id
    );
$$;

COMMENT ON FUNCTION paliad.can_see_project(uuid) IS
    'Team-based visibility predicate for paliad.projects. Direct or inherited '
    '(ancestor) membership grants access. Admins see all.';

-- ---------------------------------------------------------------------------
-- 3. Recreate note_is_visible() with English body + English parameters.
-- ---------------------------------------------------------------------------
CREATE FUNCTION paliad.note_is_visible(
    _project_id       uuid,
    _deadline_id      uuid,
    _appointment_id   uuid,
    _project_event_id uuid
) RETURNS boolean
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = paliad, public
AS $$
    SELECT CASE
        WHEN _project_id IS NOT NULL THEN paliad.can_see_project(_project_id)
        WHEN _deadline_id IS NOT NULL THEN paliad.can_see_project(
            (SELECT project_id FROM paliad.deadlines WHERE id = _deadline_id))
        WHEN _appointment_id IS NOT NULL THEN
            CASE
                WHEN (SELECT project_id FROM paliad.appointments WHERE id = _appointment_id) IS NULL
                    THEN (SELECT created_by FROM paliad.appointments WHERE id = _appointment_id) = auth.uid()
                ELSE paliad.can_see_project(
                    (SELECT project_id FROM paliad.appointments WHERE id = _appointment_id))
            END
        WHEN _project_event_id IS NOT NULL THEN paliad.can_see_project(
            (SELECT project_id FROM paliad.project_events WHERE id = _project_event_id))
        ELSE false
    END;
$$;

-- ---------------------------------------------------------------------------
-- 4. Rebuild RLS policies that the CASCADE just dropped. Names match the
--    canonical English set in migration 018. On production the dropped
--    policies were named projekte_*, projekt_teams_*, fristen_all,
--    termine_*, parteien_all, dokumente_all, projekt_events_all,
--    notizen_all — those names are gone after this migration.
-- ---------------------------------------------------------------------------

-- projects ------------------------------------------------------------------
CREATE POLICY projects_select ON paliad.projects
    FOR SELECT TO authenticated
    USING (paliad.can_see_project(id));

CREATE POLICY projects_insert ON paliad.projects
    FOR INSERT TO authenticated
    WITH CHECK (
        parent_id IS NULL
        OR paliad.can_see_project(parent_id)
    );

CREATE POLICY projects_update ON paliad.projects
    FOR UPDATE TO authenticated
    USING      (paliad.can_see_project(id))
    WITH CHECK (paliad.can_see_project(id));

CREATE POLICY projects_delete ON paliad.projects
    FOR DELETE TO authenticated
    USING (
        paliad.can_see_project(id)
        AND EXISTS (
            SELECT 1 FROM paliad.users u
             WHERE u.id = auth.uid() AND u.role IN ('partner','admin')
        )
    );

-- project_teams -------------------------------------------------------------
CREATE POLICY project_teams_select ON paliad.project_teams
    FOR SELECT TO authenticated
    USING (paliad.can_see_project(project_id));

CREATE POLICY project_teams_insert ON paliad.project_teams
    FOR INSERT TO authenticated
    WITH CHECK (
        user_id = auth.uid()
        OR paliad.can_see_project(project_id)
    );

CREATE POLICY project_teams_update ON paliad.project_teams
    FOR UPDATE TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

CREATE POLICY project_teams_delete ON paliad.project_teams
    FOR DELETE TO authenticated
    USING (
        paliad.can_see_project(project_id)
        AND (
            user_id = auth.uid()
            OR EXISTS (
                SELECT 1 FROM paliad.users u
                 WHERE u.id = auth.uid() AND u.role IN ('partner','admin')
            )
        )
    );

-- parties / deadlines / documents / project_events --------------------------
CREATE POLICY parties_all ON paliad.parties
    FOR ALL TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

CREATE POLICY deadlines_all ON paliad.deadlines
    FOR ALL TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

CREATE POLICY documents_all ON paliad.documents
    FOR ALL TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

CREATE POLICY project_events_all ON paliad.project_events
    FOR ALL TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

-- appointments (project_id nullable; personal appts stay creator-only) -----
CREATE POLICY appointments_select ON paliad.appointments
    FOR SELECT TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );

CREATE POLICY appointments_insert ON paliad.appointments
    FOR INSERT TO authenticated
    WITH CHECK (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );

CREATE POLICY appointments_update ON paliad.appointments
    FOR UPDATE TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    )
    WITH CHECK (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );

CREATE POLICY appointments_delete ON paliad.appointments
    FOR DELETE TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );

-- notes (polymorphic parent dispatch) --------------------------------------
CREATE POLICY notes_all ON paliad.notes
    FOR ALL TO authenticated
    USING      (paliad.note_is_visible(project_id, deadline_id, appointment_id, project_event_id))
    WITH CHECK (paliad.note_is_visible(project_id, deadline_id, appointment_id, project_event_id));

-- checklist_instances (project_id nullable; personal stays creator-only) ---
CREATE POLICY checklist_instances_select ON paliad.checklist_instances
    FOR SELECT TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );

CREATE POLICY checklist_instances_insert ON paliad.checklist_instances
    FOR INSERT TO authenticated
    WITH CHECK (
        created_by = auth.uid()
        AND (project_id IS NULL OR paliad.can_see_project(project_id))
    );

CREATE POLICY checklist_instances_update ON paliad.checklist_instances
    FOR UPDATE TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    )
    WITH CHECK (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );

CREATE POLICY checklist_instances_delete ON paliad.checklist_instances
    FOR DELETE TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );

-- ---------------------------------------------------------------------------
-- 5. Trigger functions: drop the German-named pair (and their bound
--    triggers), recreate under English names with English-table bodies,
--    rebind the triggers. Migration 020 missed these entirely.
-- ---------------------------------------------------------------------------
DROP TRIGGER IF EXISTS projekte_sync_path_before      ON paliad.projects;
DROP TRIGGER IF EXISTS projekte_rewrite_subtree_after ON paliad.projects;
DROP FUNCTION IF EXISTS paliad.projekte_sync_path()       CASCADE;
DROP FUNCTION IF EXISTS paliad.projekte_rewrite_subtree() CASCADE;

CREATE OR REPLACE FUNCTION paliad.projects_sync_path()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
    parent_path text;
BEGIN
    IF NEW.parent_id IS NULL THEN
        NEW.path := NEW.id::text;
    ELSE
        SELECT path INTO parent_path
          FROM paliad.projects
         WHERE id = NEW.parent_id;
        IF parent_path IS NULL THEN
            RAISE EXCEPTION 'parent project % not found', NEW.parent_id;
        END IF;
        -- Reject cycles: parent's path cannot contain this row's id.
        IF parent_path = NEW.id::text
           OR parent_path LIKE '%.' || NEW.id::text
           OR parent_path LIKE NEW.id::text || '.%'
           OR parent_path LIKE '%.' || NEW.id::text || '.%'
        THEN
            RAISE EXCEPTION 'cannot set parent to own descendant';
        END IF;
        NEW.path := parent_path || '.' || NEW.id::text;
    END IF;
    RETURN NEW;
END;
$$;

CREATE OR REPLACE FUNCTION paliad.projects_rewrite_subtree()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
    IF OLD.path IS DISTINCT FROM NEW.path THEN
        UPDATE paliad.projects
           SET path = NEW.path || substring(path FROM length(OLD.path) + 1)
         WHERE path LIKE OLD.path || '.%';
    END IF;
    RETURN NEW;
END;
$$;

DROP TRIGGER IF EXISTS projects_sync_path_before      ON paliad.projects;
DROP TRIGGER IF EXISTS projects_rewrite_subtree_after ON paliad.projects;

CREATE TRIGGER projects_sync_path_before
    BEFORE INSERT OR UPDATE OF parent_id ON paliad.projects
    FOR EACH ROW EXECUTE FUNCTION paliad.projects_sync_path();

CREATE TRIGGER projects_rewrite_subtree_after
    AFTER UPDATE OF path ON paliad.projects
    FOR EACH ROW EXECUTE FUNCTION paliad.projects_rewrite_subtree();
