-- =============================================================================
-- bibimbap local-dev schema bootstrap
-- =============================================================================
-- 출처/권위 수준 (반드시 읽을 것):
--   * recruit_posts          : 권위 DDL. docs/recruit-posts-ddl.sql 와 동일 스펙.
--   * user_auth_identities   : security-hardening 의 active-unique index 포함(docs/security-hardening-ddl.sql).
--   * users / games /
--     game_comments / game_likes : **비권위 복원본**. 운영 DB 의 pg_dump 가 아니라
--       MyBatis 매퍼(@Insert/@Select)의 컬럼 목록 + data POJO 의 Java 타입에서 역추출.
--       컬럼 "이름/유무" 는 매퍼와 일치(확인됨)하나, **타입·길이·기본값·제약은 추론값**이다.
--       실제 운영 스키마와 대조(pg_dump) 전까지 권위 자료로 신뢰하지 말 것.
--
-- 타입 매핑 근거: Long→bigint, Integer→integer, Boolean→boolean,
--                 String→varchar(길이추정)/text, OffsetDateTime→timestamptz.
--
-- 적용 범위: 이 스크립트는 dev 스키마를 채운다(기본 프로필). live 스키마는 빈 상태로
--   생성만 한다. live 도 채우려면 아래 'SET search_path TO live;' 로 바꿔 동일 블록을 재실행.
--   PostgreSQL 컨테이너의 docker-entrypoint-initdb.d 에서 최초 1회 자동 실행된다.
-- =============================================================================

CREATE SCHEMA IF NOT EXISTS dev;
CREATE SCHEMA IF NOT EXISTS live;

SET search_path TO dev;

