<?php
/**
 * REST API Logs Controller.
 *
 * @package NimblyMail\SMTP
 */

namespace NimblyMail\SMTP\Rest;

use NimblyMail\SMTP\Log\Model;
use NimblyMail\SMTP\Log\Resender;
use NimblyMail\SMTP\Options;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * Class LogsController
 *
 * Handles GET/DELETE of logs via REST, plus resend and truncate operations.
 *
 * @since 1.0.0
 */
class LogsController extends AbstractController {

    /**
     * Register the REST routes.
     *
     * @since 1.0.0
     */
    public function register_routes(): void {
        register_rest_route(
            self::NAMESPACE,
            '/logs',
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array( $this, 'get_logs' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                    'args'                => array(
                        'page'      => array(
                            'type'              => 'integer',
                            'default'           => 1,
                            'sanitize_callback' => 'absint',
                        ),
                        'per_page'  => array(
                            'type'              => 'integer',
                            'default'           => 20,
                            'sanitize_callback' => 'absint',
                        ),
                        'search'    => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                        'status'    => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                        'source'    => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                        'mailer'    => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                        'date_from' => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                        'date_to'   => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                        'orderby'   => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                        'order'     => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                    ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE,
            '/logs/(?P<id>\d+)',
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array( $this, 'get_log' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                    'args'                => array(
                        'id' => array(
                            'type'              => 'integer',
                            'required'          => true,
                            'sanitize_callback' => 'absint',
                        ),
                    ),
                ),
                array(
                    'methods'             => WP_REST_Server::DELETABLE,
                    'callback'            => array( $this, 'delete_log' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                    'args'                => array(
                        'id' => array(
                            'type'              => 'integer',
                            'required'          => true,
                            'sanitize_callback' => 'absint',
                        ),
                    ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE,
            '/logs/bulk',
            array(
                array(
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => array( $this, 'bulk_action' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                    'args'                => array(
                        'action' => array(
                            'type'              => 'string',
                            'required'          => true,
                            'sanitize_callback' => 'sanitize_text_field',
                        ),
                        'ids'    => array(
                            'type'     => 'array',
                            'required' => true,
                            'items'    => array( 'type' => 'integer' ),
                        ),
                    ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE,
            '/logs/stats',
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array( $this, 'get_stats' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE,
            '/logs/(?P<id>\d+)/resend',
            array(
                array(
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => array( $this, 'resend_log' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                    'args'                => array(
                        'id' => array(
                            'type'              => 'integer',
                            'required'          => true,
                            'sanitize_callback' => 'absint',
                        ),
                    ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE,
            '/logs/truncate',
            array(
                array(
                    'methods'             => WP_REST_Server::DELETABLE,
                    'callback'            => array( $this, 'truncate_logs' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                ),
            )
        );
    }

    /**
     * Get list of logs.
     *
     * Returns a plain-text body_preview (30 words max) instead of the full
     * HTML body to mitigate stored XSS risk in list responses. The full body
     * is only available via the single-log detail endpoint.
     *
     * Each prepared row is passed through nimblymail_log_row so Pro add-ons
     * can append or modify fields in the REST output without altering the DB.
     *
     * @since 1.0.0
     *
     * @param WP_REST_Request $request Request.
     *
     * @return WP_REST_Response
     */
    public function get_logs( WP_REST_Request $request ): WP_REST_Response {
        $args = array(
            'page'      => $request->get_param( 'page' ) ? (int) $request->get_param( 'page' ) : 1,
            'per_page'  => $request->get_param( 'per_page' ) ? (int) $request->get_param( 'per_page' ) : 20,
            'search'    => $request->get_param( 'search' ),
            'status'    => $request->get_param( 'status' ),
            'source'    => $request->get_param( 'source' ),
            'mailer'    => $request->get_param( 'mailer' ),
            'date_from' => $request->get_param( 'date_from' ),
            'date_to'   => $request->get_param( 'date_to' ),
            'orderby'   => $request->get_param( 'orderby' ),
            'order'     => $request->get_param( 'order' ),
        );

        $result = Model::find( $args );
        $total  = $result['total'];
        $pages  = ceil( $total / $args['per_page'] );

        // Replace full body with a plain-text preview to mitigate stored XSS in list view.
        $items = array_map(
            function ( $item ) {
                $body                 = $item['body'] ?? '';
                $item['body_preview'] = wp_trim_words( wp_strip_all_tags( $body ), 30 );
                unset( $item['body'] );

                /**
                 * Filters a single log row before it is included in the REST list response.
                 *
                 * Allows Pro add-ons to append or modify fields on individual log rows
                 * in the REST output (e.g. attaching delivery receipts or bounce info)
                 * without altering the database record.
                 *
                 * @since 1.0.0
                 *
                 * @param array $row The prepared log row associative array.
                 *
                 * @return array
                 */
                return (array) apply_filters( 'nimblymail_log_row', $item );
            },
            $result['items']
        );

        $response = new WP_REST_Response( $items );
        $response->header( 'X-WP-Total', (string) $total );
        $response->header( 'X-WP-TotalPages', (string) $pages );

        return $response;
    }

    /**
     * Get single log.
     *
     * @since 1.0.0
     *
     * @param WP_REST_Request $request Request.
     *
     * @return WP_REST_Response|WP_Error
     */
    public function get_log( WP_REST_Request $request ) {
        $id  = (int) $request->get_param( 'id' );
        $row = Model::find_one( $id );

        if ( ! $row ) {
            return new WP_Error( 'not_found', __( 'Log not found.', 'nimblymail-smtp' ), array( 'status' => 404 ) );
        }

        return new WP_REST_Response( $row );
    }

    /**
     * Delete a single log.
     *
     * @since 1.0.0
     *
     * @param WP_REST_Request $request Request.
     *
     * @return WP_REST_Response|WP_Error
     */
    public function delete_log( WP_REST_Request $request ) {
        $id      = (int) $request->get_param( 'id' );
        $deleted = Model::delete( $id );

        if ( ! $deleted ) {
            return new WP_Error( 'delete_failed', __( 'Could not delete log.', 'nimblymail-smtp' ), array( 'status' => 500 ) );
        }

        return new WP_REST_Response( array( 'success' => true ) );
    }

    /**
     * Handle bulk actions (delete or resend).
     *
     * For resend actions, enforces a rate limit of 3 requests per minute
     * per user and a maximum of 50 IDs per request.
     *
     * @since 1.0.0
     *
     * @param WP_REST_Request $request Request.
     *
     * @return WP_REST_Response|WP_Error
     */
    public function bulk_action( WP_REST_Request $request ) {
        $action = sanitize_text_field( $request->get_param( 'action' ) );
        $ids    = $request->get_param( 'ids' );

        if ( ! is_array( $ids ) || empty( $ids ) ) {
            return new WP_Error( 'invalid_ids', __( 'Invalid IDs provided.', 'nimblymail-smtp' ), array( 'status' => 400 ) );
        }

        $ids = array_map( 'absint', $ids );
        $ids = array_filter( $ids );

        if ( empty( $ids ) ) {
            return new WP_Error( 'invalid_ids', __( 'Invalid IDs provided.', 'nimblymail-smtp' ), array( 'status' => 400 ) );
        }

        if ( 'delete' === $action ) {
            $count = Model::bulk_delete( $ids );
            return new WP_REST_Response(
                array(
                    'success' => true,
                    'deleted' => $count,
                )
            );
        }

        if ( 'resend' === $action ) {
            if ( count( $ids ) > 50 ) {
                return new WP_Error(
                    'too_many_ids',
                    __( 'Maximum 50 IDs allowed per bulk resend request.', 'nimblymail-smtp' ),
                    array( 'status' => 400 )
                );
            }

            $rate_error = $this->check_rate_limit( 'bulk_resend_rate', 3, 60 );
            if ( is_wp_error( $rate_error ) ) {
                return $rate_error;
            }

            $result = Resender::bulk_resend( $ids );
            return new WP_REST_Response(
                array(
                    'success' => true,
                    'resent'  => $result['success'],
                    'failed'  => $result['failed'],
                )
            );
        }

        return new WP_Error( 'invalid_action', __( 'Invalid action provided.', 'nimblymail-smtp' ), array( 'status' => 400 ) );
    }

    /**
     * Get log stats.
     *
     * @since 1.0.0
     *
     * @return WP_REST_Response
     */
    public function get_stats(): WP_REST_Response {
        return new WP_REST_Response( Model::get_stats() );
    }

    /**
     * Resend a single log entry.
     *
     * Enforces a rate limit of 10 requests per minute per user.
     *
     * @since 1.0.0
     *
     * @param WP_REST_Request $request Request.
     *
     * @return WP_REST_Response|WP_Error
     */
    public function resend_log( WP_REST_Request $request ) {
        $rate_error = $this->check_rate_limit( 'resend_rate', 10, 60 );
        if ( is_wp_error( $rate_error ) ) {
            return $rate_error;
        }

        $id     = (int) $request->get_param( 'id' );
        $result = Resender::resend( $id );

        if ( ! $result ) {
            return new WP_Error(
                'resend_failed',
                __( 'Could not resend the email. The log entry may not exist or the email address is missing.', 'nimblymail-smtp' ),
                array( 'status' => 422 )
            );
        }

        // The resend succeeded, but delivery may have been intentionally
        // suppressed. Surface a clear message so the admin isn't left thinking
        // a real email went out when it didn't.
        $options    = Options::init();
        $suppressed = false;
        $message    = __( 'Email resent successfully.', 'nimblymail-smtp' );

        if ( $options->get( 'general', 'do_not_send' ) ) {
            $suppressed = true;
            $message    = __( 'Resend recorded, but no email was sent because "Do Not Send" is enabled (Settings → Misc).', 'nimblymail-smtp' );
        } elseif ( $options->get( 'sandbox', 'enabled' ) ) {
            $suppressed = true;
            $message    = ( 'redirect' === $options->get( 'sandbox', 'mode' ) )
                ? __( 'Resend redirected to the Developer Sandbox safe address, not the original recipient.', 'nimblymail-smtp' )
                : __( 'Resend captured by the Developer Sandbox (Log Only) — logged, not delivered to the recipient.', 'nimblymail-smtp' );
        }

        return new WP_REST_Response(
            array(
                'success'    => true,
                'suppressed' => $suppressed,
                'message'    => $message,
            )
        );
    }

    /**
     * Delete all log entries (truncate) with audit logging.
     *
     * Queries the row count before truncation, logs the action to PHP
     * error_log with the acting user's numeric ID, and returns the number
     * of deleted rows in the response. The username is intentionally not
     * written to the log: error.log is world-readable on some shared
     * hosting setups, and the ID is sufficient for an admin to look the
     * user up in `wp_users`.
     *
     * @since 1.0.0
     *
     * @return WP_REST_Response
     */
    public function truncate_logs(): WP_REST_Response {
        global $wpdb;

        // Get row count before truncation for audit trail.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $wpdb->prefix is internal; no user input.
        $count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}nimblymail_logs" );

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $wpdb->prefix is internal; no user input.
        $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}nimblymail_logs" );

        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        error_log(
            sprintf(
                'NimblyMail: User %d truncated %d email logs',
                (int) get_current_user_id(),
                $count
            )
        );

        return new WP_REST_Response(
            array(
                'success'       => true,
                'deleted_count' => $count,
            )
        );
    }
}
