-- ==========================================================================
-- LeAudit Platform RBAC / User Schema Patch
-- 目标：在当前 leaudit_platform 新库中补齐用户、角色、权限、路由核心表
-- 设计原则：
--   1. 兼容老系统 docauditai 的核心字段语义
--   2. 新系统只做单地区隔离，地区字段统一使用 sso_users.area
--   3. 采用 bigint 主键，兼容新库现有 leaudit_* bigint 引用字段
--   4. 当前库里尚不存在 sso_users / roles / permissions / role_permissions / sys_routes / role_route / user_role
-- ============================================================================

BEGIN;

-- --------------------------------------------------------------------------
-- 1. 用户主表 sso_users
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sso_users (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    sub VARCHAR(128) NOT NULL,
    username VARCHAR(128) NOT NULL,
    nick_name VARCHAR(128) NOT NULL,
    phone_number VARCHAR(64),
    email VARCHAR(256),
    ou_id VARCHAR(128) NOT NULL,
    ou_name VARCHAR(255) NOT NULL,
    status SMALLINT NOT NULL DEFAULT 0,
    is_leader BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP WITHOUT TIME ZONE,
    password VARCHAR(255),
    try_count INTEGER,
    try_login_time TIMESTAMP WITHOUT TIME ZONE,
    area VARCHAR(64),
    mq_person_uuid VARCHAR(64),
    mq_account_uuid VARCHAR(64),
    mq_synced_at TIMESTAMP WITHOUT TIME ZONE,
    tenant_name VARCHAR(255),
    dep_short_name VARCHAR(255),
    dep_name VARCHAR(255),
    CONSTRAINT sso_users_sub_key UNIQUE (sub)
);

CREATE INDEX IF NOT EXISTS idx_sso_users_sub ON sso_users(sub);
CREATE UNIQUE INDEX IF NOT EXISTS idx_sso_users_sub_unique ON sso_users(sub);
CREATE INDEX IF NOT EXISTS idx_sso_users_username ON sso_users(username);
CREATE INDEX IF NOT EXISTS idx_sso_users_area ON sso_users(area) WHERE area IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_sso_users_status ON sso_users(status);
CREATE INDEX IF NOT EXISTS idx_sso_users_deleted_at ON sso_users(deleted_at);
CREATE INDEX IF NOT EXISTS idx_sso_users_ou_id ON sso_users(ou_id);
CREATE INDEX IF NOT EXISTS idx_sso_users_is_leader ON sso_users(is_leader);
CREATE INDEX IF NOT EXISTS idx_sso_users_mq_person ON sso_users(mq_person_uuid);
CREATE INDEX IF NOT EXISTS idx_sso_users_mq_account ON sso_users(mq_account_uuid);
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_tenant
    ON sso_users(status, deleted_at, area, tenant_name);
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_dep_expr
    ON sso_users(status, deleted_at, area, (COALESCE(dep_name, dep_short_name, '')));
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_ou_name
    ON sso_users(status, deleted_at, area, ou_name);
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_ou_id
    ON sso_users(status, deleted_at, area, ou_id);
CREATE INDEX IF NOT EXISTS idx_sso_users_active_tenant_dep_expr
    ON sso_users(status, deleted_at, tenant_name, (COALESCE(dep_name, dep_short_name, '')));
CREATE INDEX IF NOT EXISTS idx_sso_users_active_tenant_dep_ou_name
    ON sso_users(status, deleted_at, tenant_name, (COALESCE(dep_name, dep_short_name, '')), ou_name);

COMMENT ON TABLE sso_users IS '用户主表：认证身份、组织信息、地区隔离基础字段统一沉淀在这里';
COMMENT ON COLUMN sso_users.sub IS '统一身份唯一标识，OAuth / SSO 主键';
COMMENT ON COLUMN sso_users.username IS '登录名/工号/展示账号';
COMMENT ON COLUMN sso_users.nick_name IS '用户真实姓名';
COMMENT ON COLUMN sso_users.area IS '用户主地区，当前系统唯一数据隔离字段';
COMMENT ON COLUMN sso_users.password IS '密码字段：当前阶段兼容旧值，后续应迁移为哈希';
COMMENT ON COLUMN sso_users.status IS '账户状态：0=正常，1=禁用';
COMMENT ON COLUMN sso_users.deleted_at IS '软删除时间';

-- --------------------------------------------------------------------------
-- 2. 角色表 roles
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS roles (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    role_key VARCHAR(64) NOT NULL,
    role_name VARCHAR(128) NOT NULL,
    data_scope VARCHAR(16) DEFAULT 'SELF',
    description VARCHAR(255) DEFAULT '',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    parent_role_id BIGINT,
    priority INTEGER DEFAULT 0,
    is_system_role BOOLEAN DEFAULT FALSE,
    permissions_cache JSONB,
    metadata JSONB,
    CONSTRAINT roles_role_key_key UNIQUE (role_key),
    CONSTRAINT fk_parent_role FOREIGN KEY (parent_role_id) REFERENCES roles(id) ON DELETE SET NULL,
    CONSTRAINT chk_roles_data_scope CHECK (data_scope IN ('ALL', 'DEPT', 'SELF', 'GROUP'))
);

