<?php

namespace GriffinForms;

use \GriffinForms\Traits\Singleton;

class Log
{

    use Singleton;

    private $table_name;

    protected function setTableName()
    {
        $config = Config::getInstance();
        $this->table_name = $config->getTable('log');
    }

    /**
     * Adds a log entry to the log table.
     *
     * @param string $log_type  The type/severity of the log (e.g., 'error', 'info', 'debug').
     * @param string $item_type The type of item associated with the log (e.g., 'field', 'row', 'submission').
     * @param int    $item_id   The ID of the item associated with the log.
     * @param int    $user_id   The ID of the user performing the action.
     * @param string $message   The log message.
     * @param string $category  The category for the log entry.
     * @return int              The ID of the inserted log entry.
     */
    public function add($log_type, $item_type, $item_id, $user_id, $message, $category) 
    {
        if (!$this->isLoggingEnabled()) {
            return 0; // Logging disabled by settings
        }

        if (!$this->isLogTypeAllowed($log_type)) {
            return 0; // filtered out by settings
        }

        $category = $this->normalizeCategory($category);

        global $wpdb;
        $wpdb->insert(
            $this->table_name,
            [
                'log_type'  => $log_type,
                'item_type' => $item_type,
                'item_id'   => $item_id,
                'user_id'   => $user_id,
                'message'   => $message,
                'category'  => $category,
                'date_time' => current_time('mysql', true)
            ],
            [
                '%s',  // log_type
                '%s',  // item_type
                '%d',  // item_id
                '%d',  // user_id
                '%s',  // message
                '%s',  // category
                '%s'   // date_time
            ]
        );
        
        return $wpdb->insert_id;  // Return the ID of the inserted log entry.
    }

    // Function to delete all logs matching item_id and item_type
    public function deleteByItem($item_type, $item_id) 
    {
        global $wpdb;

        return $wpdb->delete(
            $this->table_name,
            [
                'item_type' => $item_type,
                'item_id'   => $item_id
            ],
            [
                '%s', // item_type
                '%d'  // item_id
            ]
        );
    }

