<?php

declare(strict_types=1);

namespace ProRank\SEO\Core\RestApi;

use ProRank\SEO\Core\Content\H1Auditor;
use ProRank\SEO\Core\Content\H1Repairer;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_Error;

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

/**
 * Customer-facing REST surface for the "H1 tag missing or empty" issue.
 *
 * Source-level repairs only — no global hidden-H1 injector. Every mutating
 * endpoint defaults to a dry run; an actual write requires an explicit
 * dry_run=false. Bulk repair only ever touches high-confidence + fixable
 * items unless the caller explicitly opts into low-confidence.
 */
class H1Controller extends BaseController {

    protected $rest_base = 'h1';

    /** Hard ceiling on how many posts a single repair-all call may touch. */
    private const BULK_MAX = 200;

    /** Read-only rendered verification can cover a larger set than a write. */
    private const VERIFY_MAX = 500;

    /**
     * Mutating a single post requires BOTH the ProRank edit-meta capability
     * AND core edit_post on that specific post — content/Elementor data is
     * real post content, not just SEO meta.
     */
    public function check_repair_single( WP_REST_Request $request ): bool {
        $post_id = (int) $request['id'];
        return $this->check_edit_meta() && $post_id > 0 && current_user_can( 'edit_post', $post_id );
    }

