<?php
/**
 * Permission Override Manager
 *
 * Manages user-specific permission overrides with mandatory audit logging.
 * CRUD operations for the fleekdash_user_permission_overrides table.
 *
 * @package FleekDash\Core
 * @since 2.3.0
 */

namespace FleekDash\Core;

defined('ABSPATH') || exit;

class PermissionOverrideManager {
    /**
     * Override types
     */
    public const TYPE_ALLOW = 'ALLOW';
    public const TYPE_DENY = 'DENY';
    public const TYPE_TEMP_ALLOW = 'TEMP_ALLOW';
    public const TYPE_TEMP_DENY = 'TEMP_DENY';

    /**
     * Valid override types
     */
    public const VALID_TYPES = [self::TYPE_ALLOW, self::TYPE_DENY, self::TYPE_TEMP_ALLOW, self::TYPE_TEMP_DENY];

    /**
     * Audit actions
     */
    public const ACTION_CREATE = 'CREATE';
    public const ACTION_UPDATE = 'UPDATE';
    public const ACTION_DELETE = 'DELETE';
    public const ACTION_EXPIRE = 'EXPIRE';

    /**
     * Table name (without prefix)
     */
    private const TABLE_OVERRIDES = 'fleekdash_user_permission_overrides';
    private const TABLE_HISTORY = 'fleekdash_permission_override_history';

    /**
     * Create a new override
     *
     * @param array $data Override data: user_id, permission_id, override_type, reason?, created_by, expires_at?
     * @return array|null Created override row or null on failure
     */
    public static function create(array $data): ?array {
        if (self::isInvalidCreateData($data)) {
            return null;
        }

        $user_id = (int) ($data['user_id'] ?? 0);
        $permission_id = \sanitize_text_field($data['permission_id'] ?? '');
        $created_by = self::resolveActorId($data, $user_id);
        $existing = self::getActiveByUserAndPermission($user_id, $permission_id);

        if ($existing) {
            return self::update((int) $existing['id'], [
                'override_type' => $data['override_type'] ?? self::TYPE_ALLOW,
                'reason' => isset($data['reason']) ? \wp_kses_post($data['reason']) : null,
                'expires_at' => !empty($data['expires_at']) ? \sanitize_text_field($data['expires_at']) : null,
                'is_active' => 1,
            ], $created_by);
        }

        return self::insertOverride($data, $user_id, $permission_id, $created_by);
    }