-- ---------------------------------------------------------------------------
-- users  (비권위 복원본)
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS "users_id_seq";
CREATE TABLE IF NOT EXISTS "users" (
    "id"              bigint DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
    "display_name"    character varying(200),
    "canonical_email" character varying(320),
    "avatar_url"      character varying(500),
    "role"            character varying(30) DEFAULT 'USER' NOT NULL,
    "status"          character varying(30),
    "last_login_at"   timestamp with time zone,
    "created_at"      timestamp with time zone DEFAULT now() NOT NULL,
    "updated_at"      timestamp with time zone DEFAULT now() NOT NULL,
    "is_delete"       boolean DEFAULT false NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "users_id_seq" OWNED BY "users"."id";

-- W1 RBAC: users.role 값집합 확장(CHECK) + permissions_epoch (권위: docs/rbac-ddl.sql)
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_role_check') THEN
        ALTER TABLE "users"
            ADD CONSTRAINT "users_role_check"
            CHECK ("role" IN ('ADMIN', 'SUBADMIN', 'USER'));
    END IF;
END
$$;
ALTER TABLE "users"
    ADD COLUMN IF NOT EXISTS "permissions_epoch" bigint DEFAULT 0 NOT NULL;

-- ---------------------------------------------------------------------------
-- user_auth_identities  (비권위 복원본 + security-hardening active-unique index)
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS "user_auth_identities_id_seq";
CREATE TABLE IF NOT EXISTS "user_auth_identities" (
    "id"               bigint DEFAULT nextval('user_auth_identities_id_seq'::regclass) NOT NULL,
    "user_id"          bigint NOT NULL REFERENCES "users" ("id"),
    "provider"         character varying(30) NOT NULL,
    "provider_user_id" character varying(200) NOT NULL,
    "email"            character varying(320),
    "password_hash"    character varying(500),
    "display_name"     character varying(200),
    "avatar_url"       character varying(500),
    "last_login_at"    timestamp with time zone,
    "created_at"       timestamp with time zone DEFAULT now() NOT NULL,
    "updated_at"       timestamp with time zone DEFAULT now() NOT NULL,
    "is_delete"        boolean DEFAULT false NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "user_auth_identities_id_seq" OWNED BY "user_auth_identities"."id";

CREATE UNIQUE INDEX IF NOT EXISTS "ux_user_auth_identities_provider_user_id_active"
    ON "user_auth_identities" ("provider", "provider_user_id")
    WHERE "is_delete" IS NOT TRUE;

-- ---------------------------------------------------------------------------
-- games  (비권위 복원본)
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS "games_id_seq";
CREATE TABLE IF NOT EXISTS "games" (
    "id"            bigint DEFAULT nextval('games_id_seq'::regclass) NOT NULL,
    "user_id"       bigint NOT NULL REFERENCES "users" ("id"),
    "name"          character varying(200) NOT NULL,
    "creator_note"  text,
    "git_url"       character varying(500),
    "webgl_path"    character varying(500),
    "thumbnail_url" character varying(500),
    "like_count"    integer DEFAULT 0 NOT NULL,
    "is_visible"    boolean DEFAULT true NOT NULL,
    "sort_order"    integer DEFAULT 0 NOT NULL,
    "created_at"    timestamp with time zone DEFAULT now() NOT NULL,
    "updated_at"    timestamp with time zone DEFAULT now() NOT NULL,
    "is_delete"     boolean DEFAULT false NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "games_id_seq" OWNED BY "games"."id";

CREATE INDEX IF NOT EXISTS "idx_games_visible_order"
    ON "games" ("is_visible", "is_delete", "sort_order", "created_at" DESC, "id" DESC);

-- ---------------------------------------------------------------------------
-- game_comments  (비권위 복원본 — 매퍼 존재하나 컨트롤러 미연결)
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS "game_comments_id_seq";
CREATE TABLE IF NOT EXISTS "game_comments" (
    "id"         bigint DEFAULT nextval('game_comments_id_seq'::regclass) NOT NULL,
    "game_id"    bigint NOT NULL REFERENCES "games" ("id"),
    "nickname"   character varying(100),
    "content"    text,
    "created_at" timestamp with time zone DEFAULT now() NOT NULL,
    "deleted_at" timestamp with time zone,
    "is_delete"  boolean DEFAULT false NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "game_comments_id_seq" OWNED BY "game_comments"."id";

-- game_comments user_id 컬럼 (W3-2: 작성자 귀속, nullable 비파괴)
ALTER TABLE "game_comments" ADD COLUMN IF NOT EXISTS "user_id" bigint REFERENCES "users" ("id");

-- ---------------------------------------------------------------------------
-- game_reviews  (권위 DDL — docs/game-reviews-ddl.sql 와 동일. W3-2 신규)
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS "game_reviews_id_seq";
CREATE TABLE IF NOT EXISTS "game_reviews" (
    "id"         bigint DEFAULT nextval('game_reviews_id_seq'::regclass) NOT NULL,
    "game_id"    bigint NOT NULL REFERENCES "games" ("id"),
    "user_id"    bigint NOT NULL REFERENCES "users" ("id"),
    "rating"     smallint NOT NULL,
    "body"       text,
    "created_at" timestamp with time zone DEFAULT now() NOT NULL,
    "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
    "deleted_at" timestamp with time zone,
    "is_delete"  boolean DEFAULT false NOT NULL,
    PRIMARY KEY ("id"),
    CONSTRAINT "game_reviews_rating_check" CHECK ("rating" BETWEEN 1 AND 5)
);
ALTER SEQUENCE "game_reviews_id_seq" OWNED BY "game_reviews"."id";
CREATE UNIQUE INDEX IF NOT EXISTS "ux_game_reviews_game_user_active"
    ON "game_reviews" ("game_id", "user_id") WHERE "is_delete" IS NOT TRUE;
CREATE INDEX IF NOT EXISTS "idx_game_reviews_game"
    ON "game_reviews" ("game_id") WHERE "is_delete" = false;
-- C3 재분류: 다축(육각형) game_review_axes + 집계뷰 game_review_stats 는 아래에 W3-2 일반 DDL 로 신설(roadmap.md:63,201,202, W2-3 동결 무관).

-- ---------------------------------------------------------------------------
-- game_review_axes / is_rating_manual / updated_at / game_review_stats (W3-2 고도화, docs/game-reviews-ddl.sql 와 동일)

-- 1) game_comments.updated_at (A3)
ALTER TABLE "game_comments"
    ADD COLUMN IF NOT EXISTS "updated_at" timestamp with time zone DEFAULT now() NOT NULL;
-- 기존 댓글이 '수정됨' 오표시되지 않도록 정렬(멱등: 이미 정렬된 행엔 무영향)
UPDATE "game_comments" SET "updated_at" = "created_at" WHERE "updated_at" > "created_at";
COMMENT ON COLUMN "game_comments"."updated_at" IS '덧글 마지막 수정 시각. updated_at > created_at 이면 수정됨(리뷰 대칭)';

-- 2) game_reviews.is_rating_manual (overall 출처 구분)
ALTER TABLE "game_reviews"
    ADD COLUMN IF NOT EXISTS "is_rating_manual" boolean DEFAULT false NOT NULL;
COMMENT ON COLUMN "game_reviews"."is_rating_manual" IS 'true=유저 직접선택 overall, false=6축 자동평균';

-- 3) game_review_axes (다축 평점, 리뷰당 6행)
CREATE SEQUENCE IF NOT EXISTS "game_review_axes_id_seq";
CREATE TABLE IF NOT EXISTS "game_review_axes" (
    "id"        bigint DEFAULT nextval('game_review_axes_id_seq'::regclass) NOT NULL,
    "review_id" bigint NOT NULL,
    "axis_key"  character varying(20) NOT NULL,
    "score"     smallint NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "game_review_axes_id_seq" OWNED BY "game_review_axes"."id";

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'game_review_axes_review_id_fkey') THEN
        ALTER TABLE "game_review_axes"
            ADD CONSTRAINT "game_review_axes_review_id_fkey"
            FOREIGN KEY ("review_id") REFERENCES "game_reviews" ("id");
    END IF;
END
$$;

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'game_review_axes_score_check') THEN
        ALTER TABLE "game_review_axes"
            ADD CONSTRAINT "game_review_axes_score_check" CHECK ("score" BETWEEN 1 AND 5);
    END IF;
