﻿-- ============================================================
-- Migration 015: Reconciliation Row Lock
-- Date: 2026-02-18
-- Purpose: Prevent overlapping reconciliation cycles per profile.
-- ============================================================

CREATE TABLE IF NOT EXISTS reconciliation_locks (
    profile_id uuid PRIMARY KEY,
    owner text NOT NULL,
    acquired_at timestamptz NOT NULL DEFAULT now(),
    expires_at timestamptz NOT NULL
);

CREATE INDEX IF NOT EXISTS reconciliation_locks_expires_idx ON reconciliation_locks (expires_at);

ALTER TABLE reconciliation_locks ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Allow reconciliation lock owner" ON reconciliation_locks;
CREATE POLICY "Allow reconciliation lock owner" ON reconciliation_locks
    FOR ALL
    USING (auth.uid() = profile_id)
    WITH CHECK (auth.uid() = profile_id);

DROP POLICY IF EXISTS "Allow service role reconciliation lock access" ON reconciliation_locks;
CREATE POLICY "Allow service role reconciliation lock access" ON reconciliation_locks
    FOR ALL
    USING (auth.role() = 'service_role')
    WITH CHECK (auth.role() = 'service_role');

CREATE OR REPLACE FUNCTION fn_try_acquire_reconciliation_lock_row(
    p_profile_id uuid,
    p_owner text,
    p_ttl_seconds integer DEFAULT 30
)
RETURNS boolean AS
$$
DECLARE
    now_ts timestamptz := now();
    ttl integer := GREATEST(COALESCE(p_ttl_seconds, 30), 1);
    expires timestamptz := now_ts + make_interval(secs := ttl);
BEGIN
    INSERT INTO reconciliation_locks (profile_id, owner, acquired_at, expires_at)
    VALUES (p_profile_id, p_owner, now_ts, expires)
    ON CONFLICT (profile_id)
    WHERE reconciliation_locks.expires_at <= now_ts
    DO UPDATE SET
        owner = EXCLUDED.owner,
        acquired_at = EXCLUDED.acquired_at,
        expires_at = EXCLUDED.expires_at;
    RETURN TRUE;
EXCEPTION WHEN unique_violation THEN
    RETURN FALSE;
END;
$$
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION fn_release_reconciliation_lock_row(
    p_profile_id uuid,
    p_owner text
)
RETURNS boolean AS
$$
DECLARE
    deleted_count integer;
BEGIN
    DELETE FROM reconciliation_locks
    WHERE profile_id = p_profile_id
      AND owner = p_owner;
    GET DIAGNOSTICS deleted_count = ROW_COUNT;
    RETURN deleted_count > 0;
END;
$$
LANGUAGE plpgsql;