    private static function getActiveByUserAndPermission(int $user_id, string $permission_id): ?array {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE_OVERRIDES;
        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM {$table} WHERE user_id = %d AND permission_id = %s AND is_active = 1 LIMIT 1",
                $user_id,
                $permission_id
            ),
            ARRAY_A
        );

        return $row ?: null;
    }

    private static function resolveActorId(array $data, int $user_id): int {
        $actor_id = (int) ($data['created_by'] ?? \get_current_user_id());
        if ($actor_id <= 0) {
            $actor_id = $user_id;
        }

        return $actor_id;
    }

    private static function insertOverride(array $data, int $user_id, string $permission_id, int $created_by): ?array {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE_OVERRIDES;
        $insert_data = [
            'user_id' => $user_id,
            'permission_id' => $permission_id,
            'override_type' => $data['override_type'] ?? self::TYPE_ALLOW,
            'reason' => isset($data['reason']) ? \wp_kses_post($data['reason']) : null,
            'created_by' => $created_by,
            'expires_at' => !empty($data['expires_at']) ? \sanitize_text_field($data['expires_at']) : null,
            'is_active' => 1,
        ];

        $result = $wpdb->insert($table, $insert_data);
        if ($result === false) {
            return null;
        }

        $override = self::get((int) $wpdb->insert_id);
        if ($override) {
            self::logAudit((int) $override['id'], self::ACTION_CREATE, null, $override, $created_by);
            self::invalidateUserCache($user_id);
        }

        return $override;
    }

    /**
     * Check if create data is invalid
     *
     * @param array $data
     * @return bool True if invalid, false if valid
     */
    private static function isInvalidCreateData(array $data): bool {
        $user_id = (int) ($data['user_id'] ?? 0);
        $permission_id = \sanitize_text_field($data['permission_id'] ?? '');
        $override_type = $data['override_type'] ?? self::TYPE_ALLOW;
        $expires_at = isset($data['expires_at']) ? \sanitize_text_field($data['expires_at']) : null;

        $invalid = !$user_id || !$permission_id || !in_array($override_type, self::VALID_TYPES, true);
        if (!$invalid) {
            $invalid = in_array($override_type, [self::TYPE_TEMP_ALLOW, self::TYPE_TEMP_DENY], true) && empty($expires_at);
        }
        if (!$invalid) {
            $invalid = !PermissionManager::has($permission_id);
        }

        return $invalid;
    }

    /**
     * Update an override
     *
     * @param int $id Override ID
     * @param array $data Override data: override_type?, reason?, expires_at?, is_active?
     * @param int|null $performed_by User performing the action
     * @return array|null Updated override or null on failure
     */
    public static function update(int $id, array $data, ?int $performed_by = null): ?array {
        $existing = self::get($id);
        $update_data = $existing ? self::prepareUpdateData($data) : [];
        $updated = $existing;

        if ($existing && !empty($update_data)) {
            global $wpdb;
            $table = $wpdb->prefix . self::TABLE_OVERRIDES;
            $performed_by = $performed_by ?? \get_current_user_id();

            $result = $wpdb->update($table, $update_data, ['id' => $id]);
            $updated = $result !== false ? self::get($id) : null;

            if ($updated) {
                self::logAudit($id, self::ACTION_UPDATE, $existing, $updated, $performed_by);
                self::invalidateUserCache((int) $existing['user_id']);
            }
        }

        return $updated;
    }

    /**
     * Prepare data for updating an override
     *
     * @param array $data
     * @return array
     */
    private static function prepareUpdateData(array $data): array {
        $update_data = [];

        if (isset($data['override_type']) && in_array($data['override_type'], self::VALID_TYPES, true)) {
            $update_data['override_type'] = $data['override_type'];
        }

        if (array_key_exists('reason', $data)) {
            $update_data['reason'] = $data['reason'] ? \wp_kses_post($data['reason']) : null;
        }

        if (array_key_exists('expires_at', $data)) {
            $update_data['expires_at'] = !empty($data['expires_at']) ? \sanitize_text_field($data['expires_at']) : null;
        }

        if (isset($data['is_active'])) {
            $update_data['is_active'] = (bool) $data['is_active'] ? 1 : 0;
        }

        return $update_data;
    }

    /**
     * Delete an override
     *
     * @param int $id Override ID
     * @param int|null $performed_by User performing the action
     * @return bool Success
     */
    public static function delete(int $id, ?int $performed_by = null): bool {
        $existing = self::get($id);

        if (!$existing) {
            return false;
        }

        global $wpdb;
        $table = $wpdb->prefix . self::TABLE_OVERRIDES;
        $performed_by = $performed_by ?? \get_current_user_id();

        $result = $wpdb->delete($table, ['id' => $id], ['%d']);

        if ($result === false) {
            if (function_exists('fleekdash_log_error')) {
                fleekdash_log_error(
                    'PermissionOverrideManager: delete failed for id ' . $id . ' — ' . ($wpdb->last_error ?: 'unknown DB error')
                );
            }
            return false;
        }

        self::logAudit($id, self::ACTION_DELETE, $existing, null, $performed_by);
        self::invalidateUserCache((int) $existing['user_id']);

        return true;
    }

    /**
     * Get override by ID
     *
     * @param int $id Override ID
     * @return array|null Override row or null
     */
    public static function get(int $id): ?array {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE_OVERRIDES;

        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM {$table} WHERE id = %d",
                $id
            ),
            ARRAY_A
        );

        return $row ?: null;
    }

    /**
     * List overrides with filters
     *
     * @param array $filters user_id?, permission_id?, status? (active|expired|all), page?, per_page?
     * @return array{overrides: array, total: int, page: int, per_page: int}
     */
    public static function list(array $filters = []): array {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE_OVERRIDES;

        $user_id = isset($filters['user_id']) ? (int) $filters['user_id'] : null;
        $permission_id = isset($filters['permission_id']) ? \sanitize_text_field($filters['permission_id']) : null;
        $status = $filters['status'] ?? 'active';
        $page = max(1, (int) ($filters['page'] ?? 1));
        $per_page = min(100, max(1, (int) ($filters['per_page'] ?? 20)));
        $offset = ($page - 1) * $per_page;

        $where = ['1=1'];
        $values = [];

        if ($user_id) {
            $where[] = 'user_id = %d';
            $values[] = $user_id;
        }

        if ($permission_id) {
            $where[] = 'permission_id = %s';
            $values[] = $permission_id;
        }

        if ($status === 'active') {
            $where[] = 'is_active = 1';
            $where[] = '(expires_at IS NULL OR expires_at > NOW())';
        } elseif ($status === 'expired') {
            $where[] = '(is_active = 0 OR (expires_at IS NOT NULL AND expires_at <= NOW()))';
        }

        $where_sql = implode(' AND ', $where);

        $count_sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}";
        if ($values === []) {
            $total = (int) $wpdb->get_var($count_sql);
        } else {
            $total = (int) $wpdb->get_var($wpdb->prepare($count_sql, ...$values));
        }

        $list_values = array_merge($values, [$per_page, $offset]);
        $list_sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d";
        $prepared_list = $wpdb->prepare($list_sql, ...$list_values);
        $rows = [];
        if ($prepared_list) {
            $raw = $wpdb->get_results($prepared_list, ARRAY_A);
            $rows = is_array($raw) ? $raw : [];
        }

        return [
            'overrides' => $rows,
            'total' => $total,
            'page' => $page,
            'per_page' => $per_page,
        ];
    }

    /**
     * Get all active overrides for a user (for load-once cache)
     *
     * @param int $user_id User ID
     * @return array Map of permission_id => override row
     */
    public static function getActiveForUser(int $user_id): array {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE_OVERRIDES;

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM {$table}
                 WHERE user_id = %d AND is_active = 1
                 AND (expires_at IS NULL OR expires_at > NOW())",
                $user_id
            ),
            ARRAY_A
        );

        $map = [];
        foreach ($rows ?: [] as $row) {
            $map[$row['permission_id']] = $row;
        }

        return $map;
    }

    /**
     * Log audit entry (MANDATORY for compliance)
     *
     * @param int $override_id Override ID
     * @param string $action CREATE|UPDATE|DELETE|EXPIRE
     * @param array|null $old_value Previous state
     * @param array|null $new_value New state
     * @param int $performed_by User ID
     * @return bool Success
     */
    public static function logAudit(
        int $override_id,
        string $action,
        ?array $old_value,
        ?array $new_value,
        int $performed_by
    ): bool {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE_HISTORY;

        $ip = isset($_SERVER['REMOTE_ADDR']) ? \sanitize_text_field(\wp_unslash($_SERVER['REMOTE_ADDR'])) : null;
        if ($ip && strlen($ip) > 45) {
            $ip = substr($ip, 0, 45);
        }

        $insert_data = [
            'override_id' => $override_id,
            'action' => $action,
            'old_value' => $old_value ? \wp_json_encode($old_value) : null,
            'new_value' => $new_value ? \wp_json_encode($new_value) : null,
            'performed_by' => $performed_by,
            'ip_address' => $ip,
        ];

        return $wpdb->insert($table, $insert_data) !== false;
    }

    /**
     * Cleanup expired overrides (cron handler)
     *
     * @return int Number of overrides deactivated
     */
    public static function cleanupExpired(): int {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE_OVERRIDES;

        $expired = $wpdb->get_results(
            "SELECT * FROM {$table}
             WHERE expires_at IS NOT NULL
             AND expires_at <= NOW()
             AND is_active = 1",
            ARRAY_A
        );

        if (empty($expired)) {
            return 0;
        }

        $result = $wpdb->query(
            "UPDATE {$table}
             SET is_active = 0
             WHERE expires_at IS NOT NULL
             AND expires_at <= NOW()
             AND is_active = 1"
        );

        $affected_users = [];
        foreach ($expired as $row) {
            self::logAudit((int) $row['id'], self::ACTION_EXPIRE, $row, null, 0);
            $affected_users[(int) $row['user_id']] = true;
        }

        foreach (array_keys($affected_users) as $user_id) {
            self::invalidateUserCache($user_id);
        }

        return $result !== false ? $result : 0;
    }

    /**
     * Invalidate permission cache for a user
     *
     * @param int $user_id User ID
     * @return void
     */
    public static function invalidateUserCache(int $user_id): void {
        PermissionManager::clearUserOverridesCache($user_id);
        PermissionManager::clearCache(null, $user_id);

        \do_action('fleekdash_permission_cache_cleared', $user_id);
    }

    /**
     * Delete all overrides for a user
     *
     * @param int $user_id User ID
     * @param int|null $performed_by User performing the action
     * @return int Number of overrides deleted
     */
    public static function deleteAllForUser(int $user_id, ?int $performed_by = null): int {
        $list = self::list(['user_id' => $user_id, 'status' => 'all', 'per_page' => 1000]);
        $count = 0;

        foreach ($list['overrides'] as $override) {
            if (self::delete((int) $override['id'], $performed_by)) {
                $count++;
            }
        }

        return $count;
    }
}
