<?php

namespace Waypoint\Agent\Rest;

use Waypoint\Agent\Auth\Bearer_Auth;
use Waypoint\Agent\Auth\Hmac_Auth;
use Waypoint\Agent\Base\Thing;
use Waypoint\Agent\Monitor\Status_Data;
use Waypoint\Agent\Rest\Handler\Content;
use Waypoint\Agent\Rest\Handler\Media;
use Waypoint\Agent\Rest\Handler\Monitor;
use Waypoint\Agent\Rest\Handler\Redirects;
use Waypoint\Agent\Rest\Handler\Rotate_Key;
use Waypoint\Agent\Rest\Handler\Site_Context;
use Waypoint\Agent\Rest\Handler\Site_Inventory;

class Router extends Thing {

	private Hmac_Auth $hmac;
	private Bearer_Auth $bearer;

	public function __construct() {
		$this->hmac   = new Hmac_Auth();
		$this->bearer = new Bearer_Auth();
	}

	public function attach_hooks(): void {
		add_action( 'rest_api_init', [ $this, 'register_routes' ] );
		add_filter( 'rest_pre_serve_request', [ $this, 'force_serve' ], 9999, 4 );
	}

	public function register_routes(): void {
		$monitor_ns  = WAYPOINT_MONITOR_REST_NS;
		$agent_ns    = WAYPOINT_MONITOR_AGENT_REST_NS;
		$hmac_check  = [ $this, 'hmac_permission_check' ];
		$mixed_check = [ $this, 'mixed_permission_check' ];

		$monitor_handler = new Monitor( new Status_Data() );
		$status_cb       = [ $monitor_handler, 'handle_status' ];
		$ping_cb         = [ $monitor_handler, 'handle_ping' ];
		$content_handler = new Content();

		// ---- waypoint-monitor/v1 routes ----

		register_rest_route( $monitor_ns, '/status', [
			'methods'             => 'GET',
			'permission_callback' => $mixed_check,
			'callback'            => $status_cb,
		] );
		register_rest_route( $monitor_ns, '/ping', [
			'methods'             => 'GET',
			'permission_callback' => $mixed_check,
			'callback'            => $ping_cb,
		] );
		register_rest_route( $monitor_ns, '/rotate-key', [
			'methods'             => 'POST',
			'permission_callback' => $mixed_check,
			'callback'            => [ new Rotate_Key(), 'handle' ],
			'args'                => [
				'hmac_secret' => [
					'required'          => true,
					'type'              => 'string',
					'sanitize_callback' => 'sanitize_text_field',
					'validate_callback' => [ self::class, 'validate_hmac_secret' ],
				],
			],
		] );

		// ---- waypoint-agent/v1 aliases for monitor routes ----

		register_rest_route( $agent_ns, '/status', [
			'methods'             => 'GET',
			'permission_callback' => $mixed_check,
			'callback'            => $status_cb,
		] );
		register_rest_route( $agent_ns, '/ping', [
			'methods'             => 'GET',
			'permission_callback' => $mixed_check,
			'callback'            => $ping_cb,
		] );

		// ---- waypoint-agent/v1 agent endpoints ----

		register_rest_route( $agent_ns, '/site-inventory', [
			'methods'             => 'GET',
			'permission_callback' => $hmac_check,
			'callback'            => [ new Site_Inventory(), 'handle' ],
		] );
		register_rest_route( $agent_ns, '/site-context', [
			'methods'             => 'GET',
			'permission_callback' => $hmac_check,
			'callback'            => [ new Site_Context(), 'handle' ],
		] );
		register_rest_route( $agent_ns, '/content', [
			'methods'             => 'POST',
			'permission_callback' => $hmac_check,
			'callback'            => [ $content_handler, 'handle_post' ],
			'args'                => [
				'external_id'        => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'title'              => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'slug'               => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_title' ],
				'content'            => [ 'type' => 'string' ],
				'meta_description'   => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'action_type'        => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'improve_of_post_id' => [ 'type' => 'integer' ],
				'expand_of_post_id'  => [ 'type' => 'integer' ],
				'status'             => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'overwrite'          => [ 'type' => 'boolean' ],
			],
		] );
		register_rest_route( $agent_ns, '/content/(?P<external_id>[^/]+)', [
			'methods'             => 'GET',
			'permission_callback' => $hmac_check,
			'callback'            => [ $content_handler, 'handle_get' ],
		] );
		register_rest_route( $agent_ns, '/media', [
			'methods'             => 'POST',
			'permission_callback' => $hmac_check,
			'callback'            => [ new Media(), 'handle' ],
			'args'                => [
				'source_url'        => [ 'type' => 'string', 'sanitize_callback' => 'esc_url_raw' ],
				'alt_text'          => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'slot_reference'    => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'attach_to_post_id' => [ 'type' => 'integer' ],
			],
		] );
		register_rest_route( $agent_ns, '/redirects', [
			'methods'             => 'POST',
			'permission_callback' => $hmac_check,
			'callback'            => [ new Redirects(), 'handle' ],
			'args'                => [
				'from'        => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'to'          => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'code'        => [ 'type' => 'integer', 'default' => 301 ],
				'queued_only' => [ 'type' => 'boolean', 'default' => true ],
			],
		] );
	}

	public function hmac_permission_check( \WP_REST_Request $request ): bool|\WP_Error {
		return $this->hmac->permission_check( $request );
	}

	public function mixed_permission_check( \WP_REST_Request $request ): bool|\WP_Error {
		$has_hmac = '' !== trim( (string) $request->get_header( 'x-waypoint-timestamp' ) )
			|| '' !== trim( (string) $request->get_header( 'x-waypoint-signature' ) );

		return $has_hmac
			? $this->hmac->permission_check( $request )
			: $this->bearer->permission_check( $request );
	}

	public static function validate_hmac_secret( mixed $value ): bool {
		return is_string( $value ) && strlen( $value ) >= 64 && ctype_xdigit( $value );
	}

	public function force_serve( mixed $served, mixed $result, mixed $request, mixed $server ): mixed {
		if ( ! $served || headers_sent() ) {
			return $served;
		}
		if ( ! $request instanceof \WP_REST_Request ) {
			return $served;
		}
		$route = (string) $request->get_route();
		if ( 0 !== strpos( $route, '/' . WAYPOINT_MONITOR_REST_NS . '/' )
			&& 0 !== strpos( $route, '/' . WAYPOINT_MONITOR_AGENT_REST_NS . '/' ) ) {
			return $served;
		}
		return false;
	}
}
