<?php

namespace UltraguardPro\support;

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

class SecureRemoteTransport {

    private const BACKOFF_PREFIX = 'ultraguard_remote_backoff_';

    public static function request( string $method, string $url, array $args = [] ) {
        $method   = strtoupper( $method );
        $defaults = [
            'method'      => $method,
            'timeout'     => 15,
            'redirection' => 3,
            'sslverify'   => true,
            'headers'     => [],
        ];

        $args = array_merge( $defaults, $args );

        if ( isset( $args['body'] ) && is_array( $args['body'] ) && self::expects_json( (array) $args['headers'] ) ) {
            $args['body'] = wp_json_encode( $args['body'] );
        }

        return wp_remote_request( $url, $args );
    }

    public static function request_json( string $method, string $url, array $args = [] ): array {
        $response = self::request( $method, $url, $args );

        if ( is_wp_error( $response ) ) {
            return [
                'success'  => false,
                'message'  => $response->get_error_message(),
                'code'     => $response->get_error_code(),
                'response' => null,
                'body'     => null,
                'raw_body' => '',
            ];
        }

        $code     = (int) wp_remote_retrieve_response_code( $response );
        $raw_body = (string) wp_remote_retrieve_body( $response );
        $body     = [];

        if ( '' !== trim( $raw_body ) ) {
            $decoded = json_decode( $raw_body, true );
            if ( json_last_error() !== JSON_ERROR_NONE ) {
                return [
                    'success'  => false,
                    'message'  => 'Invalid JSON response',
                    'code'     => 'invalid_json',
                    'response' => $response,
                    'body'     => null,
                    'raw_body' => $raw_body,
                ];
            }
            $body = is_array( $decoded ) ? $decoded : [];
        }

        if ( $code < 200 || $code >= 300 ) {
            return [
                'success'  => false,
                'message'  => 'HTTP ' . $code,
                'code'     => $code,
                'response' => $response,
                'body'     => $body,
                'raw_body' => $raw_body,
            ];
        }

        return [
            'success'  => true,
            'message'  => '',
            'code'     => $code,
            'response' => $response,
            'body'     => $body,
            'raw_body' => $raw_body,
        ];
    }

    public static function signed_request( string $method, string $url, string $endpoint, array $body, string $signing_key, string $site_url, string $user_agent, array $args = [], string $identity_key = '' ): array {
        $backoff = self::get_backoff_state( $endpoint, $site_url );
        if ( is_array( $backoff ) ) {
            return [
                'success'     => false,
                'response'    => null,
                'request_id'  => '',
                'context'     => [],
                'message'     => sprintf( 'Backoff active for %d second(s).', max( 1, (int) ( $backoff['retry_after'] ?? 1 ) ) ),
                'code'        => 'rate_limited',
                'retry_after' => max( 1, (int) ( $backoff['retry_after'] ?? 1 ) ),
            ];
        }

        $request = self::build_request_context( $method, $endpoint, $body, $signing_key, $site_url, $user_agent, (array) ( $args['headers'] ?? [] ), $identity_key );

        $headers        = $request['headers'];
        $request_args   = $args;
        $request_args['headers'] = $headers;

        if ( ! in_array( strtoupper( $method ), [ 'GET', 'HEAD' ], true ) ) {
            $request_args['body'] = $request['body_json'];
        }

        $response = self::request( $method, $url, $request_args );

        $code = is_wp_error( $response ) ? $response->get_error_code() : (int) wp_remote_retrieve_response_code( $response );

        if ( ! is_wp_error( $response ) ) {
            if ( 429 === $code ) {
                self::store_backoff_from_response( $endpoint, $site_url, $response );
            } elseif ( $code >= 200 && $code < 300 ) {
                self::clear_backoff( $endpoint, $site_url );
            }
        }

        return [
            'success'    => ! is_wp_error( $response ),
            'response'   => $response,
            'request_id' => $request['request_id'],
            'context'    => $request,
            'message'    => is_wp_error( $response ) ? $response->get_error_message() : '',
            'code'       => $code,
        ];
    }

