<?php

declare(strict_types=1);

namespace utils\connectiondata;

use utils\type\ConnectionType;

abstract class ConnectionData implements IConnectionData
{
    /** @var array<string, string> */
    protected array $headers = [];

    /** @var array<string, string>|null */
    private static ?array $threadLocalHeaders = null;

    abstract public function getConnectionType(): ConnectionType;
    abstract public function getHostname(): string;
    abstract public function serializeConnectionData(): array;

    public function getHeaders(): array
    {
        $combined = $this->headers;
        if (self::$threadLocalHeaders !== null) {
            foreach (self::$threadLocalHeaders as $key => $value) {
                $combined[$key] = $value;
            }
        }
        return $combined;
    }

    public function setHeaders(array $headers): void
    {
        $this->headers = self::normalizeHeaders($headers);
    }

    public function setThreadLocalHeaders(array $headers): void
    {
        self::$threadLocalHeaders = self::normalizeHeaders($headers);
    }

    public function getThreadLocalHeaders(): array
    {
        return self::$threadLocalHeaders ?? [];
    }

    public function toString(): string
    {
        return $this->connectionTypeAsString() . '|' . $this->getHostname();
    }

    public function __toString(): string
    {
        return $this->toString();
    }

    public function toCanonicalKey(): string
    {
        $key = $this->toString();
        if (count($this->getHeaders()) > 0) {
            $key .= '|h:' . $this->serializeHeadersForKey();
        }
        return $key;
    }

    private function serializeHeadersForKey(): string
    {
        $headers = $this->getHeaders();
        if (empty($headers)) {
            return '';
        }

        ksort($headers);
        $pairs = [];
        foreach ($headers as $key => $value) {
            $pairs[] = (string)$key . '=' . (string)$value;
        }
        return implode(';', $pairs);
    }

    /**
     * @param array<string, string> $headers
     * @return array<string, string>
     */
    private static function normalizeHeaders(array $headers): array
    {
        $normalized = [];
        foreach ($headers as $key => $value) {
            $normalized[strtolower(trim((string)$key))] = trim((string)$value);
        }
        return $normalized;
    }

    private function connectionTypeAsString(): string
    {
        switch ($this->getConnectionType()->getValue()) {
            case ConnectionType::IN_MEMORY:
                return 'inmemory';
            case ConnectionType::TCP:
                return 'tcp';
            case ConnectionType::WEB_SOCKET:
                return 'websocket';
            case ConnectionType::HTTP2:
                return 'http2';
            default:
                return 'unknown';
        }
    }
}
