<?php

declare(strict_types=1);

/**
 * Image Attributes REST API Controller (Task 9).
 *
 *   POST /prorank-seo/v1/image-attributes/dry-run        — preview, no writes
 *   POST /prorank-seo/v1/image-attributes/apply          — write + record run
 *   GET  /prorank-seo/v1/image-attributes/history        — bounded last 50 runs
 *   POST /prorank-seo/v1/image-attributes/rollback/{id}  — conflict-aware restore
 *   GET  /prorank-seo/v1/image-attributes/candidates     — quick query for the UI:
 *                                                          attachment IDs missing alt
 *
 * Per-attachment edit rights are re-checked inside `ImageAttributeHistory`
 * itself, so the bulk endpoints cannot be tricked into writing to
 * attachments the calling user has no rights on, even if the caller
 * passed those IDs in the request body.
 *
 * @package ProRank\SEO\Core\RestApi
 * @since   1.3.1
 */

namespace ProRank\SEO\Core\RestApi;

use ProRank\SEO\Core\Media\ImageAttributeHistory;
use ProRank\SEO\Core\Media\ImageAttributePatternEngine;
use WP_REST_Request;
use WP_REST_Server;
use WP_Error;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}


class ImageAttributesController extends BaseController {

    public function __construct() {
        $this->rest_base = 'image-attributes';
    }