    public static function signed_request_json( string $method, string $url, string $endpoint, array $body, string $signing_key, string $site_url, string $user_agent, array $args = [], bool $strict = false, string $identity_key = '' ): array {
        $result = self::signed_request( $method, $url, $endpoint, $body, $signing_key, $site_url, $user_agent, $args, $identity_key );

        if ( ! $result['success'] ) {
            return array_merge( $result, [ 'body' => null, 'raw_body' => '' ] );
        }

        $verified = self::verify_response( $result['response'], (string) $result['request_id'], $signing_key, $strict );

        if ( $strict && ! $verified ) {
            return array_merge( $result, [
                'success'  => false,
                'message'  => 'Response verification failed',
                'code'     => 'invalid_signature',
                'body'     => null,
                'raw_body' => (string) wp_remote_retrieve_body( $result['response'] ),
            ] );
        }

        $response = $result['response'];
        $code     = (int) wp_remote_retrieve_response_code( $response );
        $raw_body = (string) wp_remote_retrieve_body( $response );
        $json     = [];

        if ( '' !== trim( $raw_body ) ) {
            $decoded = json_decode( $raw_body, true );
            if ( json_last_error() !== JSON_ERROR_NONE ) {
                return array_merge( $result, [
                    'success'  => false,
                    'message'  => 'Invalid JSON response',
                    'code'     => 'invalid_json',
                    'body'     => null,
                    'raw_body' => $raw_body,
                ] );
            }
            $json = is_array( $decoded ) ? $decoded : [];
        }

        if ( $code < 200 || $code >= 300 ) {
            return array_merge( $result, [
                'success'  => false,
                'message'  => 'HTTP ' . $code,
                'code'     => $code,
                'body'     => $json,
                'raw_body' => $raw_body,
            ] );
        }

        return array_merge( $result, [ 'body' => $json, 'raw_body' => $raw_body, 'code' => $code, 'signature_verified' => $verified ] );
    }

    public static function download( string $url, string $destination, array $args = [] ): array {
        $args = array_merge( $args, [
            'stream'   => true,
            'filename' => $destination,
        ] );

        $response = self::request( 'GET', $url, $args );
        if ( is_wp_error( $response ) ) {
            return [
                'success'  => false,
                'message'  => $response->get_error_message(),
                'code'     => $response->get_error_code(),
                'response' => null,
            ];
        }

        $code = (int) wp_remote_retrieve_response_code( $response );
        if ( $code < 200 || $code >= 300 ) {
            return [
                'success'  => false,
                'message'  => 'HTTP ' . $code,
                'code'     => $code,
                'response' => $response,
            ];
        }

        return [
            'success'  => true,
            'message'  => '',
            'code'     => $code,
            'response' => $response,
        ];
    }

    public static function build_request_context( string $method, string $endpoint, array $body, string $signing_key, string $site_url, string $user_agent, array $headers = [], string $identity_key = '' ): array {
        $request_id = wp_generate_uuid4();
        $timestamp  = (string) time();
        $body_json  = self::canonical_json( $body );
        $body_hash  = hash( 'sha256', $body_json );
        $signature  = self::sign_request( $method, $endpoint, $request_id, $timestamp, $body_hash, $site_url, $signing_key );
        $identity_key = '' !== $identity_key ? $identity_key : $signing_key;

        $base_headers = [
            'Content-Type'             => 'application/json',
            'Accept'                   => 'application/json',
            'User-Agent'               => $user_agent,
            'X-License-Key'            => $identity_key,
            'X-Site-URL'               => $site_url,
            'X-UltraGuard-Request-Id'  => $request_id,
            'X-UltraGuard-Timestamp'   => $timestamp,
            'X-UltraGuard-Body-Sha256' => $body_hash,
            'X-UltraGuard-Signature'   => $signature,
            'X-UltraGuard-Signature-Alg' => 'HMAC-SHA256',
        ];

        return [
            'request_id' => $request_id,
            'timestamp'  => $timestamp,
            'body_json'  => $body_json,
            'body_hash'  => $body_hash,
            'headers'    => array_merge( $base_headers, $headers ),
        ];
    }

