<?php
/**
 * REST API Health (Deliverability) Controller.
 *
 * @package NimblyMail\SMTP
 */

namespace NimblyMail\SMTP\Rest;

use NimblyMail\SMTP\Audit\AuditResult;
use NimblyMail\SMTP\Audit\HealthAuditor;
use NimblyMail\SMTP\Plugin;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

defined( 'ABSPATH' ) || exit;

/**
 * Class HealthController
 *
 * Exposes the deliverability audit endpoints. `GET /health/audit` returns
 * the latest snapshot (running an audit on first call when no snapshot
 * exists). `POST /health/audit/run` forces a fresh audit, returning 429
 * when the manual-recheck throttle is engaged.
 *
 * @since 1.0.0
 */
class HealthController extends AbstractController {

    /**
     * Auditor service.
     *
     * @since 1.0.0
     *
     * @var HealthAuditor
     */
    private HealthAuditor $auditor;

    /**
     * Constructor.
     *
     * @since 1.0.0
     *
     * @param HealthAuditor|null $auditor Optional auditor injection (for tests).
     */
    public function __construct( ?HealthAuditor $auditor = null ) {
        $this->auditor = $auditor ?? new HealthAuditor();
    }

    /**
     * Register routes.
     *
     * @since 1.0.0
     *
     * @return void
     */
    public function register_routes(): void {
        register_rest_route(
            self::NAMESPACE,
            '/health/audit',
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array( $this, 'get_audit' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE,
            '/health/audit/run',
            array(
                array(
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => array( $this, 'run_audit' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                ),
            )
        );
    }

    /**
     * GET /health/audit — return the cached snapshot, running an initial
     * audit if no snapshot exists.
     *
     * @since 1.0.0
     *
     * @param WP_REST_Request $request The REST request (unused but signature-required).
     *
     * @return WP_REST_Response
     */
    public function get_audit( WP_REST_Request $request ): WP_REST_Response {
        unset( $request );

        $result = $this->auditor->get_cached_result();
        if ( null === $result ) {
            $result = $this->auditor->audit( true );
        }

        return rest_ensure_response( $this->build_response_body( $result ) );
    }

    /**
     * POST /health/audit/run — force a fresh audit, respecting the
     * manual-recheck throttle.
     *
     * Returns 429 when the throttle is active. On success engages the
     * throttle for `MANUAL_THROTTLE_SECONDS` so subsequent forced runs from
     * the UI pause until the window expires. Background scheduler runs
     * bypass this controller and the throttle entirely.
     *
     * @since 1.0.0
     *
     * @param WP_REST_Request $request The REST request (unused but signature-required).
     *
     * @return WP_REST_Response|WP_Error
     */
    public function run_audit( WP_REST_Request $request ) {
        unset( $request );

        if ( $this->auditor->is_manual_throttled() ) {
            $next        = $this->auditor->get_next_manual_check_at();
            $retry_after = null !== $next ? max( 1, $next - time() ) : HealthAuditor::MANUAL_THROTTLE_SECONDS;

            add_filter(
                'rest_post_dispatch',
                static function ( $response ) use ( $retry_after ) {
                    if ( $response instanceof WP_REST_Response ) {
                        $response->header( 'Retry-After', (string) $retry_after );
                    }
                    return $response;
                }
            );

            return new WP_Error(
                'rest_health_throttled',
                __( 'Manual deliverability check is rate-limited. Try again later.', 'nimblymail-smtp' ),
                array(
                    'status'      => 429,
                    'retry_after' => $retry_after,
                )
            );
        }

        $result = $this->auditor->audit( true );
        $this->auditor->lock_manual_throttle();

        return rest_ensure_response( $this->build_response_body( $result ) );
    }

    /**
     * Build the response payload from an AuditResult.
     *
     * Augments the snapshot with the active mailer's audit requirements
     * (so the UI can render copy-to-clipboard hints) and the throttle state.
     *
     * @since 1.0.0
     *
     * @param AuditResult $result Snapshot to serialize.
     *
     * @return array
     */
    private function build_response_body( AuditResult $result ): array {
        $mailer       = Plugin::instance()->get_processor()->get_active_mailer();
        $requirements = $mailer->get_audit_requirements();

        return array(
            'snapshot'             => $result->to_array(),
            'requirements'         => array(
                'spf_includes'   => array_values( $requirements['spf_includes'] ),
                'dkim_selectors' => array_values( $requirements['dkim_selectors'] ),
                'docs_url'       => $requirements['docs_url'],
            ),
            'is_throttled'         => $this->auditor->is_manual_throttled(),
            'next_manual_check_at' => $this->auditor->get_next_manual_check_at(),
            'mailer_title'         => $mailer->get_title(),
        );
    }
}
