<?php
/**
 * Audit Ajax Controller.
 *
 *   - platform_helm_audit_scan   — process one chunk (AJAX, looped client-side)
 *   - platform_helm_audit_clear  — empty the audit table
 *   - platform_helm_audit_export — stream the worklist as CSV (admin-post)
 *
 * All require nonce + master-admin.
 *
 * @package Platform\Helm\Modules\Content_Auditor
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\Content_Auditor;

use Platform\Helm\Core\Authorization;
use Platform\Helm\Core\LlmClient;
use Platform\Helm\Modules\GSC\GscQueryStore;

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

final class AuditAjaxController {

	public const NONCE_ACTION = 'platform_helm_audit';

	public function __construct(
		private AuditStore $store,
		private AuditScanner $scanner
	) {}

	public function register_hooks(): void {
		add_action( 'wp_ajax_platform_helm_audit_scan',  [ $this, 'ajax_scan' ] );
		add_action( 'wp_ajax_platform_helm_audit_clear', [ $this, 'ajax_clear' ] );
		add_action( 'wp_ajax_platform_helm_audit_mark',    [ $this, 'ajax_mark' ] );
		add_action( 'wp_ajax_platform_helm_audit_noindex', [ $this, 'ajax_noindex' ] );
		add_action( 'wp_ajax_platform_helm_audit_trash',   [ $this, 'ajax_trash' ] );
		add_action( 'wp_ajax_platform_helm_audit_brief',   [ $this, 'ajax_brief' ] );
		add_action( 'admin_post_platform_helm_audit_export', [ $this, 'export_csv' ] );
	}

	/** Read up to 50 post IDs from the request. */
	private function ids_from_request(): array {
		$raw = isset( $_POST['ids'] ) ? (array) wp_unslash( $_POST['ids'] ) : []; // phpcs:ignore WordPress.Security.ValidationSanitization
		$ids = array_values( array_unique( array_filter( array_map( 'intval', $raw ) ) ) );
		return array_slice( $ids, 0, 50 ); // hard cap per request to protect the server.
	}

	/**
	 * AI Refresh Brief: a concrete editorial plan for one post, grounded in its
	 * content and the queries it already ranks for. Admin-triggered, one post.
	 */
	public function ajax_brief(): void {
		$this->verify();
		$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		$post    = $post_id > 0 ? get_post( $post_id ) : null;
		if ( ! $post ) {
			wp_send_json_error( [ 'message' => __( 'Post not found.', 'platform-helm' ) ] );
		}

		$title   = get_the_title( $post );
		$content = wp_strip_all_tags( strip_shortcodes( (string) $post->post_content ) );
		$content = mb_substr( trim( (string) preg_replace( '/\s+/', ' ', $content ) ), 0, 6000 );

		$query_lines = '';
		$top         = ( new GscQueryStore() )->top_queries_for_posts( [ $post_id ], 5 );
		foreach ( (array) ( $top[ $post_id ] ?? [] ) as $q ) {
			$query_lines .= '- ' . (string) $q['query'] . ' (position ' . number_format( (float) $q['position'], 1 ) . ")\n";
		}
		if ( '' === $query_lines ) {
			$query_lines = "(no Search Console query data yet — base the brief on the content and topic)\n";
		}

		$system = 'You are an SEO content strategist for this website. '
			. 'Write a tight, actionable REFRESH BRIEF so this existing article can climb for the queries it already ranks for. '
			. "Use these plain-text sections, each with a short heading:\n"
			. "TARGET INTENT — what the searcher actually wants.\n"
			. "WHAT'S MISSING OR OUTDATED — specific gaps vs the intent.\n"
			. "SECTIONS TO ADD/UPDATE — a bullet list of concrete H2s.\n"
			. "QUICK WINS — title, intro, internal links, freshness/date, schema.\n"
			. 'Be specific to the listed queries and the actual page content. No fluff, no preamble.';
		$user = 'Title: ' . $title . "\n\nQueries it ranks for:\n" . $query_lines . "\nArticle content (may be truncated):\n" . $content;

		$res = LlmClient::chat(
			[
				[ 'role' => 'system', 'content' => $system ],
				[ 'role' => 'user', 'content' => $user ],
			],
			[ 'max_tokens' => 750, 'temperature' => 0.5 ]
		);
		if ( is_wp_error( $res ) ) {
			wp_send_json_error( [ 'message' => $res->get_error_message() ] );
		}
		wp_send_json_success( [ 'brief' => $res ] );
	}

	public function ajax_mark(): void {
		$this->verify();
		$handled = ! empty( $_POST['handled'] );
		$n       = $this->store->set_handled( $this->ids_from_request(), $handled );
		wp_send_json_success( [
			'affected' => $n,
			'message'  => $handled
				? sprintf( /* translators: %d count */ _n( '%d post marked done.', '%d posts marked done.', $n, 'platform-helm' ), $n )
				: sprintf( /* translators: %d count */ _n( '%d post reopened.', '%d posts reopened.', $n, 'platform-helm' ), $n ),
		] );
	}

	public function ajax_noindex(): void {
		$this->verify();
		$ids = $this->ids_from_request();
		$n   = 0;
		foreach ( $ids as $id ) {
			// Yoast: '1' = noindex. Reversible — set back to '2' (index) or '' (default) to undo.
			update_post_meta( $id, '_yoast_wpseo_meta-robots-noindex', '1' );
			$n++;
		}
		wp_send_json_success( [
			'affected' => $n,
			'message'  => sprintf( /* translators: %d count */ _n( '%d post set to noindex.', '%d posts set to noindex.', $n, 'platform-helm' ), $n ),
		] );
	}

	public function ajax_trash(): void {
		$this->verify();
		if ( ! current_user_can( 'delete_others_posts' ) ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission to remove posts.', 'platform-helm' ) ], 403 );
		}
		$ids    = $this->ids_from_request();
		$target = isset( $_POST['redirect_target'] ) ? trim( sanitize_text_field( wp_unslash( $_POST['redirect_target'] ) ) ) : '';
		$want_redirect = ( '' !== $target );
		$can_redirect  = $want_redirect && Redirects::available();

		$trashed    = 0;
		$redirected = 0;
		foreach ( $ids as $id ) {
			if ( $can_redirect ) {
				// Capture the live URL BEFORE trashing (trashed permalinks change).
				$perma = get_permalink( $id );
				if ( $perma ) {
					$origin = (string) wp_make_link_relative( $perma );
					if ( Redirects::create_301( $origin, $target ) ) {
						$redirected++;
					}
				}
			}
			if ( wp_trash_post( $id ) ) {
				$trashed++;
			}
		}
		if ( $trashed > 0 ) {
			$this->store->set_handled( $ids, true );
		}

		$msg = sprintf( /* translators: %d count */ _n( '%d post moved to Trash (restorable for 30 days).', '%d posts moved to Trash (restorable for 30 days).', $trashed, 'platform-helm' ), $trashed );
		if ( $want_redirect ) {
			$msg .= ' ' . ( $can_redirect
				? sprintf( /* translators: %d count */ _n( '%d Yoast 301 redirect created.', '%d Yoast 301 redirects created.', $redirected, 'platform-helm' ), $redirected )
				: __( 'Note: Yoast redirect manager not detected — no redirects were created. Set them manually.', 'platform-helm' ) );
		}

		wp_send_json_success( [ 'affected' => $trashed, 'redirected' => $redirected, 'message' => $msg ] );
	}

	public function ajax_scan(): void {
		$this->verify();
		$offset = isset( $_POST['offset'] ) ? (int) $_POST['offset'] : 0;
		$reset  = ! empty( $_POST['reset'] );
		if ( $reset && 0 === $offset ) {
			$this->store->clear();
		}
		$result = $this->scanner->scan_chunk( $offset, 100 );
		wp_send_json_success( [
			'processed' => $result['processed'],
			'offset'    => $result['offset'],
			'total'     => $this->scanner->total_posts(),
		] );
	}

	public function ajax_clear(): void {
		$this->verify();
		$this->store->clear();
		wp_send_json_success( [ 'message' => __( 'Audit cleared.', 'platform-helm' ) ] );
	}

	public function export_csv(): void {
		if ( ! Authorization::is_master_admin() ) {
			wp_die( esc_html__( 'Not allowed.', 'platform-helm' ) );
		}
		check_admin_referer( self::NONCE_ACTION . '_export' );

		$rows = $this->store->all();
		nocache_headers();
		header( 'Content-Type: text/csv; charset=utf-8' );
		header( 'Content-Disposition: attachment; filename=content-audit-' . gmdate( 'Y-m-d' ) . '.csv' );

		$out = fopen( 'php://output', 'w' );
		fputcsv( $out, [ 'Post ID', 'Title', 'URL', 'Recommendation', 'Score', 'Words', 'Featured image', 'SEO meta', 'Age (days)', 'Clicks', 'Impressions', 'CTR %', 'Avg position', 'Reasons' ] );
		foreach ( $rows as $r ) {
			fputcsv( $out, [
				$r['post_id'],
				$r['title'],
				$r['url'],
				$r['recommendation'],
				$r['score'],
				$r['word_count'],
				( (int) $r['has_image'] ) ? 'yes' : 'no',
				( (int) $r['has_meta'] ) ? 'yes' : 'no',
				$r['age_days'],
				$r['clicks'],
				$r['impressions'],
				round( (float) $r['ctr'] * 100, 2 ),
				round( (float) $r['position'], 1 ),
				$r['reasons'],
			] );
		}
		fclose( $out );
		exit;
	}

	private function verify(): void {
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );
		Authorization::require_master_admin_ajax();
	}
}
