<?php

namespace Waypoint\Agent\Auth;

use Waypoint\Agent\Dotenv\Manager;

class Hmac_Auth {

	public function get_key(): ?string {
		$from_env = getenv( WAYPOINT_MONITOR_AGENT_KEY_NAME );
		if ( is_string( $from_env ) && strlen( $from_env ) >= 32 ) {
			return $from_env;
		}
		$path = Manager::find_dotenv();
		if ( null === $path ) {
			return null;
		}
		return Manager::get_key( $path, WAYPOINT_MONITOR_AGENT_KEY_NAME );
	}

	public function maybe_generate_key(): bool {
		if ( null !== $this->get_key() ) {
			return true;
		}
		$path = Manager::find_dotenv();
		if ( null === $path ) {
			error_log( 'Waypoint Agent: .env not found — cannot persist WAYPOINT_AGENT_KEY. Searched from ' . ABSPATH );
			return false;
		}
		if ( ! is_writable( $path ) ) {
			error_log( 'Waypoint Agent: .env at ' . $path . ' is not writable — cannot persist WAYPOINT_AGENT_KEY' );
			return false;
		}
		$secret = bin2hex( random_bytes( 32 ) );
		return Manager::append_key( $path, WAYPOINT_MONITOR_AGENT_KEY_NAME, $secret );
	}

	public function permission_check( \WP_REST_Request $request ): bool|\WP_Error {
		$secret = $this->get_key();
		if ( ! is_string( $secret ) || strlen( trim( $secret ) ) < 32 ) {
			return new \WP_Error( 'waypoint_monitor_hmac_unavailable', 'WAYPOINT_AGENT_KEY is not configured.', [ 'status' => 503 ] );
		}
		$secret = trim( $secret );

		$ts_header = $request->get_header( 'x-waypoint-timestamp' );
		if ( '' === (string) $ts_header ) {
			return new \WP_Error( 'waypoint_monitor_missing_timestamp', 'Missing X-Waypoint-Timestamp header.', [ 'status' => 401 ] );
		}
		if ( ! preg_match( '/^\d{10}$/', (string) $ts_header ) ) {
			return new \WP_Error( 'waypoint_monitor_invalid_timestamp', 'Invalid X-Waypoint-Timestamp header.', [ 'status' => 401 ] );
		}

		$timestamp = (int) $ts_header;
		if ( abs( time() - $timestamp ) > 300 ) {
			return new \WP_Error( 'waypoint_monitor_stale_timestamp', 'Timestamp outside allowed skew window.', [ 'status' => 401 ] );
		}

		$sig_header = trim( (string) $request->get_header( 'x-waypoint-signature' ) );
		if ( '' === $sig_header ) {
			return new \WP_Error( 'waypoint_monitor_missing_signature', 'Missing X-Waypoint-Signature header.', [ 'status' => 401 ] );
		}

		$provided = $sig_header;
		if ( preg_match( '/^v1=([0-9a-f]{64})$/i', $sig_header, $m ) ) {
			$provided = $m[1];
		}
		if ( ! preg_match( '/^[0-9a-f]{64}$/i', $provided ) ) {
			return new \WP_Error( 'waypoint_monitor_invalid_signature_format', 'Invalid X-Waypoint-Signature format.', [ 'status' => 401 ] );
		}

		foreach ( $this->canonical_candidates( $request, $timestamp ) as $canonical ) {
			$expected = hash_hmac( 'sha256', $canonical, $secret );
			if ( hash_equals( strtolower( $expected ), strtolower( $provided ) ) ) {
				return true;
			}
		}

		return new \WP_Error( 'waypoint_monitor_invalid_signature', 'HMAC signature verification failed.', [ 'status' => 401 ] );
	}

	private function canonical_candidates( \WP_REST_Request $request, int $timestamp ): array {
		$method = strtoupper( (string) $request->get_method() );
		$route  = '/' . ltrim( (string) $request->get_route(), '/' );

		return array_values( array_unique( [
			sprintf( '%d.%s.%s', $timestamp, $method, $route ),
			sprintf( '%d.%s.%s', $timestamp, $method, '/wp-json' . $route ),
			sprintf( '%d.%s.%s', $timestamp, $method, ltrim( $route, '/' ) ),
		] ) );
	}
}