    public static function verify_response( array $response, string $request_id, string $license_key, bool $strict = false ): bool {
        $signature = (string) wp_remote_retrieve_header( $response, 'x-ultraguard-signature' );
        $timestamp = (string) wp_remote_retrieve_header( $response, 'x-ultraguard-timestamp' );
        $response_request_id = (string) wp_remote_retrieve_header( $response, 'x-ultraguard-request-id' );

        if ( '' === $signature || '' === $timestamp ) {
            return ! $strict;
        }

        if ( '' !== $response_request_id && $response_request_id !== $request_id ) {
            return false;
        }

        $status    = (string) wp_remote_retrieve_response_code( $response );
        $body_hash = hash( 'sha256', (string) wp_remote_retrieve_body( $response ) );
        $expected  = hash_hmac( 'sha256', implode( "\n", [ $request_id, $timestamp, $status, $body_hash ] ), $license_key );

        return hash_equals( $expected, $signature );
    }

    public static function canonical_json( array $body ): string {
        $normalized = self::normalize_value( $body );
        $encoded    = wp_json_encode( $normalized );

        return is_string( $encoded ) ? $encoded : '{}';
    }

    private static function sign_request( string $method, string $endpoint, string $request_id, string $timestamp, string $body_hash, string $site_url, string $license_key ): string {
        $data = implode( "\n", [
            strtoupper( $method ),
            self::normalize_endpoint( $endpoint ),
            $request_id,
            $timestamp,
            $body_hash,
            $site_url,
        ] );

        return hash_hmac( 'sha256', $data, $license_key );
    }

    private static function normalize_endpoint( string $endpoint ): string {
        $endpoint = trim( $endpoint );
        if ( '' === $endpoint ) {
            return '/';
        }

        $parts = wp_parse_url( $endpoint );
        if ( false === $parts ) {
            return $endpoint;
        }

        if ( isset( $parts['path'] ) ) {
            $path = $parts['path'];
            if ( isset( $parts['query'] ) && '' !== $parts['query'] ) {
                $path .= '?' . $parts['query'];
            }

            return $path;
        }

        return $endpoint;
    }

    private static function normalize_value( $value ) {
        if ( ! is_array( $value ) ) {
            return $value;
        }

        if ( self::is_assoc( $value ) ) {
            ksort( $value );
        }

        foreach ( $value as $key => $item ) {
            $value[ $key ] = self::normalize_value( $item );
        }

        return $value;
    }

    private static function is_assoc( array $value ): bool {
        return array_keys( $value ) !== range( 0, count( $value ) - 1 );
    }

    private static function get_backoff_state( string $endpoint, string $site_url ): ?array {
        $state = get_transient( self::backoff_key( $endpoint, $site_url ) );
        if ( ! is_array( $state ) ) {
            return null;
        }

        $until = (int) ( $state['until'] ?? 0 );
        if ( $until <= time() ) {
            self::clear_backoff( $endpoint, $site_url );
            return null;
        }

        $state['retry_after'] = max( 1, $until - time() );

        return $state;
    }

    private static function store_backoff_from_response( string $endpoint, string $site_url, array $response ): void {
        $retry_after = self::parse_retry_after( (string) wp_remote_retrieve_header( $response, 'retry-after' ) );
        if ( $retry_after <= 0 ) {
            $retry_after = 60;
        }

        set_transient(
            self::backoff_key( $endpoint, $site_url ),
            [
                'until'       => time() + $retry_after,
                'retry_after' => $retry_after,
            ],
            $retry_after
        );
    }

    private static function clear_backoff( string $endpoint, string $site_url ): void {
        delete_transient( self::backoff_key( $endpoint, $site_url ) );
    }

    private static function backoff_key( string $endpoint, string $site_url ): string {
        return self::BACKOFF_PREFIX . md5( strtolower( trim( $endpoint ) ) . '|' . strtolower( untrailingslashit( $site_url ) ) );
    }

    private static function parse_retry_after( string $retry_after ): int {
        $retry_after = trim( $retry_after );
        if ( '' === $retry_after ) {
            return 0;
        }

        if ( ctype_digit( $retry_after ) ) {
            return (int) $retry_after;
        }

        $timestamp = strtotime( $retry_after );
        if ( false === $timestamp ) {
            return 0;
        }

        return max( 0, $timestamp - time() );
    }

    private static function expects_json( array $headers ): bool {
        foreach ( $headers as $key => $value ) {
            if ( strtolower( (string) $key ) === 'content-type' && stripos( (string) $value, 'application/json' ) !== false ) {
                return true;
            }
        }

        return false;
    }
}