END
$$;

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'game_review_axes_axis_key_check') THEN
        ALTER TABLE "game_review_axes"
            ADD CONSTRAINT "game_review_axes_axis_key_check"
            CHECK ("axis_key" IN ('immersion','creativity','controls','completeness','sound','visual'));
    END IF;
END
$$;

CREATE UNIQUE INDEX IF NOT EXISTS "ux_game_review_axes_review_axis"
    ON "game_review_axes" ("review_id", "axis_key");
CREATE INDEX IF NOT EXISTS "idx_game_review_axes_review"
    ON "game_review_axes" ("review_id");

COMMENT ON TABLE "game_review_axes" IS '리뷰 다축 평점(6축, 리뷰당 6행). axis_key 6종 각 1~5';
COMMENT ON COLUMN "game_review_axes"."axis_key" IS '몰입성 immersion/창의성 creativity/조작성 controls/완성도 completeness/사운드 sound/비주얼 visual';

-- 4) game_review_stats (읽기전용 집계뷰 — 클라 평균계산 폐기 공급원)
-- fan-out 방지: game_review_axes 직접 JOIN 시 리뷰가 axes 행수만큼 복제되어
-- COUNT(*)/AVG(rating) 왜곡. 축 평균은 서브쿼리에서 game 단위 선집계 후 LEFT JOIN.
CREATE OR REPLACE VIEW "game_review_stats" AS
SELECT
    r."game_id"                          AS "game_id",
    ROUND(AVG(r."rating")::numeric, 1)   AS "avg_rating",
    COUNT(*)                             AS "review_count",
    MAX(ax."avg_immersion")              AS "avg_immersion",
    MAX(ax."avg_creativity")             AS "avg_creativity",
    MAX(ax."avg_controls")               AS "avg_controls",
    MAX(ax."avg_completeness")           AS "avg_completeness",
    MAX(ax."avg_sound")                  AS "avg_sound",
    MAX(ax."avg_visual")                 AS "avg_visual"
FROM "game_reviews" r
LEFT JOIN (
    SELECT
        rv."game_id"                                                       AS "game_id",
        ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='immersion'),1)    AS "avg_immersion",
        ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='creativity'),1)   AS "avg_creativity",
        ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='controls'),1)     AS "avg_controls",
        ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='completeness'),1)  AS "avg_completeness",
        ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='sound'),1)        AS "avg_sound",
        ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='visual'),1)       AS "avg_visual"
    FROM "game_reviews" rv
    JOIN "game_review_axes" a ON a."review_id" = rv."id"
    WHERE rv."is_delete" IS NOT TRUE
    GROUP BY rv."game_id"
) ax ON ax."game_id" = r."game_id"
WHERE r."is_delete" IS NOT TRUE
GROUP BY r."game_id";
COMMENT ON VIEW "game_review_stats" IS 'W3-2 일반 집계뷰(W2-3 동결 무관). 게임별 평균별점·리뷰수·6축평균';