    public function register_routes(): void {
        $bulk_args = [
            'templates' => [
                'type'       => 'object',
                'required'   => true,
                'properties' => [
                    'alt'     => [ 'type' => 'string' ],
                    'title'   => [ 'type' => 'string' ],
                    'caption' => [ 'type' => 'string' ],
                ],
            ],
            'fields' => [
                'type'     => 'array',
                'required' => true,
                'items'    => [
                    'type' => 'string',
                    'enum' => ImageAttributeHistory::ALLOWED_FIELDS,
                ],
            ],
            'mode' => [
                'type'    => 'string',
                'enum'    => [ ImageAttributeHistory::MODE_ONLY_MISSING, ImageAttributeHistory::MODE_OVERWRITE ],
                'default' => ImageAttributeHistory::MODE_ONLY_MISSING,
            ],
            'attachment_ids' => [
                'type'     => 'array',
                'required' => true,
                'items'    => [ 'type' => 'integer', 'minimum' => 1 ],
            ],
        ];

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/dry-run',
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'dry_run' ],
                'permission_callback' => [ $this, 'check_permission_edit_posts' ],
                'args'                => $bulk_args,
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/apply',
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'apply' ],
                'permission_callback' => [ $this, 'check_permission_edit_posts' ],
                // R1 — apply requires a dry_run_id minted by /dry-run; the
                // engine re-verifies that the signature still matches.
                'args'                => array_merge( $bulk_args, [
                    'dry_run_id' => [
                        'type'        => 'string',
                        'required'    => true,
                        'description' => __( 'Token returned by /dry-run; single-use, 5-min TTL.', 'prorank-seo' ),
                    ],
                ] ),
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/history',
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'history' ],
                'permission_callback' => [ $this, 'check_permission_edit_posts' ],
                'args'                => [
                    'with_changes' => [ 'type' => 'boolean', 'default' => false ],
                ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/rollback/(?P<run_id>[A-Za-z0-9_\-]+)',
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'rollback' ],
                'permission_callback' => [ $this, 'check_permission_edit_posts' ],
                'args'                => [
                    'run_id' => [ 'type' => 'string', 'required' => true ],
                ],
            ]
        );

        // Convenience: surface attachment IDs the user could target via
        // the bulk form's "select missing alt" button.
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/candidates',
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'candidates' ],
                'permission_callback' => [ $this, 'check_permission_edit_posts' ],
                'args'                => [
                    'scope' => [
                        'type'    => 'string',
                        'enum'    => [ 'missing_alt', 'has_alt', 'generated' ],
                        'default' => 'missing_alt',
                    ],
                    'limit' => [
                        'type'    => 'integer',
                        'default' => ImageAttributeHistory::RUN_CAP,
                        'minimum' => 1,
                        'maximum' => ImageAttributeHistory::RUN_CAP,
                    ],
                    'offset' => [
                        'type'    => 'integer',
                        'default' => 0,
                        'minimum' => 0,
                    ],
                ],
            ]
        );
    }

    public function dry_run( WP_REST_Request $request ) {
        return $this->try_callback( function () use ( $request ) {
            $args = $this->collect_bulk_args( $request );
            $user_id = function_exists( 'get_current_user_id' ) ? (int) get_current_user_id() : 0;
            $preview = ImageAttributeHistory::dry_run( $args, $user_id );
            return $this->get_success_response( $preview );
        } );
    }

    public function apply( WP_REST_Request $request ) {
        return $this->try_callback( function () use ( $request ) {
            $args = $this->collect_bulk_args( $request );
            $user_id = function_exists( 'get_current_user_id' ) ? (int) get_current_user_id() : 0;
            $dry_run_id = (string) $request->get_param( 'dry_run_id' );
            $result = ImageAttributeHistory::apply( $args, $user_id, $dry_run_id );
            // R1 — surface dry-run gate failures as HTTP 409 so the UI
            // can distinguish "needs preview" from server errors.
            if ( empty( $result['ok'] ) && ! empty( $result['reason'] ) ) {
                return $this->get_error_response(
                    __( 'Apply rejected: dry-run validation failed.', 'prorank-seo' ),
                    409,
                    (string) $result['reason']
                );
            }
            return $this->get_success_response( $result );
        } );
    }

    public function history( WP_REST_Request $request ) {
        return $this->try_callback( function () use ( $request ) {
            $with_changes = (bool) $request->get_param( 'with_changes' );
            $log = ImageAttributeHistory::get_history( $with_changes );
            return $this->get_success_response( [
                'runs'         => $log,
                'history_max'  => ImageAttributeHistory::HISTORY_MAX,
            ] );
        } );
    }

    public function rollback( WP_REST_Request $request ) {
        return $this->try_callback( function () use ( $request ) {
            $run_id = (string) $request->get_param( 'run_id' );
            $result = ImageAttributeHistory::rollback( $run_id );
            return $this->get_success_response( $result );
        } );
    }

    public function candidates( WP_REST_Request $request ) {
        return $this->try_callback( function () use ( $request ) {
            $scope  = (string) $request->get_param( 'scope' );
            $limit  = (int) $request->get_param( 'limit' );
            $offset = (int) $request->get_param( 'offset' );

            $query_args = [
                'post_type'        => 'attachment',
                'post_status'      => 'inherit',
                'post_mime_type'   => 'image',
                'posts_per_page'   => $limit,
                'offset'           => $offset,
                'fields'           => 'ids',
                'no_found_rows'    => true,
                'orderby'          => 'date',
                'order'            => 'DESC',
            ];

            if ( $scope === 'missing_alt' ) {
                $query_args['meta_query'] = [
                    'relation' => 'OR',
                    [ 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ],
                    [ 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ],
                ];
            } elseif ( $scope === 'has_alt' ) {
                $query_args['meta_query'] = [
                    [ 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '!=' ],
                ];
            } elseif ( $scope === 'generated' ) {
                $query_args['meta_query'] = [
                    [ 'key' => ImageAttributeHistory::META_GENERATED, 'value' => '1', 'compare' => '=' ],
                ];
            }

            $query_args = apply_filters( 'prorank_image_attributes_candidates_query_args', $query_args, $scope );

            $query = new \WP_Query( $query_args );
            $ids = $query->posts ?? [];
            return $this->get_success_response( [
                'scope'   => $scope,
                'ids'     => array_map( 'intval', (array) $ids ),
                'count'   => count( $ids ),
                'next'    => count( $ids ) === $limit ? ( $offset + $limit ) : null,
                'cap'     => ImageAttributeHistory::RUN_CAP,
            ] );
        } );
    }

    /**
     * Collect + normalize the bulk-shape arguments from a request.
     */
    private function collect_bulk_args( WP_REST_Request $request ): array {
        $templates = (array) $request->get_param( 'templates' );
        return [
            'templates'      => [
                'alt'     => isset( $templates['alt'] )     ? (string) $templates['alt']     : '',
                'title'   => isset( $templates['title'] )   ? (string) $templates['title']   : '',
                'caption' => isset( $templates['caption'] ) ? (string) $templates['caption'] : '',
            ],
            'fields'         => (array) $request->get_param( 'fields' ),
            'mode'           => (string) $request->get_param( 'mode' ),
            'attachment_ids' => (array) $request->get_param( 'attachment_ids' ),
        ];
    }
}
