<?php
/**
 * acl-functions.php — توابع کمکی ACL یکپارچه
 *
 * استفاده در کنترلرها:
 *   if ( ! acl_can('gis.parcels.edit', 'project', 5) ) return new WP_REST_Response(['message'=>'دسترسی ندارید'], 403);
 *
 * یا با کاربر مشخص:
 *   acl_user_can(42, 'inv.vouchers.confirm');
 */

if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * بررسی مجوز برای کاربر فعلی
 *
 * @param string      $permission_key  مثلاً 'gis.parcels.edit'
 * @param string|null $scope_type      'global' | 'project' | 'warehouse' | 'district' | ...
 * @param int|null    $scope_id        ID مربوط به scope (یا null برای global)
 * @return bool
 */
function acl_can( string $permission_key, ?string $scope_type = null, ?int $scope_id = null ): bool {
    $uid = (int) get_current_user_id();
    if ( ! $uid && isset( $GLOBALS['acc_jwt_user_id'] ) ) {
        $uid = (int) $GLOBALS['acc_jwt_user_id'];
    }
    if ( ! $uid ) return false;

    return acl_user_can( $uid, $permission_key, $scope_type, $scope_id );
}

/**
 * بررسی مجوز برای کاربر مشخص
 */
function acl_user_can( int $user_id, string $permission_key, ?string $scope_type = null, ?int $scope_id = null ): bool {
    global $wpdb;
    static $cache = [];

    if ( $user_id <= 0 ) return false;

    $cache_key = "{$user_id}|{$permission_key}|{$scope_type}|{$scope_id}";
    if ( isset( $cache[ $cache_key ] ) ) return $cache[ $cache_key ];

    $pre = $wpdb->prefix;

    // 1) Override روی شیء خاص — قوی‌ترین قانون
    if ( $scope_type && $scope_id !== null ) {
        $override = $wpdb->get_var( $wpdb->prepare(
            "SELECT effect FROM {$pre}acl_object_overrides
             WHERE user_id = %d AND entity_type = %s AND entity_id = %d AND permission_key = %s
             LIMIT 1",
            $user_id, $scope_type, $scope_id, $permission_key
        ) );
        if ( $override === 'deny' )  { $cache[ $cache_key ] = false; return false; }
        if ( $override === 'allow' ) { $cache[ $cache_key ] = true;  return true;  }
    }

    // 2) آیا کاربر هیچ نقشی دارد که این مجوز را داشته باشد؟
    //    بدون scope مشخص: هر نقش (global یا scoped) که مجوز را دارد کافی است
    //    با scope مشخص: فقط global یا همان scope_id
    if ( $scope_type && $scope_id !== null ) {
        $sql = "SELECT COUNT(*) FROM {$pre}acl_user_roles ur
                INNER JOIN {$pre}acl_role_permissions rp ON rp.role_id = ur.role_id
                WHERE ur.user_id = %d
                  AND rp.permission_key = %s
                  AND (
                    ur.scope_type = 'global'
                    OR (ur.scope_type = %s AND ur.scope_id = %d)
                  )";
        $args = [ $user_id, $permission_key, $scope_type, $scope_id ];
    } else {
        $sql = "SELECT COUNT(*) FROM {$pre}acl_user_roles ur
                INNER JOIN {$pre}acl_role_permissions rp ON rp.role_id = ur.role_id
                WHERE ur.user_id = %d AND rp.permission_key = %s";
        $args = [ $user_id, $permission_key ];
    }

    $count = (int) $wpdb->get_var( $wpdb->prepare( $sql, ...$args ) );
    $allowed = $count > 0;

    $cache[ $cache_key ] = $allowed;
    return $allowed;
}

/**
 * لیست همه مجوزهای کاربر (برای فرستادن به فرانت)
 */
function acl_get_user_permissions( int $user_id ): array {
    global $wpdb;
    $pre = $wpdb->prefix;

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT DISTINCT rp.permission_key, ur.scope_type, ur.scope_id
         FROM {$pre}acl_user_roles ur
         INNER JOIN {$pre}acl_role_permissions rp ON rp.role_id = ur.role_id
         WHERE ur.user_id = %d",
        $user_id
    ), ARRAY_A );

    $result = [];
    foreach ( $rows ?? [] as $row ) {
        $result[] = [
            'key'        => $row['permission_key'],
            'scope_type' => $row['scope_type'],
            'scope_id'   => $row['scope_id'] !== null ? (int) $row['scope_id'] : null,
        ];
    }
    return $result;
}

