<?php
/**
 * Pair Site — handshake with the PressConduit relay.
 *
 * Posts {site_url, plugin_token} to the relay's /conduit/pair endpoint and
 * stores the returned customer_id + connector_url so the admin can display
 * the URL the customer pastes into Claude.
 */

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

class PCD_PC_Pair {

	const OPT_CUSTOMER_ID  = 'pcd_pc_customer_id';
	const OPT_CONNECTOR    = 'pcd_pc_connector_url';
	const OPT_PAIRED_AT    = 'pcd_pc_paired_at';
	const OPT_LAST_ERROR   = 'pcd_pc_pair_last_error';

	/**
	 * The backend URL is hardcoded — customers should never have to think
	 * about where the relay lives. For local development override with:
	 *
	 *   define( 'PCD_PC_DEFAULT_BACKEND_URL', 'https://your-dev.vercel.app' );
	 *
	 * in wp-config.php before this plugin loads.
	 */
	public static function backend_url(): string {
		$url = defined( 'PCD_PC_DEFAULT_BACKEND_URL' ) ? PCD_PC_DEFAULT_BACKEND_URL : '';
		return rtrim( (string) $url, '/' );
	}

	public static function customer_id(): string {
		return (string) get_option( self::OPT_CUSTOMER_ID, '' );
	}

	public static function connector_url(): string {
		return (string) get_option( self::OPT_CONNECTOR, '' );
	}

	public static function paired_at(): int {
		return (int) get_option( self::OPT_PAIRED_AT, 0 );
	}

	public static function is_paired(): bool {
		return '' !== self::customer_id() && '' !== self::connector_url();
	}

	public static function last_error(): string {
		return (string) get_option( self::OPT_LAST_ERROR, '' );
	}

	/**
	 * Run the pair handshake. Returns true on success, false on failure
	 * (the failure reason is stored in OPT_LAST_ERROR).
	 *
	 * @param string $existing_connector_url Optional. If provided, the relay
	 *   attaches this site to the existing connector bundle instead of
	 *   creating a new one — that's how one connector URL covers many sites.
	 */
	public static function pair( string $existing_connector_url = '' ): bool {
		$backend = self::backend_url();
		if ( ! $backend ) {
			update_option( self::OPT_LAST_ERROR, __( 'Backend URL is not set.', 'pressconduit' ) );
			return false;
		}

		$payload = [
			'site_url'          => site_url(),
			'plugin_token'      => PCD_PC_Auth::get_token(),
			'product'           => 'pressconduit',
			'builder'           => class_exists( 'PCD_PC_Builder' ) ? PCD_PC_Builder::primary() : 'none',
			'wp_rest_namespace' => 'pressconduit/v1',
			// Identity so the relay can attribute this site to its owner instead
			// of seeing an anonymous URL. The WP admin email is the grouping key
			// that ties one person to all their sites (free or paid).
			'admin_email'       => sanitize_email( (string) get_option( 'admin_email', '' ) ),
			'site_title'        => sanitize_text_field( (string) get_bloginfo( 'name' ) ),
		];
		$existing_connector_url = trim( $existing_connector_url );
		if ( '' !== $existing_connector_url ) {
			$payload['existing_connector_url'] = esc_url_raw( $existing_connector_url );
		}

		$response = wp_remote_post( $backend . '/conduit/pair', [
			'headers' => [ 'Content-Type' => 'application/json' ],
			'body'    => wp_json_encode( $payload ),
			'timeout' => 15,
		] );

		if ( is_wp_error( $response ) ) {
			update_option( self::OPT_LAST_ERROR, $response->get_error_message() );
			return false;
		}

		$status = (int) wp_remote_retrieve_response_code( $response );
		$body   = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( $status < 200 || $status >= 300 ) {
			$msg = is_array( $body ) && isset( $body['error'] )
				? $body['error'] . ( isset( $body['error_description'] ) ? ': ' . $body['error_description'] : '' )
				: 'HTTP ' . $status;
			update_option( self::OPT_LAST_ERROR, $msg );
			return false;
		}

		if ( empty( $body['customer_id'] ) || empty( $body['connector_url'] ) ) {
			update_option( self::OPT_LAST_ERROR, __( 'Backend response missing customer_id or connector_url.', 'pressconduit' ) );
			return false;
		}

		update_option( self::OPT_CUSTOMER_ID, sanitize_text_field( $body['customer_id'] ) );
		update_option( self::OPT_CONNECTOR, esc_url_raw( $body['connector_url'] ) );
		update_option( self::OPT_PAIRED_AT, time() );
		delete_option( self::OPT_LAST_ERROR );

		return true;
	}

	/**
	 * Clear local pairing state. The relay still has the record on its side —
	 * use this when rotating tokens or switching backends.
	 */
	public static function reset(): void {
		delete_option( self::OPT_CUSTOMER_ID );
		delete_option( self::OPT_CONNECTOR );
		delete_option( self::OPT_PAIRED_AT );
		delete_option( self::OPT_LAST_ERROR );
	}

	/**
	 * URL the "Connect to Conduit" button opens. The relay's /connect page
	 * verifies the site is paired, sets a browser-session cookie, and shows
	 * the universal MCP URL for the user to paste into Claude.
	 */
	public static function connect_url(): string {
		$backend = self::backend_url();
		if ( ! $backend ) {
			return '';
		}
		return $backend . '/connect?site=' . rawurlencode( site_url() );
	}

	/**
	 * Tell the relay this site is going away (plugin deactivated). Fires from
	 * register_deactivation_hook in the main plugin file so any browser
	 * sessions referencing this site stop seeing it on the next MCP call.
	 */
	public static function notify_disconnect( bool $purge = false ): void {
		$backend = self::backend_url();
		if ( ! $backend ) {
			return;
		}
		$payload = [
			'site_url'     => site_url(),
			'plugin_token' => PCD_PC_Auth::get_token(),
		];
		if ( $purge ) {
			$payload['purge'] = true;
		}
		// Fire-and-forget; deactivation shouldn't block on the network.
		wp_remote_post( $backend . '/disconnect', [
			'headers'  => [ 'Content-Type' => 'application/json' ],
			'body'     => wp_json_encode( $payload ),
			'timeout'  => 5,
			'blocking' => false,
		] );
	}
}
