package com.writeoff.module.system.service;

import com.writeoff.common.api.PageResult;
import com.writeoff.common.exception.BusinessException;
import com.writeoff.module.system.dto.AssignRoleDataPermissionRequest;
import com.writeoff.module.system.dto.CreateDataPermissionPolicyRequest;
import com.writeoff.module.system.dto.UpdateDataPermissionPolicyRequest;
import com.writeoff.module.system.model.DataPermissionPolicy;
import com.writeoff.security.AuthContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
public class DataPermissionService {
    private final JdbcTemplate jdbcTemplate;

    private static final RowMapper<DataPermissionPolicy> POLICY_ROW_MAPPER = (rs, n) -> new DataPermissionPolicy(
            rs.getLong("id"),
            rs.getString("policy_name"),
            rs.getString("project_scope"),
            rs.getString("project_ids_csv"),
            rs.getString("meeting_scope"),
            rs.getString("meeting_ids_csv"),
            rs.getString("user_scope"),
            rs.getString("user_ids_csv"),
            rs.getString("expert_scope"),
            rs.getString("expert_ids_csv"),
            rs.getString("module_scope"),
            rs.getInt("export_allowed") == 1,
            rs.getString("status")
    );

    public DataPermissionService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public PageResult<DataPermissionPolicy> listPolicies() {
        List<DataPermissionPolicy> list = jdbcTemplate.query(
                "SELECT * FROM data_permission_policy WHERE tenant_id=? ORDER BY id DESC",
                POLICY_ROW_MAPPER
                ,
                tenantId()
        );
        return new PageResult<>(list, list.size(), 1, 50);
    }