/**
 * لیست نقش‌های کاربر
 */
function acl_get_user_roles( int $user_id ): array {
    global $wpdb;
    $pre = $wpdb->prefix;
    return $wpdb->get_results( $wpdb->prepare(
        "SELECT r.slug, r.name, ur.scope_type, ur.scope_id, ur.granted_at
         FROM {$pre}acl_user_roles ur
         INNER JOIN {$pre}acl_roles r ON r.id = ur.role_id
         WHERE ur.user_id = %d
         ORDER BY r.sort_order",
        $user_id
    ), ARRAY_A ) ?? [];
}

/**
 * منوهای صریحاً اختصاص‌داده‌شده به کاربر (پروفایل دسترسی)
 * null = جدول وجود ندارد (حالت legacy)
 */
function acl_get_user_menu_grants( int $user_id ): ?array {
    global $wpdb;
    static $cache = [];
    if ( array_key_exists( $user_id, $cache ) ) return $cache[ $user_id ];

    $table = $wpdb->prefix . 'acl_user_menu_access';
    if ( ! acl_table_exists( $table ) ) {
        $cache[ $user_id ] = null;
        return null;
    }

    $rows = $wpdb->get_col( $wpdb->prepare(
        "SELECT menu_key FROM {$table} WHERE user_id = %d",
        $user_id
    ) );
    $cache[ $user_id ] = $rows ?? [];
    return $cache[ $user_id ];
}

/**
 * لیست منوهای قابل دسترسی برای کاربر
 */
function acl_get_user_menus( int $user_id ): array {
    global $wpdb;
    $pre = $wpdb->prefix;

    $menus = $wpdb->get_results(
        "SELECT * FROM {$pre}acl_menus WHERE is_active = 1 ORDER BY sort_order",
        ARRAY_A
    );

    $explicit = acl_get_user_menu_grants( $user_id );
    $has_explicit = $explicit !== null;

    $allowed = [];
    foreach ( $menus ?? [] as $menu ) {
        if ( $has_explicit ) {
            if ( in_array( $menu['menu_key'], $explicit, true ) ) {
                $allowed[] = $menu;
            }
            continue;
        }
        $req = $menu['required_permission'];
        if ( $req === '' || acl_user_can( $user_id, $req ) ) {
            $allowed[] = $menu;
        }
    }
    return $allowed;
}

/**
 * آیا جدول وجود دارد؟
 */
