-- +goose Up
-- Stage 3 game domain: the per-game lifecycle, the dictionary-independent move
-- journal, word-check complaints and per-account statistics, plus two account
-- columns the game domain needs.
SET search_path = backend, pg_catalog;

-- Extend accounts with the per-user away window (one interval per day, in the
-- account's local time_zone) honoured by the turn-timeout sweeper -- and, in a
-- later stage, by the robot's sleep -- and a hint wallet (purchasable hints; the
-- purchase flow lands later, so the balance defaults to empty). Profile editing
-- of the away window arrives with the profile surface (Stage 4).
ALTER TABLE accounts
    ADD COLUMN away_start   time    NOT NULL DEFAULT '00:00',
    ADD COLUMN away_end     time    NOT NULL DEFAULT '07:00',
    ADD COLUMN hint_balance integer NOT NULL DEFAULT 0,
    ADD CONSTRAINT accounts_hint_balance_chk CHECK (hint_balance >= 0);

-- One match. The live position is event-sourced: this row carries the pinned
-- dictionary, the bag seed and the denormalised turn cursor the sweeper needs,
-- while game_moves is the append-only journal the in-memory engine.Game is
-- replayed from (docs/ARCHITECTURE.md §9). turn_timeout_secs is the per-game move
-- clock; its allowed values are enforced in Go. variant uses engine.Variant's
-- stable labels.
CREATE TABLE games (
    game_id           uuid        PRIMARY KEY,
    variant           text        NOT NULL,
    dict_version      text        NOT NULL,
    seed              bigint      NOT NULL,
    status            text        NOT NULL DEFAULT 'active',
    players           smallint    NOT NULL,
    to_move           smallint    NOT NULL DEFAULT 0,
    turn_started_at   timestamptz NOT NULL DEFAULT now(),
    turn_timeout_secs integer     NOT NULL,
    hints_allowed     boolean     NOT NULL DEFAULT true,
    hints_per_player  smallint    NOT NULL DEFAULT 1,
    move_count        integer     NOT NULL DEFAULT 0,
    end_reason        text,
    created_at        timestamptz NOT NULL DEFAULT now(),
    updated_at        timestamptz NOT NULL DEFAULT now(),
    finished_at       timestamptz,
    CONSTRAINT games_variant_chk CHECK (variant IN ('english', 'russian_scrabble', 'erudit')),
    CONSTRAINT games_status_chk CHECK (status IN ('active', 'finished')),
    CONSTRAINT games_players_chk CHECK (players BETWEEN 2 AND 4),
    CONSTRAINT games_to_move_chk CHECK (to_move >= 0 AND to_move < players),
    CONSTRAINT games_turn_timeout_chk CHECK (turn_timeout_secs > 0),
    CONSTRAINT games_hints_per_player_chk CHECK (hints_per_player >= 0),
    CONSTRAINT games_end_reason_chk CHECK (
        end_reason IS NULL OR end_reason IN ('out_of_tiles', 'scoreless', 'resign', 'timeout')
    )
);
-- The sweeper scans active games oldest-turn-first; a partial index keeps it
-- off the finished archive.
CREATE INDEX games_active_idx ON games (turn_started_at) WHERE status = 'active';

-- Seats in turn order (seat 0 moves first), one row per player. account_id is a
-- durable account (guests and robots are revisited when they arrive). score is
-- the running/final score, is_winner is stamped on finish (false for every seat
-- on a draw), hints_used counts the per-game allowance consumed before the
-- profile wallet.
CREATE TABLE game_players (
    game_id    uuid     NOT NULL REFERENCES games (game_id) ON DELETE CASCADE,
    seat       smallint NOT NULL,
    account_id uuid     NOT NULL REFERENCES accounts (account_id),
    score      integer  NOT NULL DEFAULT 0,
    hints_used smallint NOT NULL DEFAULT 0,
    is_winner  boolean  NOT NULL DEFAULT false,
    PRIMARY KEY (game_id, seat),
    CONSTRAINT game_players_account_key UNIQUE (game_id, account_id)
);
CREATE INDEX game_players_account_idx ON game_players (account_id);

-- The append-only, dictionary-independent move journal (docs/ARCHITECTURE.md
-- §9.1). seq orders the moves from 0. payload holds the decoded values needed to
-- both replay the game through the engine and emit GCG without a dictionary: the
-- acting rack, and for a play its direction, placed tiles and formed words; for
-- an exchange the swapped tiles. score / running_total / exchanged_count are
-- lifted out for cheap history rendering.
CREATE TABLE game_moves (
    game_id         uuid        NOT NULL REFERENCES games (game_id) ON DELETE CASCADE,
    seq             integer     NOT NULL,
    seat            smallint    NOT NULL,
    action          text        NOT NULL,
    score           integer     NOT NULL DEFAULT 0,
    running_total   integer     NOT NULL DEFAULT 0,
    exchanged_count smallint    NOT NULL DEFAULT 0,
    payload         text        NOT NULL DEFAULT '{}',
    created_at      timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (game_id, seq),
    CONSTRAINT game_moves_action_chk CHECK (action IN ('play', 'pass', 'exchange', 'resign', 'timeout'))
);

-- Word-check complaints captured in the context of a game's pinned dictionary.
-- The admin review queue and the resolution lifecycle land in Stage 9, which
-- owns the status state machine; Stage 3 only ever writes 'open'.
CREATE TABLE complaints (
    complaint_id   uuid        PRIMARY KEY,
    complainant_id uuid        NOT NULL REFERENCES accounts (account_id),
    game_id        uuid        NOT NULL REFERENCES games (game_id) ON DELETE CASCADE,
    variant        text        NOT NULL,
    dict_version   text        NOT NULL,
    word           text        NOT NULL,
    was_valid      boolean     NOT NULL,
    note           text        NOT NULL DEFAULT '',
    status         text        NOT NULL DEFAULT 'open',
    created_at     timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX complaints_status_idx ON complaints (status);

-- Per-account lifetime statistics, recomputed incrementally on each game finish.
-- Guests have no durable account and never appear here. A draw increments draws
-- only (neither wins nor losses). max_word_points is the best single move score
-- (which already folds in every word the move formed and the all-tiles bonus).
CREATE TABLE account_stats (
    account_id      uuid        PRIMARY KEY REFERENCES accounts (account_id) ON DELETE CASCADE,
    wins            integer     NOT NULL DEFAULT 0,
    losses          integer     NOT NULL DEFAULT 0,
    draws           integer     NOT NULL DEFAULT 0,
    max_game_points integer     NOT NULL DEFAULT 0,
    max_word_points integer     NOT NULL DEFAULT 0,
    updated_at      timestamptz NOT NULL DEFAULT now()
);

-- +goose Down
DROP TABLE account_stats;
DROP TABLE complaints;
DROP TABLE game_moves;
DROP TABLE game_players;
DROP TABLE games;
ALTER TABLE accounts
    DROP CONSTRAINT accounts_hint_balance_chk,
    DROP COLUMN hint_balance,
    DROP COLUMN away_end,
    DROP COLUMN away_start;
