<?php

namespace WPSN\Modules\ActivityLog;

defined('ABSPATH') || exit;

final class ActivityLogRepository
{
    private function tableName(): string
    {
        global $wpdb;
        return $wpdb->prefix . 'wpsn_activity_log';
    }

    /**
     * @param array<string,mixed> $args
     */
    public function insert(string $action, string $message, array $args = []): int
    {
        global $wpdb;

        $row = [
            'created_at'  => gmdate('Y-m-d H:i:s'),
            'user_id'     => isset($args['user_id']) ? (int) $args['user_id'] : (get_current_user_id() ?: null),
            'action'      => substr(sanitize_key($action), 0, 80),
            'context'     => isset($args['context']) ? substr(sanitize_key((string) $args['context']), 0, 80) : '',
            'object_type' => isset($args['object_type']) ? substr(sanitize_key((string) $args['object_type']), 0, 30) : '',
            'object_id'   => isset($args['object_id']) && (int) $args['object_id'] > 0 ? (int) $args['object_id'] : null,
            'object_key'  => isset($args['object_key']) ? substr((string) $args['object_key'], 0, 191) : '',
            'message'     => (string) $message,
            'meta'        => null,
        ];

        if (isset($args['meta'])) {
            $meta = $args['meta'];
            if (is_array($meta) || is_object($meta)) {
                $meta = wp_json_encode($meta);
            }
            if (is_string($meta) && $meta !== '') {
                // Keep meta bounded.
                if (strlen($meta) > 20000) {
                    $meta = substr($meta, 0, 20000);
                }
                $row['meta'] = $meta;
            }
        }

        $format = [
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
        ];

        // Allow NULL object_id.
        if ($row['object_id'] === null) {
            $row['object_id'] = null;
        }

        $wpdb->insert($this->tableName(), $row, $format);
        return (int) $wpdb->insert_id;
    }

    /**
     * @param array<string,mixed> $filters
     * @return array<int,array<string,mixed>>
     */
    public function list(int $limit, int $offset, array $filters = []): array
    {
        global $wpdb;

        $limit = max(1, min(200, $limit));
        $offset = max(0, $offset);

        [$whereSql, $params] = $this->buildWhere($filters);

        $sql = "SELECT * FROM {$this->tableName()} {$whereSql} ORDER BY id DESC LIMIT %d OFFSET %d";
        $params[] = $limit;
        $params[] = $offset;

        // phpcs:ignore WordPress.DB.PreparedSQL
        $rows = $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
        return is_array($rows) ? $rows : [];
    }

    /**
     * @param array<string,mixed> $filters
     */
    public function count(array $filters = []): int
    {
        global $wpdb;
        [$whereSql, $params] = $this->buildWhere($filters);
        $sql = "SELECT COUNT(1) FROM {$this->tableName()} {$whereSql}";
        // Avoid wpdb::prepare() warnings when there are no placeholders.
        if (!empty($params)) {
            // phpcs:ignore WordPress.DB.PreparedSQL
            $sql = $wpdb->prepare($sql, $params);
        }

        // phpcs:ignore WordPress.DB.PreparedSQL
        $total = (int) $wpdb->get_var($sql);
        return max(0, $total);
    }

    public function deleteAll(): int
    {
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery
        $wpdb->query("TRUNCATE TABLE {$this->tableName()}");
        return 1;
    }

    public function purgeOlderThan(int $days): int
    {
        if ($days <= 0) {
            return 0;
        }
        global $wpdb;
        $cutoff = gmdate('Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS));
        $sql = "DELETE FROM {$this->tableName()} WHERE created_at < %s";
        // phpcs:ignore WordPress.DB.PreparedSQL
        $wpdb->query($wpdb->prepare($sql, $cutoff));
        return (int) $wpdb->rows_affected;
    }

    /**
     * @return string[]
     */
    public function distinctActions(int $limit = 100): array
    {
        global $wpdb;
        $limit = max(1, min(200, $limit));
        $sql = "SELECT DISTINCT action FROM {$this->tableName()} ORDER BY action ASC LIMIT %d";
        // phpcs:ignore WordPress.DB.PreparedSQL
        $rows = $wpdb->get_col($wpdb->prepare($sql, $limit));
        if (!is_array($rows)) {
            return [];
        }
        $out = [];
        foreach ($rows as $r) {
            $r = sanitize_key((string) $r);
            if ($r !== '') {
                $out[] = $r;
            }
        }
        return $out;
    }

    /**
     * @return string[]
     */
    public function distinctObjectTypes(int $limit = 100): array
    {
        global $wpdb;
        $limit = max(1, min(200, $limit));
        $sql = "SELECT DISTINCT object_type FROM {$this->tableName()} WHERE object_type <> '' ORDER BY object_type ASC LIMIT %d";
        // phpcs:ignore WordPress.DB.PreparedSQL
        $rows = $wpdb->get_col($wpdb->prepare($sql, $limit));
        if (!is_array($rows)) {
            return [];
        }
        $out = [];
        foreach ($rows as $r) {
            $r = sanitize_key((string) $r);
            if ($r !== '') {
                $out[] = $r;
            }
        }
        return $out;
    }

    /**
     * @param array<string,mixed> $filters
     * @return array{0:string,1:array<int,mixed>}
     */
    private function buildWhere(array $filters): array
    {
        global $wpdb;

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

        if (!empty($filters['action'])) {
            $where[] = 'action = %s';
            $params[] = sanitize_key((string) $filters['action']);
        }

        if (!empty($filters['object_type'])) {
            $where[] = 'object_type = %s';
            $params[] = sanitize_key((string) $filters['object_type']);
        }

        if (!empty($filters['user_id'])) {
            $where[] = 'user_id = %d';
            $params[] = (int) $filters['user_id'];
        }

        if (!empty($filters['q'])) {
            $q = (string) $filters['q'];
            $q = trim($q);
            if ($q !== '') {
                $like = '%' . $wpdb->esc_like($q) . '%';
                $where[] = '(message LIKE %s OR object_key LIKE %s)';
                $params[] = $like;
                $params[] = $like;
            }
        }

        // Date range filters (UTC). Accepts YYYY-MM-DD.
        if (!empty($filters['date_from'])) {
            $df = (string) $filters['date_from'];
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $df)) {
                $where[] = 'created_at >= %s';
                $params[] = $df . ' 00:00:00';
            }
        }

        if (!empty($filters['date_to'])) {
            $dt = (string) $filters['date_to'];
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dt)) {
                $where[] = 'created_at <= %s';
                $params[] = $dt . ' 23:59:59';
            }
        }

        $sql = 'WHERE ' . implode(' AND ', $where);
        return [$sql, $params];
    }
}