    // Function to fetch all logs matching item_type and item_id
    public function getByItem($item_type, $item_id) 
    {
        global $wpdb;
        $logs = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM %i WHERE item_type = %s AND item_id = %d ORDER BY date_time DESC",
            $this->table_name,
            $item_type,
            $item_id
            )
        );

        return $logs;
    }

    /**
     * Fetch logs for an item with pagination.
     *
     * @param string $item_type
     * @param int    $item_id
     * @param int    $limit
     * @param int    $offset
     * @return array
     */
    public function getByItemPaged($item_type, $item_id, $limit = 20, $offset = 0)
    {
        $limit = max(1, (int) $limit);
        $offset = max(0, (int) $offset);

        global $wpdb;
        $logs = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM %i WHERE item_type = %s AND item_id = %d ORDER BY date_time DESC LIMIT %d OFFSET %d",
            $this->table_name,
            $item_type,
            $item_id,
            $limit,
            $offset
        ));

        return $logs ?? [];
    }

    // Function to delete all logs older than the passed date
    public function deleteOlderThan($date) 
    {
        global $wpdb;

        return $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM %i WHERE date_time < %s",
                $this->table_name,
                $date
            )
        );
    }

    public function getByCategory($category) 
    {
        global $wpdb;
        $logs = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM %i WHERE category = %s ORDER BY date_time DESC",
            $this->table_name,
            $category
        ));
        
        return $logs;
    }

    public function getLatestLogByCategory($category)
    {
        global $wpdb;
        $log = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM %i WHERE category = %s ORDER BY date_time DESC LIMIT 1",
                $this->table_name,
                $category
            )
        );

        return $log;
    }

    /**
     * Fetch recent logs by one or more canonical categories.
     *
     * Accepts either raw internal category keys or already-normalized labels.
     */
    public function getByCategories(array $categories, int $limit = 10): array
    {
        global $wpdb;

        $limit = max(1, (int) $limit);
        $normalized_categories = $this->expandCategoriesForQuery($categories);

        if (empty($normalized_categories)) {
            return [];
        }

        $placeholders = implode(', ', array_fill(0, count($normalized_categories), '%s'));
        $sql = "SELECT * FROM %i WHERE category IN ($placeholders) ORDER BY date_time DESC LIMIT %d";
        $args = array_merge([$this->table_name], array_values($normalized_categories), [$limit]);

        $logs = $wpdb->get_results($wpdb->prepare($sql, ...$args));

        return $logs ?? [];
    }

    /**
     * Fetch logs with search capability on item_type and category fields
     *
     * Searches for logs matching a keyword in item_type or category fields,
     * returns results ordered by date_time DESC with optional limit.
     *
     * @param int    $limit   Maximum number of logs to fetch (default: 5)
     * @param string $keyword Search keyword to match in item_type or category
     * @return array Array of matching log objects
     */
    public function getByKeyword($limit = 5, $keyword = '')
    {
        global $wpdb;

        // Ensure limit is a positive integer
        $limit = max(1, (int) $limit);

        // If no keyword provided, return empty array
        if (empty($keyword)) {
            return [];
        }

        // Sanitize keyword for use in LIKE queries
        $search_term = '%' . $wpdb->esc_like($keyword) . '%';

        // Query logs where item_type OR category contains the keyword
        $logs = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM %i WHERE item_type LIKE %s OR category LIKE %s ORDER BY date_time DESC LIMIT %d",
                $this->table_name,
                $search_term,
                $search_term,
                $limit
            )
        );

        return $logs ?? [];
    }

    protected function isLoggingEnabled(): bool
    {
        $settings = Settings::getInstance();
        $option = $settings->getOption('enable_native_logging');
        return !empty($option);
    }

    /**
     * Expose logging enabled flag for UI consumers.
     */
    public function isLoggingEnabledPublic(): bool
    {
        return $this->isLoggingEnabled();
    }

    /**
     * Return allowed log types selected in settings. If none saved, default to all.
     * @return array
     */
    protected function getAllowedLogTypes(): array
    {
        $settings = Settings::getInstance();
        $selected = $settings->getOption('log_message_types');
        if (is_string($selected)) {
            // In case it's stored serialized
            $maybe = maybe_unserialize($selected);
            if ($maybe !== false) {
                $selected = $maybe;
            }
        }
        if (is_array($selected) && !empty($selected)) {
            return array_map('strtolower', array_map('sanitize_text_field', $selected));
        }
        // Fallback to all known types when not configured
        $choices = \GriffinForms\Admin\Choices\General::getInstance()->getChoices('log_message_types', false);
        if (is_array($choices) && !empty($choices)) {
            return $choices;
        }
        
        return array('success','error','info','warning');
    }

    /**
     * Standardized category map (single source of truth).
     *
     * Keys must be lowercase.
     */
    protected function getCategoryStandardMap(): array
    {
        return array(
            'autoloader' => 'System: Autoloader',
            'db' => 'System: DB',
            'data' => 'System: Data',
            'install' => 'System: Install',
            'scheduler' => 'System: Scheduler',
            'storage' => 'System: Storage',
            'upload' => 'System: Upload',
            'request_validation' => 'Security: Request Validation',
            'ratelimiter' => 'Security: Rate Limiter',
            'security' => 'Security',
            'conditional_logic' => 'Logic: Conditional',
            'createfield' => 'Field: Create',
            'updatechildren' => 'Data: Update Children',
            'validation' => 'Validation',
            'input' => 'Input',
            'terms' => 'Terms',
            'submission' => 'Submission',
            'frontend submission' => 'Submission: Frontend',
            'submission edit' => 'Submission: Edit',
            'template' => 'Template',
            'import' => 'Import',
            'formtheme' => 'Theme: Form',
            'theme_assets' => 'Theme: Assets',
            'theme_css' => 'Theme: CSS',
            'theme_export' => 'Theme: Export',
            'theme_import' => 'Theme: Import',
            'theme_preview' => 'Theme: Preview',
            'integration (cloudflare_turnstile)' => 'Integration: Cloudflare Turnstile',
            'integration (custom_smtp)' => 'Integration: Custom SMTP',
            'integration (hcaptcha)' => 'Integration: hCaptcha',
            'integration (mailgun)' => 'Integration: Mailgun',
            'integration (mailchimp)' => 'Integration: Mailchimp',
            'integration (pipeline)' => 'Integration: Pipeline',
            'integration (recaptcha)' => 'Integration: reCAPTCHA',
            'integration (sendgrid)' => 'Integration: SendGrid',
            'integration (stripe)' => 'Integration: Stripe',
            'mailgun_config' => 'Integration: Mailgun',
            'mailchimp_config' => 'Integration: Mailchimp',
            'recaptcha_config' => 'Integration: reCAPTCHA',
            'stripe_config' => 'Integration: Stripe',
            'payment integration (gateway)' => 'Payment: Gateway',
            'payment integration (resume)' => 'Payment: Resume',
            'payment integration (stripe)' => 'Payment: Stripe',
            'payment integration (unknown)' => 'Payment: Integration',
            'payment manual' => 'Payment: Manual',
            'payment' => 'Payment',
            'compliance' => 'Compliance',
            'audit' => 'Compliance: Audit',
            'api_audit' => 'API: Audit',
            'ai_broker' => 'AI: Broker',
            'package_lifecycle' => 'Package: Lifecycle',
            'package_compatibility' => 'Package: Compatibility',
            'package_approval' => 'Package: Approval',
            'package_integrity' => 'Package: Integrity',
            'package_provider' => 'Package: Provider',
            'license_state' => 'License: State',
            'license_verification' => 'License: Verification',
            'license_grace' => 'License: Grace',
            'license_activation' => 'License: Activation',
            'package_update' => 'Package: Update',
            'workflow_dependency' => 'Workflow: Dependency',
            'package_diagnostics' => 'Package: Diagnostics',
        );
    }

    /**
     * Normalize category to standardized labels for storage and display.
     */
    protected function normalizeCategory($category): string
    {
        $raw = trim((string) $category);
        if ($raw === '') {
            return '';
        }

        $map = $this->getCategoryStandardMap();
        $key = strtolower($raw);
        if (isset($map[$key])) {
            return $map[$key];
        }

        return $raw;
    }

    protected function expandCategoriesForQuery(array $categories): array
    {
        $values = [];

        foreach ($categories as $category) {
            $raw = trim((string) $category);
            if ($raw === '') {
                continue;
            }

            $normalized = $this->normalizeCategory($raw);
            $values[] = $raw;
            $values[] = $normalized;
        }

        $values = array_map('sanitize_text_field', $values);
        $values = array_values(array_unique(array_filter($values, static function ($value) {
            return $value !== '';
        })));

        return $values;
    }

    /**
     * Expose category map for UI and tooling.
     */
    public function getCategoryStandardMapPublic(): array
    {
        return $this->getCategoryStandardMap();
    }

    /**
     * Expose normalization for UI consumers.
     */
    public function normalizeCategoryPublic($category): string
    {
        return $this->normalizeCategory($category);
    }

    /**
     * Expose allowed log types for read-only usage in renderers.
     */
    public function getAllowedLogTypesPublic(): array
    {
        return $this->getAllowedLogTypes();
    }

    /**
     * Check if a log type is permitted by settings
     */
    protected function isLogTypeAllowed(string $log_type): bool
    {
        $type = strtolower(sanitize_text_field($log_type));
        $allowed = $this->getAllowedLogTypes();
        return in_array($type, $allowed, true);
    }

    /**
     * Prune the logs table according to the current retention setting.
     */
    public function pruneByRetention(): void
    {
        $limit = $this->getRetentionLimit();
        if ($limit === null) {
            return; // keep all logs
        }
        $deleted = $this->deleteOldBeyondLimit($limit);
        if ($deleted > 0) {
            // Record a maintenance info log about pruning
            $this->add('info', 'log', 0, 0, sprintf('Pruned %d old log entries (limit: %d).', (int) $deleted, (int) $limit), 'log_retention');
        }
    }

    /**
     * Map retention setting to a numeric row limit. Returns null for "all".
     */
    protected function getRetentionLimit(): ?int
    {
        $settings = Settings::getInstance();
        $key = (string) $settings->getOption('log_retention');

        switch ($key) {
            case 'latest_1000': return 1000;
            case 'latest_5000': return 5000;
            case 'latest_10000': return 10000;
            case 'latest_50000': return 50000;
            case 'latest_100000': return 100000;
            case 'all':
            default:
                return null;
        }
    }

    /**
     * Delete all rows older than the newest N entries.
     */
    protected function deleteOldBeyondLimit(int $limit): int
    {
        global $wpdb;
        if ($limit <= 0) {
            return 0;
        }
        if (empty($this->table_name)) {
            $this->setTableName();
        }

        // Delete rows not in the newest N ids; subquery wrapped to satisfy MySQL constraints
        $sql = "DELETE FROM %i WHERE id NOT IN (SELECT id FROM (SELECT id FROM %i ORDER BY id DESC LIMIT %d) t)";
        $affected = (int) $wpdb->query($wpdb->prepare($sql, $this->table_name, $this->table_name, $limit));
        return $affected;
    }
}
