-- +goose Up
-- Initial Game Master PostgreSQL schema.
--
-- Four tables cover the durable surface of the service:
--   * runtime_records — one row per game with the latest known runtime
--     status, scheduling state, and engine health summary;
--   * engine_versions — the deployable engine version registry consumed
--     by Lobby's start flow and the GM admin/patch flow;
--   * player_mappings — the (game_id, user_id) → (race_name,
--     engine_player_uuid) projection installed at register-runtime;
--   * operation_log — append-only audit of every register-runtime,
--     turn-generation, force-next-turn, banish, stop, patch, and
--     engine-version mutation GM performed.
--
-- Schema and the matching `gamemasterservice` role are provisioned
-- outside this script (in tests via cmd/jetgen/main.go::provisionRoleAndSchema;
-- in production via an ops init script). This migration runs as the
-- schema owner with `search_path=gamemaster` and only contains DDL for
-- the service-owned tables and indexes. ARCHITECTURE.md §Database topology
-- mandates that the per-service role's grants stay restricted to its own
-- schema; consequently this file deliberately deviates from PLAN.md
-- Stage 09's literal `CREATE SCHEMA IF NOT EXISTS gamemaster;` instruction.

-- runtime_records holds one durable record per game with the latest
-- known runtime status, scheduling state, and engine health summary.
-- The status enum is enforced by a CHECK so domain code can rely on it
-- without reading every callsite. The composite (status,
-- next_generation_at) index drives the scheduler ticker scan that
-- selects `status='running' AND next_generation_at <= now()` once per
-- second. next_generation_at is nullable: a row enters with
-- status='starting' and a null tick, and only acquires a tick when the
-- register-runtime CAS flips it to 'running'.
CREATE TABLE runtime_records (
    game_id                text PRIMARY KEY,
    status                 text NOT NULL,
    engine_endpoint        text NOT NULL,
    current_image_ref      text NOT NULL,
    current_engine_version text NOT NULL,
    turn_schedule          text NOT NULL,
    current_turn           integer NOT NULL DEFAULT 0,
    next_generation_at     timestamptz,
    skip_next_tick         boolean NOT NULL DEFAULT false,
    engine_health          text NOT NULL DEFAULT '',
    created_at             timestamptz NOT NULL,
    updated_at             timestamptz NOT NULL,
    started_at             timestamptz,
    stopped_at             timestamptz,
    finished_at            timestamptz,
    CONSTRAINT runtime_records_status_chk
        CHECK (status IN (
            'starting', 'running', 'generation_in_progress',
            'generation_failed', 'stopped', 'engine_unreachable',
            'finished'
        ))
);

CREATE INDEX runtime_records_status_next_gen_idx
    ON runtime_records (status, next_generation_at);

-- engine_versions is the deployable engine version registry. Each row
-- ties a semver string to a Docker reference and a free-form options
-- document; the status enum gates the start flow (active versions are
-- accepted by Lobby's resolve, deprecated versions are rejected on new
-- starts but remain valid for already-running games). `options` is
-- jsonb: v1 stores it verbatim and never element-filters.
CREATE TABLE engine_versions (
    version    text PRIMARY KEY,
    image_ref  text NOT NULL,
    options    jsonb NOT NULL DEFAULT '{}'::jsonb,
    status     text NOT NULL,
    created_at timestamptz NOT NULL,
    updated_at timestamptz NOT NULL,
    CONSTRAINT engine_versions_status_chk
        CHECK (status IN ('active', 'deprecated'))
);

-- player_mappings carries the (game_id, user_id) → (race_name,
-- engine_player_uuid) projection installed at register-runtime. The
-- composite primary key both serves the lookups by (game_id, user_id)
-- on every command/order/report request and as a leftmost-prefix index
-- for the per-game roster reads (`WHERE game_id = $1`). The partial
-- UNIQUE index on (game_id, race_name) enforces the one-race-per-game
-- invariant at the storage boundary.
CREATE TABLE player_mappings (
    game_id            text NOT NULL,
    user_id            text NOT NULL,
    race_name          text NOT NULL,
    engine_player_uuid text NOT NULL,
    created_at         timestamptz NOT NULL,
    PRIMARY KEY (game_id, user_id)
);

CREATE UNIQUE INDEX player_mappings_game_race_uniq
    ON player_mappings (game_id, race_name);

-- operation_log is an append-only audit of every operation Game Master
-- performed against a game's runtime or against the engine version
-- registry. The (game_id, started_at DESC) index drives audit reads
-- from the GM/Admin REST surface. finished_at is nullable for in-flight
-- rows even though the service layer always finalises the row. The
-- op_kind / op_source / outcome enums are enforced by CHECK constraints
-- to keep the audit schema honest without a separate Go validator.
CREATE TABLE operation_log (
    id            bigserial PRIMARY KEY,
    game_id       text NOT NULL,
    op_kind       text NOT NULL,
    op_source     text NOT NULL,
    source_ref    text NOT NULL DEFAULT '',
    outcome       text NOT NULL,
    error_code    text NOT NULL DEFAULT '',
    error_message text NOT NULL DEFAULT '',
    started_at    timestamptz NOT NULL,
    finished_at   timestamptz,
    CONSTRAINT operation_log_op_kind_chk
        CHECK (op_kind IN (
            'register_runtime', 'turn_generation', 'force_next_turn',
            'banish', 'stop', 'patch',
            'engine_version_create', 'engine_version_update',
            'engine_version_deprecate', 'engine_version_delete'
        )),
    CONSTRAINT operation_log_op_source_chk
        CHECK (op_source IN (
            'gateway_player', 'lobby_internal', 'admin_rest'
        )),
    CONSTRAINT operation_log_outcome_chk
        CHECK (outcome IN ('success', 'failure'))
);

CREATE INDEX operation_log_game_started_idx
    ON operation_log (game_id, started_at DESC);

-- +goose Down
DROP TABLE IF EXISTS operation_log;
DROP TABLE IF EXISTS player_mappings;
DROP TABLE IF EXISTS engine_versions;
DROP TABLE IF EXISTS runtime_records;
