-- ---------------------------------------------------------------
-- 019 — ledger_entries (Session 58, work-order Phase 1 + CLV amendments).
--
-- The truth infrastructure: every grade — user scan AND pipeline pre-grade —
-- persists here, settles against the real result, and carries closing-line
-- value. Rows with user_id NULL are the PUBLIC model record.
--
-- DATA SEMANTICS: `line`, `locked_odds`, `book`, `closing_line`,
-- `closing_odds` are REAL book values captured at a timestamp — never
-- generated. `model_value` / grade / edge / confidence are VYNDR model
-- output. A row must never exist with a fabricated market value.
--
-- All writes go through the service role (pipeline + API). Clients only
-- read: their own rows, plus the public (user_id IS NULL) record.
-- ---------------------------------------------------------------

CREATE TABLE IF NOT EXISTS public.ledger_entries (
  id                  uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id             uuid NULL REFERENCES auth.users (id) ON DELETE CASCADE,
  player_key          text NOT NULL,
  player_name         text NOT NULL,
  sport               text NOT NULL,
  stat                text NOT NULL,
  line                numeric NOT NULL,
  side                text NOT NULL CHECK (side IN ('over', 'under')),
  locked_odds         text NULL,
  book                text NULL,
  grade               text NOT NULL,
  edge                numeric NULL,
  confidence          numeric NULL,
  model_value         numeric NULL,
  graded_at           timestamptz NOT NULL DEFAULT now(),
  game_id             text NOT NULL,
  game_date           date NOT NULL,
  closing_line        numeric NULL,
  closing_odds        text NULL,
  clv                 numeric NULL,
  clv_result          text NULL CHECK (clv_result IN ('beat', 'faded', 'flat')),
  outcome             text NULL CHECK (outcome IN ('hit', 'miss', 'push')),
  actual_value        numeric NULL,
  settled_at          timestamptz NULL,
  revised_from_grade  text NULL
);

-- Idempotency: pipeline re-runs and double-taps upsert into this. PG15+
-- NULLS NOT DISTINCT makes the null-user (public record) rows dedupe too,
-- and — unlike a coalesce-expression index — PostgREST `on_conflict` can
-- target a real constraint's column list.
ALTER TABLE public.ledger_entries
  ADD CONSTRAINT ledger_entries_dedupe
  UNIQUE NULLS NOT DISTINCT (user_id, player_key, stat, line, side, game_id);

CREATE INDEX IF NOT EXISTS idx_ledger_user_graded
  ON public.ledger_entries (user_id, graded_at DESC);
CREATE INDEX IF NOT EXISTS idx_ledger_gamedate_outcome
  ON public.ledger_entries (game_date, outcome);
CREATE INDEX IF NOT EXISTS idx_ledger_player_stat
  ON public.ledger_entries (player_key, stat);
CREATE INDEX IF NOT EXISTS idx_ledger_public_clv
  ON public.ledger_entries (clv_result) WHERE user_id IS NULL;
-- The settlement worker's scan: unsettled rows by date.
CREATE INDEX IF NOT EXISTS idx_ledger_unsettled
  ON public.ledger_entries (game_date) WHERE outcome IS NULL;

ALTER TABLE public.ledger_entries ENABLE ROW LEVEL SECURITY;

-- Users read their own rows.
CREATE POLICY ledger_select_own ON public.ledger_entries
  FOR SELECT TO authenticated
  USING (user_id = auth.uid());

-- Everyone (anon + authenticated) reads the public model record.
CREATE POLICY ledger_select_public ON public.ledger_entries
  FOR SELECT TO anon, authenticated
  USING (user_id IS NULL);

-- No INSERT/UPDATE/DELETE policies: client-side writes are impossible.
-- The pipeline + API write via the service role, which bypasses RLS.
