<?php

declare(strict_types=1);

namespace PrettyLinks\Pro\Rest;

// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_value
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter
// Custom plugin tables (prli_*): table names interpolated from $wpdb->prefix (trusted),
// user values bind through $wpdb->prepare(). No caching: these tables are the source
// of truth for click/redirect data and must read-through. "meta_key"/"meta_value" here
// refer to our own prli_link_metas table, not wp_postmeta.
use PrettyLinks\Pro\Health\HealthChecker;
use PrettyLinks\Pro\Health\Scheduler;
use PrettyLinks\Rest\Controllers\BaseController;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Link Health REST surface.
 *   POST /pretty-links/v1/pro/health/run         — queue a one-off batched
 *     pass of the checker. Work happens on the next WP-Cron tick so the
 *     request returns immediately regardless of link count.
 *   POST /pretty-links/v1/pro/health/check-link  — synchronous single-link
 *     probe; cheap, safe from any UI.
 */
class HealthController extends BaseController
{
    /**
     * Register the Link Health REST routes.
     *
     * @return void
     */
    public function register(): void
    {
        register_rest_route($this->namespace(), '/pro/health/run', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'run'],
                'permission_callback' => $this->permission(),
            ],
        ]);

        register_rest_route($this->namespace(), '/pro/health/check-link/(?P<id>\d+)', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'checkLink'],
                'permission_callback' => $this->permission(),
                'args'                => [
                    'id' => [
                        'type'     => 'integer',
                        'required' => true,
                    ],
                ],
            ],
        ]);
    }

    /**
     * Queue a one-off batched pass of the link health checker.
     *
     * @param WP_REST_Request $request Incoming REST request.
     *
     * @return WP_REST_Response
     */
    public function run(WP_REST_Request $request): WP_REST_Response
    {
        unset($request);
        // Force a full re-check: every link, not just those stale by the
        // frequency window. Without this "Force check now" was a no-op for any
        // link checked within the last 6/28 days, so a stuck "broken" result
        // could never be refreshed no matter how many times it was pressed
        // (#878964).
        //
        // The FORCE_CUTOFF_KEY transient is the single source of truth for a
        // forced pass — HealthChecker reads it on every tick. Freshen it to now
        // on each click (a deliberate "Force check now" always re-checks as of
        // the moment it was pressed, and is never blocked by a leftover cutoff
        // from an earlier stalled pass), then kick an immediate tick. The plain
        // no-arg event coalesces repeat clicks under WP-Cron's dedupe, and
        // because the pass is driven by the transient rather than a cron
        // argument, no tick can outlive or disagree with the pass state.
        set_transient(HealthChecker::FORCE_CUTOFF_KEY, gmdate('Y-m-d H:i:s'), HealthChecker::FORCE_CUTOFF_TTL);
        wp_schedule_single_event(time(), Scheduler::HOOK);

        return new WP_REST_Response([
            'ok'       => true,
            'queued'   => true,
            'queuedAt' => gmdate('Y-m-d H:i:s'),
            'summary'  => $this->summary(),
        ]);
    }

    /**
     * Synchronously probe a single link's health.
     *
     * @param WP_REST_Request $request Incoming REST request.
     *
     * @return WP_REST_Response
     */
    public function checkLink(WP_REST_Request $request): WP_REST_Response
    {
        $linkId = (int) $request['id'];
        /**
         * Health checker service.
         *
         * @var HealthChecker $checker
         */
        $checker = $this->container->get(HealthChecker::class);
        $health  = $checker->checkLink($linkId);
        return new WP_REST_Response([
            'ok'     => true,
            'health' => $health,
        ]);
    }

    /**
     * Build the aggregate link-health summary counts.
     *
     * @return array{total: int, broken: int, lastChecked: string}
     */
    private function summary(): array
    {
        global $wpdb;
        $links = $wpdb->prefix . 'prli_links';
        $metas = $wpdb->prefix . 'prli_link_metas';

        $total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$links} WHERE deleted_at IS NULL");

        $broken = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT m.link_id)
                 FROM {$metas} m
                 INNER JOIN {$links} l ON l.id = m.link_id AND l.deleted_at IS NULL
                 WHERE m.meta_key = %s AND m.meta_value = %s",
                HealthChecker::META_STATUS,
                'inactive'
            )
        );

        $lastChecked = (string) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT MAX(meta_value) FROM {$metas} WHERE meta_key = %s",
                HealthChecker::META_LAST_CHECKED
            )
        );

        return [
            'total'       => $total,
            'broken'      => $broken,
            'lastChecked' => $lastChecked,
        ];
    }
}
