<?php
namespace GriffinForms\Includes\Schedulers;

use GriffinForms\Traits\Singleton;

/**
 * Scheduler responsible for pruning submissions based on per-form save_entries policy.
 */
class SubmissionRetentionCleanup
{
    use Singleton;

    protected $config;
    protected $log;

    protected $cron_hook = 'griffinforms/submissions/retention_cleanup';
    protected $lock_key = 'griffinforms_submissions_retention_lock';

    protected function setConfig()
    {
        $this->config = \GriffinForms\Config::getInstance();
    }

    protected function setLog()
    {
        $this->log = \GriffinForms\Log::getInstance();
    }

    public function register()
    {
        if (empty($this->log)) { $this->setLog(); }

        add_action($this->cron_hook, array($this, 'run'));

        $recurrence = 'hourly';
        $next = wp_next_scheduled($this->cron_hook);
        if (!$next) {
            wp_schedule_event(time() + 60, $recurrence, $this->cron_hook);
            if (!empty($this->log)) {
                $this->log->add('info', 'submission', 0, 0, '[SubmissionRetentionCleanup] Scheduled cron hook: ' . $this->cron_hook . ' (recurrence=' . $recurrence . ')', 'scheduler');
            }
        } else {
            $current_recurrence = wp_get_schedule($this->cron_hook);
            if ($current_recurrence !== $recurrence) {
                wp_clear_scheduled_hook($this->cron_hook);
                wp_schedule_event(time() + 60, $recurrence, $this->cron_hook);
                if (!empty($this->log)) {
                    $this->log->add('info', 'submission', 0, 0, '[SubmissionRetentionCleanup] Rescheduled cron hook with new recurrence=' . $recurrence, 'scheduler');
                }
            }
        }
    }

    public function unschedule()
    {
        wp_clear_scheduled_hook($this->cron_hook);
    }

    public function run()
    {
        if (empty($this->log)) { $this->setLog(); }
        if (empty($this->config)) { $this->setConfig(); }

        if (!empty($this->log)) {
            $this->log->add('info', 'submission', 0, 0, '[SubmissionRetentionCleanup] Cleanup run started', 'scheduler');
        }

        if (!$this->acquireLock()) {
            if (!empty($this->log)) {
                $this->log->add('info', 'submission', 0, 0, '[SubmissionRetentionCleanup] Skipped: another run is in progress', 'scheduler');
            }
            return;
        }

        $started_at  = microtime(true);
        $time_budget = (int) apply_filters('griffinforms/submissions/retention/time_budget', 15);
        $batch_limit = (int) apply_filters('griffinforms/submissions/retention/form_batch_limit', 200);
        $processed   = 0;
        $deleted     = 0;
        $compliance_deleted = 0;

        try {
            $forms = $this->getFormsWithRetention($batch_limit);
            foreach ($forms as $form) {
                $limit = $this->resolveRetentionLimit($form['save_entries'] ?? '');
                if ($limit === null) {
                    continue;
                }
                $deleted += $this->pruneFormSubmissions((int) $form['id'], $limit);
                $processed++;

                if ((microtime(true) - $started_at) >= $time_budget) {
                    break;
                }
            }

            $compliance_forms = $this->getComplianceRetentionForms($batch_limit);
            foreach ($compliance_forms as $form) {
                $days = $this->resolveComplianceRetentionDays($form);
                if ($days <= 0) {
                    continue;
                }
                $compliance_deleted += $this->pruneFormSubmissionsByDate((int) $form['id'], $days);
                $processed++;

                if ((microtime(true) - $started_at) >= $time_budget) {
                    break;
                }
            }
        } catch (\Throwable $t) {
            if (!empty($this->log)) {
                $this->log->add('error', 'submission', 0, 0, '[SubmissionRetentionCleanup] Exception during cleanup: ' . $t->getMessage(), 'scheduler');
            }
        } finally {
            $this->releaseLock();
        }

        if (!empty($this->log)) {
            $log_type = ($deleted > 0 || $compliance_deleted > 0) ? 'success' : 'info';
            $this->log->add($log_type, 'submission', 0, 0,
                sprintf('[SubmissionRetentionCleanup] Cleanup finished: forms=%d, deleted=%d, compliance_deleted=%d', $processed, $deleted, $compliance_deleted),
                'scheduler'
            );
        }
    }

    protected function getFormsWithRetention(int $limit): array
    {
        global $wpdb;
        $table = $this->config->getTable('form');
        if (empty($table)) {
            return [];
        }
        $limit = max(1, $limit);

        // Exclude forms that keep all entries or have no save_entries set.
        $sql = $wpdb->prepare(
            "SELECT id, save_entries FROM {$table} WHERE save_entries IS NOT NULL AND save_entries != '' AND save_entries != %s LIMIT %d",
            'all_entries',
            $limit
        );
        return $wpdb->get_results($sql, ARRAY_A);
    }