    public DataPermissionPolicy createPolicy(CreateDataPermissionPolicyRequest request) {
        jdbcTemplate.update(
                "INSERT INTO data_permission_policy (tenant_id, policy_name, project_scope, project_ids_csv, meeting_scope, meeting_ids_csv, user_scope, user_ids_csv, expert_scope, expert_ids_csv, module_scope, export_allowed, status) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'ENABLED')",
                tenantId(),
                request.getPolicyName(),
                request.getProjectScope(),
                request.getProjectIdsCsv(),
                request.getMeetingScope(),
                request.getMeetingIdsCsv(),
                request.getUserScope(),
                request.getUserIdsCsv(),
                request.getExpertScope(),
                request.getExpertIdsCsv(),
                request.getModuleScope(),
                Boolean.TRUE.equals(request.getExportAllowed()) ? 1 : 0
        );
        Long id = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) FROM data_permission_policy WHERE tenant_id=?", Long.class, tenantId());
        return findById(id == null ? 0L : id);
    }

    public DataPermissionPolicy updatePolicy(Long id, UpdateDataPermissionPolicyRequest request) {
        assertPolicyExists(id);
        jdbcTemplate.update(
                "UPDATE data_permission_policy SET policy_name=?, project_scope=?, project_ids_csv=?, meeting_scope=?, meeting_ids_csv=?, user_scope=?, user_ids_csv=?, expert_scope=?, expert_ids_csv=?, module_scope=?, export_allowed=?, updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND id=?",
                request.getPolicyName(),
                request.getProjectScope(),
                request.getProjectIdsCsv(),
                request.getMeetingScope(),
                request.getMeetingIdsCsv(),
                request.getUserScope(),
                request.getUserIdsCsv(),
                request.getExpertScope(),
                request.getExpertIdsCsv(),
                request.getModuleScope(),
                Boolean.TRUE.equals(request.getExportAllowed()) ? 1 : 0,
                tenantId(),
                id
        );
        return findById(id);
    }

    public DataPermissionPolicy copyPolicy(Long id) {
        DataPermissionPolicy source = findById(id);
        String copiedName = source.getPolicyName() + "_COPY_" + System.currentTimeMillis();
        jdbcTemplate.update(
                "INSERT INTO data_permission_policy (tenant_id, policy_name, project_scope, project_ids_csv, meeting_scope, meeting_ids_csv, user_scope, user_ids_csv, expert_scope, expert_ids_csv, module_scope, export_allowed, status) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'ENABLED')",
                tenantId(),
                copiedName,
                source.getProjectScope(),
                source.getProjectIdsCsv(),
                source.getMeetingScope(),
                source.getMeetingIdsCsv(),
                source.getUserScope(),
                source.getUserIdsCsv(),
                source.getExpertScope(),
                source.getExpertIdsCsv(),
                source.getModuleScope(),
                Boolean.TRUE.equals(source.getExportAllowed()) ? 1 : 0
        );
        Long newId = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) FROM data_permission_policy WHERE tenant_id=?", Long.class, tenantId());
        return findById(newId == null ? 0L : newId);
    }

    public void enablePolicy(Long id) {
        assertPolicyExists(id);
        jdbcTemplate.update(
                "UPDATE data_permission_policy SET status='ENABLED', updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND id=?",
                tenantId(),
                id
        );
    }

    public void disablePolicy(Long id) {
        assertPolicyExists(id);
        jdbcTemplate.update(
                "UPDATE data_permission_policy SET status='DISABLED', updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND id=?",
                tenantId(),
                id
        );
    }

    public void assignRoles(Long policyId, AssignRoleDataPermissionRequest request) {
        assertPolicyExists(policyId);
        String assignMode = request.getAssignMode() == null ? "APPEND" : request.getAssignMode().trim().toUpperCase();
        if ("REPLACE".equals(assignMode)) {
            jdbcTemplate.update("DELETE FROM role_data_permission WHERE tenant_id=? AND policy_id=?", tenantId(), policyId);
        }
        List<Long> roleIds = request.getRoleIds() == null ? new ArrayList<Long>() : request.getRoleIds();
        for (Long roleId : roleIds) {
            Integer count = jdbcTemplate.queryForObject(
                    "SELECT COUNT(1) FROM role_data_permission WHERE tenant_id=? AND role_id=? AND policy_id=?",
                    Integer.class,
                    tenantId(),
                    roleId,
                    policyId
            );
            if (count != null && count > 0) {
                continue;
            }
            jdbcTemplate.update(
                    "INSERT INTO role_data_permission (tenant_id, role_id, policy_id) VALUES (?, ?, ?)",
                    tenantId(),
                    roleId,
                    policyId
            );
        }
    }

    public Map<String, Object> currentScopeSummary() {
        Long userId = AuthContext.userId();
        DataScope scope = resolveCurrentUserScope();
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("userId", userId);
        data.put("projectAll", scope.isProjectAll());
        data.put("projectIds", new ArrayList<>(scope.getProjectIds()));
        data.put("projectOwnerOnly", scope.isProjectOwnerOnly());
        data.put("meetingAll", scope.isMeetingAll());
        data.put("meetingIds", new ArrayList<>(scope.getMeetingIds()));
        data.put("meetingOwnerOnly", scope.isMeetingOwnerOnly());
        data.put("userAll", scope.isUserAll());
        data.put("userIds", new ArrayList<>(scope.getUserIds()));
        data.put("userOwnerOnly", scope.isUserOwnerOnly());
        data.put("expertAll", scope.isExpertAll());
        data.put("expertIds", new ArrayList<>(scope.getExpertIds()));
        data.put("expertOwnerOnly", scope.isExpertOwnerOnly());
        data.put("matchedPolicyIds", userId == null ? new ArrayList<Long>() : listMatchedPolicyIds(userId));
        data.put("exportAllowed", canExportCurrentUser());
        return data;
    }

    public List<Long> listPolicyRoleIds(Long policyId) {
        assertPolicyExists(policyId);
        return jdbcTemplate.queryForList(
                "SELECT role_id FROM role_data_permission WHERE tenant_id=? AND policy_id=? ORDER BY role_id ASC",
                Long.class,
                tenantId(),
                policyId
        );
    }

    public DataScope resolveCurrentUserScope() {
        Long userId = AuthContext.userId();
        if (userId == null) {
            return DataScope.allowAll();
        }
        return resolveUserScope(userId);
    }

    public DataScope resolveUserScope(Long userId) {
        List<DataPermissionPolicy> policies = jdbcTemplate.query(
                "SELECT DISTINCT p.* FROM user_role ur " +
                        "JOIN role_data_permission rdp ON ur.role_id=rdp.role_id AND ur.tenant_id=rdp.tenant_id " +
                        "JOIN data_permission_policy p ON rdp.policy_id=p.id AND p.tenant_id=rdp.tenant_id " +
                        "WHERE ur.user_id=? AND ur.tenant_id=? AND p.status='ENABLED'",
                POLICY_ROW_MAPPER,
                userId,
                tenantId()
        );
        if (policies.isEmpty()) {
            return DataScope.denyAll();
        }
        boolean projectAll = false;
        boolean meetingAll = false;
        boolean userAll = false;
        boolean expertAll = false;
        boolean projectOwnerOnly = false;
        boolean meetingOwnerOnly = false;
        boolean userOwnerOnly = false;
        boolean expertOwnerOnly = false;
        Set<Long> projectIds = new HashSet<>();
        Set<Long> meetingIds = new HashSet<>();
        Set<Long> userIds = new HashSet<>();
        Set<Long> expertIds = new HashSet<>();
        for (DataPermissionPolicy p : policies) {
            if ("ALL".equalsIgnoreCase(p.getProjectScope())) {
                projectAll = true;
            } else if ("IDS".equalsIgnoreCase(p.getProjectScope())) {
                projectIds.addAll(parseIds(p.getProjectIdsCsv()));
            } else if ("OWNER".equalsIgnoreCase(p.getProjectScope())) {
                projectOwnerOnly = true;
            }
            if ("ALL".equalsIgnoreCase(p.getMeetingScope())) {
                meetingAll = true;
            } else if ("IDS".equalsIgnoreCase(p.getMeetingScope())) {
                meetingIds.addAll(parseIds(p.getMeetingIdsCsv()));
            } else if ("OWNER".equalsIgnoreCase(p.getMeetingScope())) {
                meetingOwnerOnly = true;
            }
            if ("ALL".equalsIgnoreCase(p.getUserScope())) {
                userAll = true;
            } else if ("IDS".equalsIgnoreCase(p.getUserScope())) {
                userIds.addAll(parseIds(p.getUserIdsCsv()));
            } else if ("OWNER".equalsIgnoreCase(p.getUserScope())) {
                userOwnerOnly = true;
            }
            if ("ALL".equalsIgnoreCase(p.getExpertScope())) {
                expertAll = true;
            } else if ("IDS".equalsIgnoreCase(p.getExpertScope())) {
                expertIds.addAll(parseIds(p.getExpertIdsCsv()));
            } else if ("OWNER".equalsIgnoreCase(p.getExpertScope())) {
                expertOwnerOnly = true;
            }
        }
        DataScope baseScope = new DataScope(
                projectAll, projectIds, projectOwnerOnly,
                meetingAll, meetingIds, meetingOwnerOnly,
                userAll, userIds, userOwnerOnly,
                expertAll, expertIds, expertOwnerOnly
        );
        return applyProjectBindingScopeIfNeeded(userId, baseScope);
    }

    public boolean canAccessProject(Long projectId, DataScope scope) {
        return canAccessProject(projectId, null, scope);
    }

    public boolean canAccessProject(Long projectId, Long createdBy, DataScope scope) {
        if (scope.isProjectAll() || scope.getProjectIds().contains(projectId)) {
            return true;
        }
        return scope.isProjectOwnerOnly() && AuthContext.userId() != null && AuthContext.userId().equals(createdBy);
    }

    public boolean canAccessMeeting(Long meetingId, Long projectId, DataScope scope) {
        return canAccessMeeting(meetingId, projectId, null, null, scope);
    }

    public boolean canAccessMeeting(Long meetingId, Long projectId, Long meetingCreatedBy, Long projectCreatedBy, DataScope scope) {
        if (scope.isMeetingAll()) {
            return true;
        }
        if (scope.getMeetingIds().contains(meetingId)) {
            return true;
        }
        if (scope.isMeetingOwnerOnly() && AuthContext.userId() != null && AuthContext.userId().equals(meetingCreatedBy)) {
            return true;
        }
        if (scope.isProjectOwnerOnly()) {
            return canAccessProject(projectId, projectCreatedBy, scope);
        }
        return canAccessProject(projectId, scope);
    }

    public boolean canAccessUser(Long targetUserId, Long targetCreatedBy, DataScope scope) {
        if (scope.isUserAll() || scope.getUserIds().contains(targetUserId)) {
            return true;
        }
        return scope.isUserOwnerOnly() && AuthContext.userId() != null && AuthContext.userId().equals(targetCreatedBy);
    }

    public boolean canAccessExpert(Long expertId, Long expertCreatedBy, DataScope scope) {
        if (scope.isExpertAll() || scope.getExpertIds().contains(expertId)) {
            return true;
        }
        return scope.isExpertOwnerOnly() && AuthContext.userId() != null && AuthContext.userId().equals(expertCreatedBy);
    }

    public boolean canExportCurrentUser() {
        Long userId = AuthContext.userId();
        if (userId == null) {
            return false;
        }
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM user_role ur " +
                        "JOIN role_data_permission rdp ON ur.role_id=rdp.role_id AND ur.tenant_id=rdp.tenant_id " +
                        "JOIN data_permission_policy p ON rdp.policy_id=p.id AND p.tenant_id=rdp.tenant_id " +
                        "WHERE ur.user_id=? AND ur.tenant_id=? AND p.status='ENABLED' AND p.export_allowed=1",
                Integer.class,
                userId,
                tenantId()
        );
        return count != null && count > 0;
    }

    private List<Long> listMatchedPolicyIds(Long userId) {
        return jdbcTemplate.queryForList(
                "SELECT DISTINCT p.id FROM user_role ur " +
                        "JOIN role_data_permission rdp ON ur.role_id=rdp.role_id AND ur.tenant_id=rdp.tenant_id " +
                        "JOIN data_permission_policy p ON rdp.policy_id=p.id AND p.tenant_id=rdp.tenant_id " +
                        "WHERE ur.user_id=? AND ur.tenant_id=? AND p.status='ENABLED' ORDER BY p.id ASC",
                Long.class,
                userId,
                tenantId()
        );
    }

    private DataScope applyProjectBindingScopeIfNeeded(Long userId, DataScope baseScope) {
        Set<String> roleCodes = listUserRoleCodes(userId);
        if (roleCodes.contains("TENANT_ADMIN")) {
            return baseScope;
        }
        boolean ownerScoped = roleCodes.contains("PROJECT_OWNER");
        boolean executorScoped = roleCodes.contains("PROJECT_EXECUTOR");
        boolean legacyExecutorScoped = roleCodes.contains("EXECUTOR");
        if (!ownerScoped && !executorScoped && !legacyExecutorScoped) {
            return baseScope;
        }
        Set<Long> boundProjectIds = listBoundProjectIds(userId, ownerScoped, executorScoped, legacyExecutorScoped);
        if (boundProjectIds.isEmpty()) {
            return DataScope.denyAll();
        }
        Set<Long> effectiveProjectIds = new HashSet<>(boundProjectIds);
        if (!baseScope.isProjectAll() && !baseScope.isProjectOwnerOnly()) {
            effectiveProjectIds.retainAll(baseScope.getProjectIds());
        }
        if (effectiveProjectIds.isEmpty()) {
            return DataScope.denyAll();
        }
        return new DataScope(
                false, effectiveProjectIds, baseScope.isProjectOwnerOnly(),
                false, new HashSet<Long>(), false,
                baseScope.isUserAll(), new HashSet<Long>(baseScope.getUserIds()), baseScope.isUserOwnerOnly(),
                baseScope.isExpertAll(), new HashSet<Long>(baseScope.getExpertIds()), baseScope.isExpertOwnerOnly()
        );
    }

    public Map<Long, Long> listProjectCreators(Collection<Long> projectIds) {
        Map<Long, Long> result = new LinkedHashMap<>();
        if (projectIds == null || projectIds.isEmpty()) {
            return result;
        }
        String inSql = buildInClause(projectIds.size());
        List<Object> args = new ArrayList<>();
        args.add(tenantId());
        args.addAll(projectIds);
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT id, created_by FROM project WHERE tenant_id=? AND is_deleted=0 AND id IN (" + inSql + ")",
                args.toArray()
        );
        for (Map<String, Object> row : rows) {
            result.put(((Number) row.get("id")).longValue(), ((Number) row.get("created_by")).longValue());
        }
        return result;
    }

    public Map<Long, Long> listMeetingCreators(Collection<Long> meetingIds) {
        Map<Long, Long> result = new LinkedHashMap<>();
        if (meetingIds == null || meetingIds.isEmpty()) {
            return result;
        }
        String inSql = buildInClause(meetingIds.size());
        List<Object> args = new ArrayList<>();
        args.add(tenantId());
        args.addAll(meetingIds);
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT id, created_by FROM meeting WHERE tenant_id=? AND is_deleted=0 AND id IN (" + inSql + ")",
                args.toArray()
        );
        for (Map<String, Object> row : rows) {
            result.put(((Number) row.get("id")).longValue(), ((Number) row.get("created_by")).longValue());
        }
        return result;
    }

    public Map<Long, Long> listMeetingProjectIds(Collection<Long> meetingIds) {
        Map<Long, Long> result = new LinkedHashMap<>();
        if (meetingIds == null || meetingIds.isEmpty()) {
            return result;
        }
        String inSql = buildInClause(meetingIds.size());
        List<Object> args = new ArrayList<>();
        args.add(tenantId());
        args.addAll(meetingIds);
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT id, project_id FROM meeting WHERE tenant_id=? AND is_deleted=0 AND id IN (" + inSql + ")",
                args.toArray()
        );
        for (Map<String, Object> row : rows) {
            result.put(((Number) row.get("id")).longValue(), ((Number) row.get("project_id")).longValue());
        }
        return result;
    }

    public Map<Long, Long> listExpertCreators(Collection<Long> expertIds) {
        Map<Long, Long> result = new LinkedHashMap<>();
        if (expertIds == null || expertIds.isEmpty()) {
            return result;
        }
        String inSql = buildInClause(expertIds.size());
        List<Object> args = new ArrayList<>();
        args.add(0L);
        args.addAll(expertIds);
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT id, created_by FROM expert WHERE tenant_id=? AND is_deleted=0 AND id IN (" + inSql + ")",
                args.toArray()
        );
        for (Map<String, Object> row : rows) {
            result.put(((Number) row.get("id")).longValue(), ((Number) row.get("created_by")).longValue());
        }
        return result;
    }

    private Set<String> listUserRoleCodes(Long userId) {
        List<String> roleCodes = jdbcTemplate.queryForList(
                "SELECT DISTINCT r.role_code FROM user_role ur " +
                        "JOIN role r ON ur.tenant_id=r.tenant_id AND ur.role_id=r.id " +
                        "WHERE ur.tenant_id=? AND ur.user_id=? AND r.is_deleted=0",
                String.class,
                tenantId(),
                userId
        );
        return new HashSet<>(roleCodes);
    }

    private Set<Long> listBoundProjectIds(Long userId, boolean ownerScoped, boolean executorScoped, boolean legacyExecutorScoped) {
        List<Long> ids;
        if (ownerScoped && executorScoped && legacyExecutorScoped) {
            ids = jdbcTemplate.queryForList(
                    "SELECT DISTINCT project_id FROM project_user_binding " +
                            "WHERE tenant_id=? AND user_id=? AND is_deleted=0 AND bind_role_code IN ('PROJECT_OWNER', 'PROJECT_EXECUTOR', 'EXECUTOR')",
                    Long.class,
                    tenantId(),
                    userId
            );
        } else if (ownerScoped && executorScoped) {
            ids = jdbcTemplate.queryForList(
                    "SELECT DISTINCT project_id FROM project_user_binding " +
                            "WHERE tenant_id=? AND user_id=? AND is_deleted=0 AND bind_role_code IN ('PROJECT_OWNER', 'PROJECT_EXECUTOR')",
                    Long.class,
                    tenantId(),
                    userId
            );
        } else if (ownerScoped && legacyExecutorScoped) {
            ids = jdbcTemplate.queryForList(
                    "SELECT DISTINCT project_id FROM project_user_binding " +
                            "WHERE tenant_id=? AND user_id=? AND is_deleted=0 AND bind_role_code IN ('PROJECT_OWNER', 'EXECUTOR')",
                    Long.class,
                    tenantId(),
                    userId
            );
        } else if (executorScoped && legacyExecutorScoped) {
            ids = jdbcTemplate.queryForList(
                    "SELECT DISTINCT project_id FROM project_user_binding " +
                            "WHERE tenant_id=? AND user_id=? AND is_deleted=0 AND bind_role_code IN ('PROJECT_EXECUTOR', 'EXECUTOR')",
                    Long.class,
                    tenantId(),
                    userId
            );
        } else if (ownerScoped) {
            ids = jdbcTemplate.queryForList(
                    "SELECT DISTINCT project_id FROM project_user_binding " +
                            "WHERE tenant_id=? AND user_id=? AND is_deleted=0 AND bind_role_code='PROJECT_OWNER'",
                    Long.class,
                    tenantId(),
                    userId
            );
        } else {
            if (executorScoped) {
                ids = jdbcTemplate.queryForList(
                        "SELECT DISTINCT project_id FROM project_user_binding " +
                                "WHERE tenant_id=? AND user_id=? AND is_deleted=0 AND bind_role_code='PROJECT_EXECUTOR'",
                        Long.class,
                        tenantId(),
                        userId
                );
            } else {
                ids = jdbcTemplate.queryForList(
                        "SELECT DISTINCT project_id FROM project_user_binding " +
                                "WHERE tenant_id=? AND user_id=? AND is_deleted=0 AND bind_role_code='EXECUTOR'",
                        Long.class,
                        tenantId(),
                        userId
                );
            }
        }
        return new HashSet<>(ids);
    }

    private Set<Long> parseIds(String idsCsv) {
        Set<Long> ids = new HashSet<>();
        if (idsCsv == null || idsCsv.trim().isEmpty()) {
            return ids;
        }
        String[] arr = idsCsv.split(",");
        for (String item : arr) {
            String val = item.trim();
            if (val.isEmpty()) {
                continue;
            }
            try {
                ids.add(Long.parseLong(val));
            } catch (NumberFormatException ignored) {
            }
        }
        return ids;
    }

    private void assertPolicyExists(Long id) {
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM data_permission_policy WHERE tenant_id=? AND id=?",
                Integer.class,
                tenantId(),
                id
        );
        if (count == null || count == 0) {
            throw new BusinessException(10003, "数据权限策略不存在");
        }
    }

    private DataPermissionPolicy findById(Long id) {
        List<DataPermissionPolicy> list = jdbcTemplate.query(
                "SELECT * FROM data_permission_policy WHERE tenant_id=? AND id=?",
                POLICY_ROW_MAPPER,
                tenantId(),
                id
        );
        if (list.isEmpty()) {
            throw new BusinessException(10003, "数据权限策略不存在");
        }
        return list.get(0);
    }

    public static class DataScope {
        private final boolean projectAll;
        private final Set<Long> projectIds;
        private final boolean projectOwnerOnly;
        private final boolean meetingAll;
        private final Set<Long> meetingIds;
        private final boolean meetingOwnerOnly;
        private final boolean userAll;
        private final Set<Long> userIds;
        private final boolean userOwnerOnly;
        private final boolean expertAll;
        private final Set<Long> expertIds;
        private final boolean expertOwnerOnly;

        public DataScope(boolean projectAll,
                         Set<Long> projectIds,
                         boolean projectOwnerOnly,
                         boolean meetingAll,
                         Set<Long> meetingIds,
                         boolean meetingOwnerOnly,
                         boolean userAll,
                         Set<Long> userIds,
                         boolean userOwnerOnly,
                         boolean expertAll,
                         Set<Long> expertIds,
                         boolean expertOwnerOnly) {
            this.projectAll = projectAll;
            this.projectIds = projectIds;
            this.projectOwnerOnly = projectOwnerOnly;
            this.meetingAll = meetingAll;
            this.meetingIds = meetingIds;
            this.meetingOwnerOnly = meetingOwnerOnly;
            this.userAll = userAll;
            this.userIds = userIds;
            this.userOwnerOnly = userOwnerOnly;
            this.expertAll = expertAll;
            this.expertIds = expertIds;
            this.expertOwnerOnly = expertOwnerOnly;
        }

        public static DataScope allowAll() {
            return new DataScope(true, new HashSet<Long>(), false, true, new HashSet<Long>(), false, true, new HashSet<Long>(), false, true, new HashSet<Long>(), false);
        }

        public static DataScope denyAll() {
            return new DataScope(false, new HashSet<Long>(), false, false, new HashSet<Long>(), false, false, new HashSet<Long>(), false, false, new HashSet<Long>(), false);
        }

        public boolean isProjectAll() {
            return projectAll;
        }

        public Set<Long> getProjectIds() {
            return projectIds;
        }

        public boolean isProjectOwnerOnly() {
            return projectOwnerOnly;
        }

        public boolean isMeetingAll() {
            return meetingAll;
        }

        public Set<Long> getMeetingIds() {
            return meetingIds;
        }

        public boolean isMeetingOwnerOnly() {
            return meetingOwnerOnly;
        }

        public boolean isUserAll() {
            return userAll;
        }

        public Set<Long> getUserIds() {
            return userIds;
        }

        public boolean isUserOwnerOnly() {
            return userOwnerOnly;
        }

        public boolean isExpertAll() {
            return expertAll;
        }

        public Set<Long> getExpertIds() {
            return expertIds;
        }

        public boolean isExpertOwnerOnly() {
            return expertOwnerOnly;
        }
    }

    private String buildInClause(int size) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            if (i > 0) {
                sb.append(",");
            }
            sb.append("?");
        }
        return sb.toString();
    }

    private Long tenantId() {
        return AuthContext.requireTenantId();
    }
}
