-- Cheap enrichment tables (item 2): small per-player-per-season aggregates from
-- the NBA's own pre-computed endpoints, plus a free derived team-season view.
-- Each table is ~500 rows/season (~2.5k total) — well under the free-tier budget.
-- Run after schema.sql:  psql $DATABASE_URL -f db/enrich.sql

-- Clutch performance (NBA "clutch" = last 5 min, score within 5).
CREATE TABLE IF NOT EXISTS clutch_stats (
    season       TEXT    NOT NULL,
    player_id    INTEGER NOT NULL,
    player_name  TEXT,
    team_abbreviation TEXT,
    gp INTEGER, w INTEGER, l INTEGER, min NUMERIC,
    fgm NUMERIC, fga NUMERIC, fg_pct NUMERIC,
    fg3m NUMERIC, fg3a NUMERIC, fg3_pct NUMERIC,
    ftm NUMERIC, fta NUMERIC, ft_pct NUMERIC,
    reb NUMERIC, ast NUMERIC, tov NUMERIC, stl NUMERIC, blk NUMERIC,
    pts NUMERIC, plus_minus NUMERIC,
    dd2 INTEGER, td3 INTEGER,            -- double-doubles / triple-doubles in clutch
    PRIMARY KEY (season, player_id)
);

-- Clutch *efficiency* view — the insightful answer to "best clutch scorer/shooter"
-- without the raw-points trap. Pure arithmetic over the tiny clutch_stats table
-- (no game-log scan), so it adds no query cost. ts_pct/efg_pct/pts_per_min make
-- an efficiency leaderboard a one-liner; passthrough columns (gp, min, fga) carry
-- the volume guard so a 2-for-2 night can't top the board.
CREATE OR REPLACE VIEW v_clutch AS
SELECT
    season, player_id, player_name, team_abbreviation,
    gp, w, l, min,
    fgm, fga, fg_pct, fg3m, fg3a, fg3_pct, ftm, fta, ft_pct,
    reb, ast, tov, stl, blk, pts, plus_minus, dd2, td3,
    round(pts / nullif(2 * (fga + 0.44 * fta), 0), 3) AS ts_pct,        -- true shooting
    round((fgm + 0.5 * fg3m) / nullif(fga, 0), 3)     AS efg_pct,       -- effective FG%
    round(pts / nullif(min, 0), 2)                    AS pts_per_min    -- scoring rate
FROM clutch_stats;

-- Hustle: effort metrics not visible in the box score.
CREATE TABLE IF NOT EXISTS hustle_stats (
    season       TEXT    NOT NULL,
    player_id    INTEGER NOT NULL,
    player_name  TEXT,
    team_abbreviation TEXT,
    g INTEGER, min NUMERIC,
    contested_shots NUMERIC,
    contested_shots_2pt NUMERIC,
    contested_shots_3pt NUMERIC,
    deflections NUMERIC,
    charges_drawn NUMERIC,
    screen_assists NUMERIC,
    screen_ast_pts NUMERIC,
    loose_balls_recovered NUMERIC,
    box_outs NUMERIC,
    PRIMARY KEY (season, player_id)
);

-- Tracking defense: how the player they guard shoots (overall defended FG%).
CREATE TABLE IF NOT EXISTS defense_tracking (
    season       TEXT    NOT NULL,
    player_id    INTEGER NOT NULL,
    player_name  TEXT,
    player_position TEXT,
    gp INTEGER,
    freq NUMERIC,                -- share of opp shots this player defended
    d_fgm NUMERIC, d_fga NUMERIC, d_fg_pct NUMERIC,
    normal_fg_pct NUMERIC,       -- those shooters' normal FG%
    pct_plusminus NUMERIC,       -- d_fg_pct - normal_fg_pct (negative = good defense)
    PRIMARY KEY (season, player_id)
);