    protected function getComplianceRetentionForms(int $limit): array
    {
        global $wpdb;
        $table = $this->config->getTable('form');
        if (empty($table)) {
            return [];
        }
        $limit = max(1, $limit);

        $sql = $wpdb->prepare(
            "SELECT id, compliance_flags FROM {$table} WHERE compliance_mode = 1 LIMIT %d",
            $limit
        );

        return $wpdb->get_results($sql, ARRAY_A);
    }

    protected function resolveRetentionLimit(string $mode): ?int
    {
        $mode = strtolower(trim($mode));
        if ($mode === '' || $mode === 'all_entries') {
            return null;
        }
        if ($mode === 'no_entries') {
            return 0;
        }
        if ($mode === 'recent_ten') {
            return 10;
        }
        if ($mode === 'recent_fifty') {
            return 50;
        }
        if ($mode === 'recent_hundred') {
            return 100;
        }
        if ($mode === 'recent_thousand') {
            return 1000;
        }
        return null;
    }

    protected function pruneFormSubmissions(int $form_id, int $limit): int
    {
        $sql = \GriffinForms\Frontend\Sql\Form::getInstance();
        $count = (int) $sql->submissionCount($form_id, 1);
        if ($count <= $limit) {
            return 0;
        }

        $to_delete = $count - $limit;
        $table = esc_sql($this->config->getTable('submission'));

        global $wpdb;
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM %i WHERE form_id = %d AND status = 1 LIMIT %d",
                $table,
                $form_id,
                $to_delete
            )
        );

        $deleted = (int) $wpdb->rows_affected;
        if ($deleted > 0) {
            \GriffinForms\Includes\Pipelines\SubmissionHistoryManager::getInstance()->deleteOrphanedRevisions();
        }

        return $deleted;
    }

    protected function pruneFormSubmissionsByDate(int $form_id, int $days): int
    {
        if ($form_id <= 0 || $days <= 0) {
            return 0;
        }

        $table = esc_sql($this->config->getTable('submission'));
        $cutoff = gmdate('Y-m-d H:i:s', (int) current_time('timestamp', true) - ($days * DAY_IN_SECONDS));

        global $wpdb;
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM %i WHERE form_id = %d AND status = 1 AND created < %s",
                $table,
                $form_id,
                $cutoff
            )
        );

        $deleted = (int) $wpdb->rows_affected;
        if ($deleted > 0) {
            \GriffinForms\Includes\Pipelines\SubmissionHistoryManager::getInstance()->deleteOrphanedRevisions();
        }

        return $deleted;
    }

    protected function resolveComplianceRetentionDays(array $form): int
    {
        $flags = $form['compliance_flags'] ?? [];
        if (!is_array($flags)) {
            $flags = maybe_unserialize($flags);
        }

        if (!is_array($flags)) {
            $flags = [];
        }

        $flags = \GriffinForms\Includes\Helpers\Compliance::resolveFlags($flags);
        $days = isset($flags['retention_days']) ? absint($flags['retention_days']) : 0;

        return max(0, $days);
    }

    public function runForForm(int $form_id): array
    {
        if (empty($this->log)) { $this->setLog(); }
        if (empty($this->config)) { $this->setConfig(); }

        $form = new \GriffinForms\Items\Form();
        $form->loadItem($form_id);
        $save_entries = (string) $form->getProp('save_entries');
        $limit = $this->resolveRetentionLimit($save_entries);

        if ($limit === null) {
            return [
                'deleted' => 0,
                'limit' => null,
                'save_entries' => $save_entries,
            ];
        }

        $deleted = $this->pruneFormSubmissions($form_id, $limit);

        if (!empty($this->log)) {
            $this->log->add('info', 'submission', $form_id, get_current_user_id(),
                sprintf('[SubmissionRetentionCleanup] Manual cleanup: form=%d, deleted=%d', $form_id, $deleted),
                'scheduler'
            );
        }

        return [
            'deleted' => $deleted,
            'limit' => $limit,
            'save_entries' => $save_entries,
        ];
    }

    protected function acquireLock(): bool
    {
        $now      = time();
        $ttl      = (int) apply_filters('griffinforms/submissions/retention/lock_ttl', 15 * 60);
        $existing = get_transient($this->lock_key);
        if ($existing && ($now - (int) $existing) < $ttl) {
            return false;
        }
        set_transient($this->lock_key, $now, $ttl);
        return true;
    }

    protected function releaseLock(): void
    {
        delete_transient($this->lock_key);
    }
}