CREATE INDEX IF NOT EXISTS idx_roles_parent_role_id ON roles(parent_role_id);
CREATE INDEX IF NOT EXISTS idx_roles_priority ON roles(priority DESC);
CREATE INDEX IF NOT EXISTS idx_roles_permissions_cache ON roles USING GIN (permissions_cache);

COMMENT ON TABLE roles IS '角色表：当前主业务角色只使用 provincial_admin/admin/common，可选 super_admin';
COMMENT ON COLUMN roles.role_key IS '角色机器标识，例如 provincial_admin/admin/common';
COMMENT ON COLUMN roles.role_name IS '角色展示名称';
COMMENT ON COLUMN roles.data_scope IS '默认数据范围：ALL/DEPT/SELF；GROUP 仅保留兼容';
COMMENT ON COLUMN roles.priority IS '角色优先级，数值越大优先级越高';

-- --------------------------------------------------------------------------
-- 3. 菜单/路由表 sys_routes
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_routes (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    route_path VARCHAR(255) NOT NULL,
    route_name VARCHAR(128) NOT NULL,
    component VARCHAR(255),
    parent_id BIGINT,
    route_title VARCHAR(255),
    icon VARCHAR(128),
    sort_order INTEGER DEFAULT 0,
    is_hidden BOOLEAN DEFAULT FALSE,
    is_cache BOOLEAN DEFAULT TRUE,
    meta JSONB,
    status INTEGER DEFAULT 0,
    created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP WITHOUT TIME ZONE,
    CONSTRAINT fk_parent_route FOREIGN KEY (parent_id) REFERENCES sys_routes(id) ON DELETE SET NULL
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_routes_path ON sys_routes(route_path) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_routes_parent_id ON sys_routes(parent_id);
CREATE INDEX IF NOT EXISTS idx_routes_status ON sys_routes(status);

COMMENT ON TABLE sys_routes IS '前端菜单/页面路由表：用于控制角色可见菜单，不替代 API 权限表';
COMMENT ON COLUMN sys_routes.route_path IS '前端路由路径';
COMMENT ON COLUMN sys_routes.status IS '状态：0=启用，1=禁用';

-- --------------------------------------------------------------------------
-- 4. 角色路由关系表 role_route
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS role_route (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    role_id BIGINT NOT NULL,
    route_id BIGINT NOT NULL,
    permission VARCHAR(8) DEFAULT 'RW',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    status SMALLINT NOT NULL DEFAULT 1,
    CONSTRAINT role_route_role_id_route_id_key UNIQUE (role_id, route_id),
    CONSTRAINT fk_role_route_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    CONSTRAINT fk_role_route_route FOREIGN KEY (route_id) REFERENCES sys_routes(id) ON DELETE CASCADE,
    CONSTRAINT chk_role_route_status CHECK (status IN (0, 1)),
    CONSTRAINT chk_role_route_permission CHECK (permission IN ('R', 'W', 'RW'))
);

CREATE INDEX IF NOT EXISTS idx_role_route_status ON role_route(status);
CREATE INDEX IF NOT EXISTS idx_role_route_role_status ON role_route(role_id, status);

COMMENT ON TABLE role_route IS '角色与菜单路由关联表';
COMMENT ON COLUMN role_route.permission IS '路由权限类型：R=读，W=写，RW=读写';

-- --------------------------------------------------------------------------
-- 5. 权限点表 permissions
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS permissions (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    permission_key VARCHAR(100) NOT NULL,
    module VARCHAR(50) NOT NULL,
    resource VARCHAR(50) NOT NULL,
    action VARCHAR(50) NOT NULL,
    description TEXT,
    display_name VARCHAR(200),
    permission_type VARCHAR(20) NOT NULL DEFAULT 'API',
    is_system BOOLEAN NOT NULL DEFAULT FALSE,
    metadata JSONB,
    created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    created_by BIGINT,
    updated_by BIGINT,
    parent_id BIGINT,
    sort_order INTEGER DEFAULT 0,
    route_id BIGINT,
    api_path VARCHAR(255),
    api_method VARCHAR(16),
    related_routes BIGINT[],
    CONSTRAINT permissions_permission_key_key UNIQUE (permission_key),
    CONSTRAINT permissions_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES permissions(id) ON DELETE SET NULL,
    CONSTRAINT permissions_route_id_fkey FOREIGN KEY (route_id) REFERENCES sys_routes(id) ON DELETE SET NULL,
    CONSTRAINT permissions_key_format_check CHECK (permission_key ~ '^[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$')
);

CREATE INDEX IF NOT EXISTS idx_permissions_module ON permissions(module);
CREATE INDEX IF NOT EXISTS idx_permissions_parent_id ON permissions(parent_id);
CREATE INDEX IF NOT EXISTS idx_permissions_route_id ON permissions(route_id);
CREATE INDEX IF NOT EXISTS idx_permissions_sort_order ON permissions(sort_order);
CREATE INDEX IF NOT EXISTS idx_permissions_system ON permissions(is_system);
CREATE INDEX IF NOT EXISTS idx_permissions_type ON permissions(permission_type);
CREATE INDEX IF NOT EXISTS idx_permissions_metadata ON permissions USING GIN (metadata);

COMMENT ON TABLE permissions IS '权限点定义表：统一使用 module:resource:action 风格 permission_key';
COMMENT ON COLUMN permissions.permission_key IS '权限键，例如 documents:list:read、audit:run:execute';
COMMENT ON COLUMN permissions.related_routes IS '共享权限可关联多个路由 ID';

-- --------------------------------------------------------------------------
-- 6. 角色权限关系表 role_permissions
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS role_permissions (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    grant_type VARCHAR(10) NOT NULL DEFAULT 'GRANT',
    data_scope VARCHAR(20),
    condition_filter JSONB,
    metadata JSONB,
    created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    created_by BIGINT,
    updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_by BIGINT,
    CONSTRAINT unique_role_permission UNIQUE (role_id, permission_id),
    CONSTRAINT role_permissions_role_id_fkey FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    CONSTRAINT role_permissions_permission_id_fkey FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
    CONSTRAINT chk_role_permissions_grant_type CHECK (grant_type IN ('GRANT', 'DENY')),
    CONSTRAINT chk_role_permissions_data_scope CHECK (data_scope IS NULL OR data_scope IN ('ALL', 'DEPT', 'SELF', 'GROUP'))
);

CREATE INDEX IF NOT EXISTS idx_role_permissions_role_id ON role_permissions(role_id);
CREATE INDEX IF NOT EXISTS idx_role_permissions_permission_id ON role_permissions(permission_id);
CREATE INDEX IF NOT EXISTS idx_role_permissions_grant_type ON role_permissions(grant_type);
CREATE INDEX IF NOT EXISTS idx_role_permissions_data_scope ON role_permissions(data_scope);
CREATE INDEX IF NOT EXISTS idx_role_permissions_lookup ON role_permissions(role_id, grant_type);
CREATE INDEX IF NOT EXISTS idx_role_permissions_condition ON role_permissions USING GIN (condition_filter);

COMMENT ON TABLE role_permissions IS '角色权限关联表：grant_type 兼容旧系统 DENY 语义，当前新系统默认只配置 GRANT';
COMMENT ON COLUMN role_permissions.data_scope IS '数据范围：ALL/DEPT/SELF，GROUP 仅保留兼容';

-- --------------------------------------------------------------------------
-- 7. 用户角色关系表 user_role
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS user_role (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    CONSTRAINT user_role_user_id_role_id_key UNIQUE (user_id, role_id),
    CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sso_users(id) ON DELETE CASCADE,
    CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_user_role_user_role ON user_role(user_id, role_id);

COMMENT ON TABLE user_role IS '用户与角色关联表；一个用户允许挂多个角色';

-- --------------------------------------------------------------------------
-- 8. 给现有 leaudit_* 业务表补用户外键
-- 当前库中这些字段均为 bigint，且当前全为空，可安全补充外键。
-- --------------------------------------------------------------------------
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_document_files_created_by'
    ) THEN
        ALTER TABLE leaudit_document_files
            ADD CONSTRAINT fk_leaudit_document_files_created_by
            FOREIGN KEY (created_by) REFERENCES sso_users(id) ON DELETE SET NULL;
    END IF;

    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_audit_runs_trigger_user_id'
    ) THEN
        ALTER TABLE leaudit_audit_runs
            ADD CONSTRAINT fk_leaudit_audit_runs_trigger_user_id
            FOREIGN KEY (trigger_user_id) REFERENCES sso_users(id) ON DELETE SET NULL;
    END IF;

    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_rule_sets_owner_user_id'
    ) THEN
        ALTER TABLE leaudit_rule_sets
            ADD CONSTRAINT fk_leaudit_rule_sets_owner_user_id
            FOREIGN KEY (owner_user_id) REFERENCES sso_users(id) ON DELETE SET NULL;
    END IF;

    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_rule_versions_editor_user_id'
    ) THEN
        ALTER TABLE leaudit_rule_versions
            ADD CONSTRAINT fk_leaudit_rule_versions_editor_user_id
            FOREIGN KEY (editor_user_id) REFERENCES sso_users(id) ON DELETE SET NULL;
    END IF;

    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_rule_versions_publisher_user_id'
    ) THEN
        ALTER TABLE leaudit_rule_versions
            ADD CONSTRAINT fk_leaudit_rule_versions_publisher_user_id
            FOREIGN KEY (publisher_user_id) REFERENCES sso_users(id) ON DELETE SET NULL;
    END IF;
END $$;

COMMIT;