-- Advanced per-player-season rollup. MATERIALIZED so the heavy aggregate over
-- player_game_logs runs ONCE at build, not per query — every "best/most
-- efficient/most valuable" leaderboard then reads ~500 rows/season instead of
-- scanning 1.14M. Covers all seasons (TS%/eFG% need only box-score columns we
-- have back to 1980). Refresh after an ingest:  REFRESH MATERIALIZED VIEW
-- CONCURRENTLY player_advanced;
DROP MATERIALIZED VIEW IF EXISTS player_advanced;
CREATE MATERIALIZED VIEW player_advanced AS
SELECT
    player_id,
    max(player_name)                                              AS player_name,
    season,
    count(*)                                                     AS gp,
    sum(min)                                                     AS min,
    round(sum(pts) / nullif(2 * (sum(fga) + 0.44 * sum(fta)), 0)::numeric, 3) AS ts_pct,
    round((sum(fgm) + 0.5 * sum(fg3m)) / nullif(sum(fga), 0)::numeric, 3)     AS efg_pct,
    round(sum(fg3a)::numeric / nullif(sum(fga), 0), 3)          AS fg3a_rate,   -- 3-point reliance
    round(sum(fta)::numeric  / nullif(sum(fga), 0), 3)          AS ft_rate,     -- how often they get to the line
    round(sum(ast)::numeric  / nullif(sum(tov), 0), 2)          AS ast_to,      -- assist-to-turnover
    round(36 * sum(pts)::numeric / nullif(sum(min), 0), 1)      AS pts_per36,
    round(36 * sum(reb)::numeric / nullif(sum(min), 0), 1)      AS reb_per36,
    round(36 * sum(ast)::numeric / nullif(sum(min), 0), 1)      AS ast_per36,
    round(sum(pts) / nullif(sum(fga) + 0.44 * sum(fta), 0)::numeric, 2)        AS pts_per_shot
FROM player_game_logs
WHERE season_type = 'Regular Season'
GROUP BY player_id, season;
CREATE UNIQUE INDEX IF NOT EXISTS idx_player_advanced ON player_advanced (player_id, season);

-- Season-over-season improvement, precomputed so "most improved / breakout /
-- declined" is a trivial ORDER BY a delta — not a fragile two-season self-join
-- the model writes by hand (which errored and fell back unguarded). A plain
-- VIEW over the small player_advanced table (self-join on the prior season, so
-- it stays fresh when player_advanced is refreshed; no game-log scan). The
-- prior-season key is derived from the season string: '2025-26' -> '2024-25'.
-- Floored at gp >= 20 in BOTH seasons so noise jumps (a 5-game sample doubling)
-- can't top the board; rank by any *_delta. Positive = improved.
CREATE OR REPLACE VIEW player_improvement AS
SELECT
    a.player_id, a.player_name, a.season,
    a.gp, b.gp AS prev_gp,
    a.ts_pct,    b.ts_pct    AS prev_ts_pct,    round(a.ts_pct    - b.ts_pct,    3) AS ts_pct_delta,
    a.efg_pct,   b.efg_pct   AS prev_efg_pct,   round(a.efg_pct   - b.efg_pct,   3) AS efg_pct_delta,
    a.pts_per36, b.pts_per36 AS prev_pts_per36, round(a.pts_per36 - b.pts_per36, 1) AS pts_per36_delta,
    a.reb_per36, b.reb_per36 AS prev_reb_per36, round(a.reb_per36 - b.reb_per36, 1) AS reb_per36_delta,
    a.ast_per36, b.ast_per36 AS prev_ast_per36, round(a.ast_per36 - b.ast_per36, 1) AS ast_per36_delta
FROM player_advanced a
JOIN player_advanced b
  ON b.player_id = a.player_id
 AND b.season = (left(a.season, 4)::int - 1)::text || '-'
              || lpad((left(a.season, 4)::int % 100)::text, 2, '0')
WHERE a.gp >= 20 AND b.gp >= 20;