function acl_table_exists( string $table ): bool {
    global $wpdb;
    return (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
}

/**
 * IDهای scope کاربر برای یک نوع (مثلاً warehouse)
 */
function acl_get_user_scope_ids( int $user_id, string $scope_type ): array {
    global $wpdb;
    $rows = $wpdb->get_col( $wpdb->prepare(
        "SELECT DISTINCT scope_id FROM {$wpdb->prefix}acl_user_roles
         WHERE user_id = %d AND scope_type = %s AND scope_id IS NOT NULL",
        $user_id, $scope_type
    ) );
    return array_map( 'intval', $rows ?? [] );
}

/**
 * آیا کاربر scope سراسری برای نوع مشخص دارد؟
 */
function acl_user_has_global_scope( int $user_id, string $scope_type = '' ): bool {
    global $wpdb;
    if ( acl_user_can( $user_id, 'system.acl.manage' ) ) return true;

    $sql  = "SELECT COUNT(*) FROM {$wpdb->prefix}acl_user_roles WHERE user_id = %d AND scope_type = 'global'";
    $args = [ $user_id ];
    $cnt  = (int) $wpdb->get_var( $wpdb->prepare( $sql, ...$args ) );
    return $cnt > 0;
}

/**
 * بارگذاری پروفایل دسترسی کاربر
 */
function acl_get_user_access_profile( int $user_id ): array {
    global $wpdb;

    $user = $wpdb->get_row( $wpdb->prepare(
        "SELECT ID AS user_id, user_login, display_name, user_email FROM {$wpdb->users} WHERE ID = %d",
        $user_id
    ), ARRAY_A );
    if ( ! $user ) return [];

    $menu_grants = acl_get_user_menu_grants( $user_id );

    // اگر پروفایل صریح ندارد، منوها را از مجوزهای فعلی استنتاج کن (حالت legacy)
    if ( $menu_grants === null ) {
        $menu_keys = [];
        $all_menus = $wpdb->get_results(
            "SELECT menu_key, required_permission FROM {$wpdb->prefix}acl_menus WHERE is_active = 1",
            ARRAY_A
        ) ?? [];
        foreach ( $all_menus as $m ) {
            $req = $m['required_permission'];
            if ( $req === '' || acl_user_can( $user_id, $req ) ) {
                $menu_keys[] = $m['menu_key'];
            }
        }
        $menu_keys = array_values( array_unique( $menu_keys ) );
    } else {
        $menu_keys = $menu_grants;
    }

    $assignments = $wpdb->get_results( $wpdb->prepare(
        "SELECT ur.id AS assignment_id, ur.role_id, ur.scope_type, ur.scope_id,
                r.slug AS role_slug, r.name AS role_name
         FROM {$wpdb->prefix}acl_user_roles ur
         INNER JOIN {$wpdb->prefix}acl_roles r ON r.id = ur.role_id
         WHERE ur.user_id = %d
         ORDER BY r.sort_order, ur.scope_type, ur.scope_id",
        $user_id
    ), ARRAY_A ) ?? [];

    $role_ids = [];
    $scopes   = [];
    foreach ( $assignments as $a ) {
        $role_ids[ (int) $a['role_id'] ] = true;
        if ( $a['scope_type'] !== 'global' && $a['scope_id'] !== null ) {
            $st = $a['scope_type'];
            if ( ! isset( $scopes[ $st ] ) ) $scopes[ $st ] = [];
            $scopes[ $st ][ (int) $a['scope_id'] ] = true;
        }
    }

    foreach ( $scopes as $st => $ids ) {
        $scopes[ $st ] = array_map( 'intval', array_keys( $ids ) );
    }

    return [
        'user'       => $user,
        'menu_keys'  => $menu_keys,
        'role_ids'   => array_map( 'intval', array_keys( $role_ids ) ),
        'scopes'     => $scopes,
        'assignments'=> $assignments,
    ];
}

/**
 * اعتبارسنجی هم‌خوانی منو و نقش
 */
function acl_validate_menu_role_consistency( array $menu_keys, array $role_ids ): array {
    global $wpdb;
    require_once __DIR__ . '/acl-scope-registry.php';

    $registry = acl_get_module_registry();
    $warnings = [];
    $missing  = [];

    if ( ! $role_ids ) {
        return ['valid' => false, 'warnings' => ['حداقل یک نقش انتخاب کنید.'], 'missing_perms' => []];
    }

    $perms = $wpdb->get_col( $wpdb->prepare(
        "SELECT DISTINCT rp.permission_key
         FROM {$wpdb->prefix}acl_role_permissions rp
         WHERE rp.role_id IN (" . implode( ',', array_map( 'intval', $role_ids ) ) . ")"
    ) ) ?? [];
    $perm_set = array_flip( $perms );

    foreach ( $menu_keys as $mk ) {
        foreach ( $registry as $mod ) {
            if ( $mod['menu_key'] !== $mk ) continue;
            $vp = $mod['view_perm'];
            if ( $vp && ! isset( $perm_set[ $vp ] ) ) {
                $missing[] = ['menu_key' => $mk, 'permission' => $vp, 'label' => $mod['label']];
                $warnings[] = "منوی «{$mod['label']}» نیاز به مجوز {$vp} دارد که در نقش‌های انتخاب‌شده نیست.";
            }
        }
    }

    return [
        'valid'         => count( $missing ) === 0,
        'warnings'      => $warnings,
        'missing_perms' => $missing,
    ];
}

/**
 * ذخیره پروفایل دسترسی کاربر
 */
function acl_save_user_access_profile( int $user_id, array $data ): array {
    global $wpdb;

    if ( $user_id <= 0 ) {
        return ['success' => false, 'message' => 'کاربر نامعتبر است'];
    }

    $menu_keys = array_values( array_unique( array_map( 'sanitize_key', $data['menu_keys'] ?? [] ) ) );
    $role_ids  = array_values( array_unique( array_map( 'intval', $data['role_ids'] ?? [] ) ) );
    $scopes    = is_array( $data['scopes'] ?? null ) ? $data['scopes'] : [];

    $validation = acl_validate_menu_role_consistency( $menu_keys, $role_ids );
    if ( ! $validation['valid'] && empty( $data['force'] ) ) {
        return [
            'success'  => false,
            'message'  => 'منو و نقش هم‌خوان نیستند',
            'warnings' => $validation['warnings'],
        ];
    }

    // super_admin — فقط از UI درخواست‌شده ذخیره می‌شود (نه حفظ کورکورانه ردیف قبلی)
    $super_role_id = (int) $wpdb->get_var(
        "SELECT id FROM {$wpdb->prefix}acl_roles WHERE slug = 'super_admin' LIMIT 1"
    );
    $wants_super = $super_role_id && in_array( $super_role_id, $role_ids, true );
    $role_ids    = array_values( array_filter( $role_ids, fn( $rid ) => (int) $rid !== $super_role_id ) );

    $extra_warnings = [];
    if ( $wants_super && function_exists( 'acl_user_is_super_admin' ) ) {
        $editor_id = (int) get_current_user_id();
        if ( $editor_id > 0 && ! acl_user_is_super_admin( $editor_id ) ) {
            return [
                'success' => false,
                'message' => 'فقط مدیر کل می‌تواند نقش super_admin تخصیص دهد',
            ];
        }
        if ( array_filter( $scopes, fn( $ids ) => is_array( $ids ) && count( $ids ) > 0 ) ) {
            $extra_warnings[] = 'نقش «مدیر کل» دسترسی سراسری دارد؛ محدوده (scope) فقط روی نقش‌های دیگر اعمال می‌شود.';
        }
    }

    $wpdb->delete( "{$wpdb->prefix}acl_user_roles", ['user_id' => $user_id] );

    $current_uid = (int) get_current_user_id();
    if ( $wants_super && $super_role_id ) {
        $wpdb->insert( "{$wpdb->prefix}acl_user_roles", [
            'user_id'    => $user_id,
            'role_id'    => $super_role_id,
            'scope_type' => 'global',
            'scope_id'   => null,
            'granted_by' => $current_uid > 0 ? $current_uid : null,
        ] );
    }

    $scope_types = [];
    foreach ( $scopes as $stype => $ids ) {
        if ( ! is_array( $ids ) ) continue;
        $scope_types[ sanitize_text_field( $stype ) ] = array_values( array_unique( array_map( 'intval', $ids ) ) );
    }

    foreach ( $role_ids as $rid ) {
        $has_scoped = false;
        foreach ( $scope_types as $stype => $ids ) {
            if ( ! $ids ) continue;
            foreach ( $ids as $sid ) {
                $wpdb->insert( "{$wpdb->prefix}acl_user_roles", [
                    'user_id'    => $user_id,
                    'role_id'    => $rid,
                    'scope_type' => $stype,
                    'scope_id'   => $sid,
                    'granted_by' => $current_uid > 0 ? $current_uid : null,
                ] );
                $has_scoped = true;
            }
        }
        if ( ! $has_scoped ) {
            $wpdb->insert( "{$wpdb->prefix}acl_user_roles", [
                'user_id'    => $user_id,
                'role_id'    => $rid,
                'scope_type' => 'global',
                'scope_id'   => null,
                'granted_by' => $current_uid > 0 ? $current_uid : null,
            ] );
        }
    }

    $menus_saved = false;
    $table = $wpdb->prefix . 'acl_user_menu_access';
    if ( acl_table_exists( $table ) ) {
        $menus_saved = true;
        $wpdb->delete( $table, ['user_id' => $user_id] );
        foreach ( $menu_keys as $mk ) {
            if ( $mk ) {
                $wpdb->insert( $table, [
                    'user_id'  => $user_id,
                    'menu_key' => $mk,
                ] );
            }
        }
    } else {
        $extra_warnings[] = 'جدول acl_user_menu_access وجود ندارد — منوها ذخیره نشد. یک‌بار run-acl-migrate.php را اجرا کنید.';
    }

    return [
        'success'  => true,
        'warnings' => array_values( array_unique( array_merge( $validation['warnings'] ?? [], $extra_warnings ) ) ),
        'menus_saved' => $menus_saved,
    ];
}

/**
 * permission_callback آماده برای REST
 *
 * استفاده:
 *   register_rest_route(..., [
 *     'permission_callback' => acl_permission_callback('gis.parcels.edit'),
 *   ]);
 */
function acl_permission_callback( string $permission_key ): callable {
    return function() use ( $permission_key ) {
        // ابتدا JWT را check کن
        if ( function_exists( 'acc_jwt_check_permission' ) ) {
            $jwt_ok = call_user_func( 'acc_jwt_check_permission' );
            if ( $jwt_ok !== true && ! is_bool( $jwt_ok ) ) return $jwt_ok;
            if ( $jwt_ok === false ) return false;
        }
        return acl_can( $permission_key );
    };
}