    public function register_routes(): void {
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/scan',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'scan' ],
                    'permission_callback' => [ $this, 'check_run_audits' ],
                    'args'                => [
                        'verify' => [
                            'default'           => false,
                            'sanitize_callback' => 'rest_sanitize_boolean',
                        ],
                        'limit' => [
                            'default'           => self::VERIFY_MAX,
                            'sanitize_callback' => 'absint',
                        ],
                    ],
                ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/post/(?P<id>\d+)',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_post_diagnosis' ],
                    'permission_callback' => [ $this, 'check_run_audits' ],
                    'args'                => [
                        'id' => [
                            'required'          => true,
                            'sanitize_callback' => 'absint',
                        ],
                    ],
                ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/repair/(?P<id>\d+)',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [ $this, 'repair_post' ],
                    'permission_callback' => [ $this, 'check_repair_single' ],
                    'args'                => [
                        'id' => [
                            'required'          => true,
                            'sanitize_callback' => 'absint',
                        ],
                        'dry_run' => [
                            'default'           => true,
                            'sanitize_callback' => 'rest_sanitize_boolean',
                        ],
                        'skip_rendered_check' => [
                            'default'           => false,
                            'sanitize_callback' => 'rest_sanitize_boolean',
                        ],
                    ],
                ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/restore/(?P<id>\d+)',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [ $this, 'restore_post' ],
                    'permission_callback' => [ $this, 'check_repair_single' ],
                    'args'                => [
                        'id' => [
                            'required'          => true,
                            'sanitize_callback' => 'absint',
                        ],
                        'dry_run' => [
                            'default'           => true,
                            'sanitize_callback' => 'rest_sanitize_boolean',
                        ],
                    ],
                ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/repair-all',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [ $this, 'repair_all' ],
                    'permission_callback' => [ $this, 'check_edit_meta' ],
                    'args'                => [
                        'dry_run' => [
                            'default'           => true,
                            'sanitize_callback' => 'rest_sanitize_boolean',
                        ],
                        'limit' => [
                            'default'           => 25,
                            'sanitize_callback' => 'absint',
                        ],
                        'high_confidence_only' => [
                            'default'           => true,
                            'sanitize_callback' => 'rest_sanitize_boolean',
                        ],
                        'include_utility' => [
                            'default'           => false,
                            'sanitize_callback' => 'rest_sanitize_boolean',
                        ],
                    ],
                ],
            ]
        );
    }

    public function scan( WP_REST_Request $request ): WP_REST_Response {
        $auditor = new H1Auditor();
        if ( (bool) $request['verify'] ) {
            $limit = max( 1, min( self::VERIFY_MAX, (int) $request['limit'] ) );
            return new WP_REST_Response( $auditor->verify( $limit ), 200 );
        }
        return new WP_REST_Response( $auditor->scan(), 200 );
    }

    public function get_post_diagnosis( WP_REST_Request $request ) {
        $post_id = (int) $request['id'];
        $post    = get_post( $post_id );
        if ( ! $post ) {
            return new WP_Error( 'prorank_h1_not_found', __( 'Post not found.', 'prorank-seo' ), [ 'status' => 404 ] );
        }

        $auditor   = new H1Auditor();
        $diagnosis = $auditor->diagnose_post( $post_id );

        // Rendered check resolves the "theme already renders an H1" case so the
        // UI can show why a low-confidence item is skipped rather than fixed.
        $rendered = $auditor->verify_rendered( $post_id );

        return new WP_REST_Response(
            [
                'post_id'   => $post_id,
                'title'     => get_the_title( $post_id ),
                'permalink' => get_permalink( $post_id ),
                'edit_link' => admin_url( 'post.php?post=' . $post_id . '&action=edit' ),
                'diagnosis' => $diagnosis,
                'rendered'  => $rendered,
                'has_backup' => is_array( get_post_meta( $post_id, H1Repairer::BACKUP_META_KEY, true ) ),
            ],
            200
        );
    }

    public function repair_post( WP_REST_Request $request ) {
        $post_id = (int) $request['id'];
        if ( ! get_post( $post_id ) ) {
            return new WP_Error( 'prorank_h1_not_found', __( 'Post not found.', 'prorank-seo' ), [ 'status' => 404 ] );
        }

        $dry_run  = (bool) $request['dry_run'];
        $skip     = (bool) $request['skip_rendered_check'];
        $result   = ( new H1Repairer() )->repair_post( $post_id, $dry_run, $skip );
        $result['dry_run'] = $dry_run;
        $result['committed'] = ! $dry_run && ! empty( $result['mutated'] );

        return new WP_REST_Response( $result, 200 );
    }

    public function restore_post( WP_REST_Request $request ) {
        $post_id = (int) $request['id'];
        if ( ! get_post( $post_id ) ) {
            return new WP_Error( 'prorank_h1_not_found', __( 'Post not found.', 'prorank-seo' ), [ 'status' => 404 ] );
        }
        $dry_run = (bool) $request['dry_run'];
        $result  = ( new H1Repairer() )->restore_post( $post_id, $dry_run );
        $result['dry_run'] = $dry_run;

        if ( ( $result['reason'] ?? '' ) === 'no_backup' ) {
            return new WP_Error( 'prorank_h1_no_backup', __( 'No restorable backup exists for this post.', 'prorank-seo' ), [ 'status' => 404, 'result' => $result ] );
        }

        return new WP_REST_Response( $result, 200 );
    }

    public function repair_all( WP_REST_Request $request ) {
        $dry_run = (bool) $request['dry_run'];
        $limit   = max( 1, min( self::BULK_MAX, (int) $request['limit'] ) );
        $high_only = (bool) $request['high_confidence_only'];
        $include_utility = (bool) $request['include_utility'];

        $auditor = new H1Auditor();
        $scan    = $auditor->scan();
        $repairer = new H1Repairer();

        $totals = [
            'dry_run'                => $dry_run,
            'limit'                  => $limit,
            'high_confidence_only'   => $high_only,
            'include_utility'        => $include_utility,
            'examined'               => 0,
            'mutated'                => 0,
            'skipped_not_fixable'    => 0,
            'skipped_low_confidence' => 0,
            'skipped_no_permission'  => 0,
            'skipped_utility'        => 0,
            'skipped_theme_h1'       => 0,
            'skipped_other'          => 0,
            'by_source'              => [],
        ];
        $items = [];

        foreach ( $scan['missing'] as $candidate ) {
            if ( $totals['examined'] >= $limit ) {
                break;
            }
            if ( empty( $candidate['fixable'] ) ) {
                $totals['skipped_not_fixable']++;
                continue;
            }
            // Safety: bulk never mutates low-confidence items unless the caller
            // explicitly disabled the high-confidence-only guard.
            if ( $high_only && ( $candidate['confidence'] ?? 'low' ) !== 'high' ) {
                $totals['skipped_low_confidence']++;
                continue;
            }
            // Noindex / WooCommerce utility pages (Cart, Checkout, My Account)
            // are never mass-repaired unless explicitly included.
            if ( ! $include_utility && ! empty( $candidate['bulk_excluded'] ) ) {
                $totals['skipped_utility']++;
                continue;
            }
            // Bulk must skip every candidate the current user cannot edit —
            // the route cap alone does not authorise editing arbitrary posts.
            if ( ! current_user_can( 'edit_post', (int) $candidate['id'] ) ) {
                $totals['skipped_no_permission']++;
                continue;
            }

            $totals['examined']++;
            $result = $repairer->repair_post( (int) $candidate['id'], $dry_run );

            if ( ! empty( $result['mutated'] ) ) {
                $totals['mutated']++;
                $src = (string) ( $result['source'] ?? 'unknown' );
                $totals['by_source'][ $src ] = ( $totals['by_source'][ $src ] ?? 0 ) + 1;
            } elseif ( ( $result['reason'] ?? '' ) === 'theme_renders_h1' ) {
                $totals['skipped_theme_h1']++;
            } else {
                $totals['skipped_other']++;
            }

            $items[] = [
                'id'      => (int) $candidate['id'],
                'title'   => (string) $candidate['title'],
                'source'  => (string) ( $result['source'] ?? $candidate['source'] ?? '' ),
                'mutated' => ! empty( $result['mutated'] ),
                'reason'  => (string) ( $result['reason'] ?? '' ),
            ];
        }

        return new WP_REST_Response(
            [
                'scanned_at'             => $scan['scanned_at'],
                'total_missing'          => $scan['missing_count'],
                'high_confidence_count'  => $scan['high_confidence_count'],
                'low_confidence_count'   => $scan['low_confidence_count'],
                'totals'                 => $totals,
                'items'                  => $items,
            ],
            200
        );
    }
}
