<?php
namespace EasyChat\Library\Messenger;

use PhpWA\Exception\TransportException;
use PhpWA\Runtime\Deadline;
use PhpWA\Transport\StreamConnectorInterface;

 
final class WhatsAppPersonalProxyConnector implements StreamConnectorInterface {
    private $proxy;

    public function __construct(array $proxy) {
        $host = trim((string)($proxy['ip'] ?? ''));
        $port = (int)($proxy['port'] ?? 0);
        if (!$this->validHost($host) || $port < 1 || $port > 65535) {
            throw new \InvalidArgumentException('whatsapp_proxy_endpoint_invalid');
        }
        $this->proxy = [
            'host' => $host,
            'port' => $port,
            'login' => (string)($proxy['login'] ?? ''),
            'password' => (string)($proxy['password'] ?? ''),
        ];
    }

    public function connect(string $host, int $port, bool $secure, Deadline $deadline) {
        $stream = null;
        try {
            if (!$this->validHost($host) || $port < 1 || $port > 65535) {
                throw new \RuntimeException('target_endpoint_invalid');
            }
            $authority = $this->authority($this->proxy['host'], $this->proxy['port']);
            $errno = 0;
            $error = '';
            $stream = @stream_socket_client(
                'tcp://' . $authority,
                $errno,
                $error,
                $deadline->remaining('WhatsApp proxy TCP connection'),
                STREAM_CLIENT_CONNECT,
                stream_context_create()
            );
            if (!is_resource($stream)) {
                throw new \RuntimeException('proxy_connection_failed');
            }
            stream_set_blocking($stream, true);

            $target = $this->authority($host, $port);
            $headers = [
                'CONNECT ' . $target . ' HTTP/1.1',
                'Host: ' . $target,
                'Proxy-Connection: Keep-Alive',
            ];
            if ($this->proxy['login'] !== '') {
                $headers[] = 'Proxy-Authorization: Basic ' . base64_encode(
                    $this->proxy['login'] . ':' . $this->proxy['password']
                );
            }
            $this->writeAll($stream, implode("\r\n", $headers) . "\r\n\r\n", $deadline);
            $response = $this->readHeaders($stream, $deadline);
            $status = strtok($response, "\r\n");
            if (!is_string($status) || preg_match('/^HTTP\/\S+\s+2\d\d\b/', $status) !== 1) {
                throw new \RuntimeException('proxy_connect_rejected');
            }

            if ($secure) {
                stream_context_set_option($stream, 'ssl', 'SNI_enabled', true);
                stream_context_set_option($stream, 'ssl', 'peer_name', $host);
                stream_context_set_option($stream, 'ssl', 'verify_peer', true);
                stream_context_set_option($stream, 'ssl', 'verify_peer_name', true);
                $this->enableTls($stream, $deadline);
            }
            return $stream;
        } catch (\Throwable $error) {
            if (is_resource($stream)) {
                fclose($stream);
            }
             
            throw new TransportException('Configured WhatsApp proxy connection failed.', 0, $error);
        }
    }

    private function writeAll($stream, string $data, Deadline $deadline): void {
        $offset = 0;
        while ($offset < strlen($data)) {
            $this->applyTimeout($stream, $deadline, 'WhatsApp proxy write');
            $written = fwrite($stream, substr($data, $offset));
            if ($written === false || $written === 0) {
                throw new \RuntimeException('proxy_write_failed');
            }
            $offset += $written;
        }
    }

    private function readHeaders($stream, Deadline $deadline): string {
        $headers = '';
        while (strpos($headers, "\r\n\r\n") === false) {
            $this->applyTimeout($stream, $deadline, 'WhatsApp proxy read');
            $chunk = fread($stream, 1);
            if ($chunk === false || ($chunk === '' && feof($stream))) {
                throw new \RuntimeException('proxy_response_incomplete');
            }
            $headers .= $chunk;
            if (strlen($headers) > 16384) {
                throw new \RuntimeException('proxy_headers_too_large');
            }
        }
        return $headers;
    }

    private function enableTls($stream, Deadline $deadline): void {
        $this->applyTimeout($stream, $deadline, 'WhatsApp proxy TLS handshake');
        if (@stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT) !== true) {
            throw new \RuntimeException('proxy_tls_failed');
        }
    }

    private function applyTimeout($stream, Deadline $deadline, string $operation): void {
        $remaining = $deadline->remaining($operation);
        $seconds = (int)floor($remaining);
        $microseconds = (int)(($remaining - $seconds) * 1000000);
        stream_set_timeout($stream, $seconds, max(0, $microseconds));
    }

    private function authority(string $host, int $port): string {
        return (strpos($host, ':') !== false ? '[' . $host . ']' : $host) . ':' . $port;
    }

    private function validHost(string $host): bool {
        if ($host === '' || strlen($host) > 253 || strpos($host, '..') !== false) {
            return false;
        }
        if (filter_var($host, FILTER_VALIDATE_IP) !== false) {
            return true;
        }
        return preg_match('/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/Di', $host) === 1;
    }
}