-- Advanced per-team-season rollup: pace-adjusted ratings + four factors (and the
-- "allowed" mirror), via the Oliver possession estimate. Pace-fair, so it answers
-- "is X more offense or defense", "fastest team", and opponent-strength tiers far
-- better than raw per-game points. Also MATERIALIZED (one scan at build).
DROP MATERIALIZED VIEW IF EXISTS team_advanced;
CREATE MATERIALIZED VIEW team_advanced AS
WITH g AS (
    SELECT tm.abbreviation AS team, t.season,
           (t.wl = 'W')::int AS win,
           t.pts, opp.pts AS opp_pts,
           t.fga, t.fta, t.tov, t.oreb, t.fgm, t.fg3m,
           opp.fga AS o_fga, opp.fta AS o_fta, opp.tov AS o_tov,
           opp.oreb AS o_oreb, opp.dreb AS o_dreb, t.dreb AS dreb
    FROM team_game_logs t
    JOIN teams tm ON tm.team_id = t.team_id
    LEFT JOIN team_game_logs opp
           ON opp.game_id = t.game_id AND opp.team_id <> t.team_id
    WHERE t.season_type = 'Regular Season'
)
SELECT
    team, season, count(*) AS gp, sum(win) AS wins,
    round(100 * sum(pts)     / nullif(sum(fga + 0.44 * fta + tov - oreb), 0)::numeric, 1)         AS off_rtg,
    round(100 * sum(opp_pts) / nullif(sum(o_fga + 0.44 * o_fta + o_tov - o_oreb), 0)::numeric, 1) AS def_rtg,
    round(100 * sum(pts)     / nullif(sum(fga + 0.44 * fta + tov - oreb), 0)::numeric
        - 100 * sum(opp_pts) / nullif(sum(o_fga + 0.44 * o_fta + o_tov - o_oreb), 0)::numeric, 1) AS net_rtg,
    round(sum(fga + 0.44 * fta + tov - oreb)::numeric / nullif(count(*), 0), 1)                   AS pace,   -- poss / game
    round((sum(fgm) + 0.5 * sum(fg3m)) / nullif(sum(fga), 0)::numeric, 3)                         AS efg_pct,
    round(sum(tov)  / nullif(sum(fga + 0.44 * fta + tov - oreb), 0)::numeric, 3)                  AS tov_rate,
    round(sum(oreb)::numeric / nullif(sum(oreb + o_dreb), 0), 3)                                  AS oreb_rate,
    round(sum(fta)::numeric  / nullif(sum(fga), 0), 3)                                            AS ft_rate
FROM g
GROUP BY team, season;
CREATE UNIQUE INDEX IF NOT EXISTS idx_team_advanced ON team_advanced (team, season);

-- Derived (free) team-season summary: offense, defense, net, record. Lets the
-- model rank teams and join opponent-strength tiers ("vs a top-10 defense").
CREATE OR REPLACE VIEW v_team_season AS
SELECT
    tm.abbreviation                                   AS team,
    t.season,
    count(*)                                          AS gp,
    sum((t.wl = 'W')::int)                            AS wins,
    sum((t.wl = 'L')::int)                            AS losses,
    round(avg(t.pts), 1)                              AS pts_pg,        -- offense
    round(avg(opp.pts), 1)                            AS opp_pts_pg,    -- defense
    round(avg(t.pts) - avg(opp.pts), 1)               AS net_pg,
    round(avg(t.fg3a), 1)                             AS fg3a_pg,
    round(sum(t.fg3m)::numeric / nullif(sum(t.fg3a), 0), 3) AS fg3_pct
FROM team_game_logs t
JOIN teams tm ON tm.team_id = t.team_id
LEFT JOIN team_game_logs opp
       ON opp.game_id = t.game_id AND opp.team_id <> t.team_id
WHERE t.season_type = 'Regular Season'
GROUP BY tm.abbreviation, t.season;