-- ---------------------------------------------------------------------------
-- game_likes  (비권위 복원본 — 매퍼는 hard delete 사용, is_delete 컬럼 없음)
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS "game_likes_id_seq";
CREATE TABLE IF NOT EXISTS "game_likes" (
    "id"         bigint DEFAULT nextval('game_likes_id_seq'::regclass) NOT NULL,
    "game_id"    bigint NOT NULL REFERENCES "games" ("id"),
    "user_key"   character varying(200) NOT NULL,
    "created_at" timestamp with time zone DEFAULT now() NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "game_likes_id_seq" OWNED BY "game_likes"."id";

-- ---------------------------------------------------------------------------
-- recruit_posts  (권위 DDL — docs/recruit-posts-ddl.sql 와 동일)
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS "recruit_posts_id_seq";
CREATE TABLE IF NOT EXISTS "recruit_posts" (
    "id"                 bigint DEFAULT nextval('recruit_posts_id_seq'::regclass) NOT NULL,
    "user_id"            bigint NOT NULL,
    "project_name"       character varying(200) NOT NULL,
    "genre"              character varying(80),
    "summary"            character varying(200) NOT NULL,
    "role"               character varying(30) NOT NULL,
    "project_status"     character varying(50) NOT NULL,
    "participation_type" character varying(30) NOT NULL,
    "expected_period"    character varying(80),
    "team_members"       character varying(200),
    "contact"            character varying(200) NOT NULL,
    "description"        text,
    "reference_url"      character varying(500),
    "deadline_at"        timestamp with time zone,
    "is_visible"         boolean DEFAULT true NOT NULL,
    "sort_order"         integer DEFAULT 0 NOT NULL,
    "created_at"         timestamp with time zone DEFAULT now() NOT NULL,
    "updated_at"         timestamp with time zone DEFAULT now() NOT NULL,
    "deleted_at"         timestamp with time zone,
    "is_delete"          boolean DEFAULT false NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "recruit_posts_id_seq" OWNED BY "recruit_posts"."id";

ALTER TABLE "recruit_posts"
    ADD CONSTRAINT "recruit_posts_user_id_fkey"
    FOREIGN KEY ("user_id") REFERENCES "users" ("id");
ALTER TABLE "recruit_posts"
    ADD CONSTRAINT "recruit_posts_role_check"
    CHECK ("role" IN ('기획', '아트', '프로그래머'));
ALTER TABLE "recruit_posts"
    ADD CONSTRAINT "recruit_posts_participation_type_check"
    CHECK ("participation_type" IN ('취미', '수익쉐어', '유급', '게임잼'));

CREATE INDEX IF NOT EXISTS "idx_recruit_posts_visible_order"
    ON "recruit_posts" ("is_visible", "is_delete", "sort_order", "created_at" DESC, "id" DESC);
CREATE INDEX IF NOT EXISTS "idx_recruit_posts_role"
    ON "recruit_posts" ("role")
    WHERE "is_delete" = false AND "is_visible" = true;
CREATE INDEX IF NOT EXISTS "idx_recruit_posts_participation_type"
    ON "recruit_posts" ("participation_type")
    WHERE "is_delete" = false AND "is_visible" = true;

-- ---------------------------------------------------------------------------
-- W1 RBAC: permissions / user_permissions / rbac_audit_log
-- (권위 DDL — docs/rbac-ddl.sql 와 동일 스펙)
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS "permissions_id_seq";
CREATE TABLE IF NOT EXISTS "permissions" (
    "id"             bigint DEFAULT nextval('permissions_id_seq'::regclass) NOT NULL,
    "permission_key" character varying(50) NOT NULL,
    "display_name"   character varying(100) NOT NULL,
    "is_active"      boolean DEFAULT true NOT NULL,
    "created_at"     timestamp with time zone DEFAULT now() NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "permissions_id_seq" OWNED BY "permissions"."id";
CREATE UNIQUE INDEX IF NOT EXISTS "ux_permissions_key"
    ON "permissions" ("permission_key");

CREATE SEQUENCE IF NOT EXISTS "user_permissions_id_seq";
CREATE TABLE IF NOT EXISTS "user_permissions" (
    "id"             bigint DEFAULT nextval('user_permissions_id_seq'::regclass) NOT NULL,
    "user_id"        bigint NOT NULL,
    "permission_key" character varying(50) NOT NULL,
    "granted_by"     bigint,
    "created_at"     timestamp with time zone DEFAULT now() NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "user_permissions_id_seq" OWNED BY "user_permissions"."id";
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'user_permissions_user_id_fkey') THEN
        ALTER TABLE "user_permissions"
            ADD CONSTRAINT "user_permissions_user_id_fkey"
            FOREIGN KEY ("user_id") REFERENCES "users" ("id");
    END IF;
END
$$;
CREATE UNIQUE INDEX IF NOT EXISTS "ux_user_permissions_user_key"
    ON "user_permissions" ("user_id", "permission_key");
CREATE INDEX IF NOT EXISTS "idx_user_permissions_user"
    ON "user_permissions" ("user_id");

CREATE SEQUENCE IF NOT EXISTS "rbac_audit_log_id_seq";
CREATE TABLE IF NOT EXISTS "rbac_audit_log" (
    "id"            bigint DEFAULT nextval('rbac_audit_log_id_seq'::regclass) NOT NULL,
    "actor_id"      bigint NOT NULL,
    "target_id"     bigint NOT NULL,
    "action"        character varying(30) NOT NULL,
    "permission_key" character varying(50),
    "created_at"    timestamp with time zone DEFAULT now() NOT NULL,
    PRIMARY KEY ("id")
);
ALTER SEQUENCE "rbac_audit_log_id_seq" OWNED BY "rbac_audit_log"."id";
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'rbac_audit_log_action_check') THEN
        ALTER TABLE "rbac_audit_log"
            ADD CONSTRAINT "rbac_audit_log_action_check"
            CHECK ("action" IN ('APPOINT', 'DEMOTE', 'GRANT', 'REVOKE'));
    END IF;
END
$$;
