<?php

declare(strict_types=1);

namespace App\Support\SupportRequests;

use App\Models\SupportRequest;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;

final class ExternalSupportDeskHandoffService
{
    private const int MAX_TIMEOUT_SECONDS = 5;

    /**
     * @return array{
     *     successful: bool,
     *     external_ticket_reference: ?string,
     *     external_ticket_url: ?string,
     *     failure_summary: ?string
     * }
     */
    public function createTicket(SupportRequest $supportRequest): array
    {
        if (! $this->targetIsConfigured()) {
            return $this->failed('External support desk target is not configured.');
        }

        try {
            $response = Http::timeout($this->timeoutSeconds())
                ->acceptJson()
                ->asJson()
                ->withHeaders($this->headers())
                ->post($this->createUrl(), $this->payloadFor($supportRequest));
        } catch (ConnectionException) {
            return $this->failed('External support desk did not respond before the configured timeout.');
        } catch (RequestException $exception) {
            return $this->failed('External support desk rejected the ticket create request (HTTP '.$exception->response->status().').');
        }

        if (! $response->successful()) {
            return $this->failed('External support desk rejected the ticket create request (HTTP '.$response->status().').');
        }

        $responsePayload = $response->json();
        $responsePayload = is_array($responsePayload) ? $responsePayload : [];

        $reference = $this->normalizeReference(
            data_get($responsePayload, 'ticket_reference')
                ?? data_get($responsePayload, 'external_ticket_reference')
                ?? data_get($responsePayload, 'reference')
                ?? data_get($responsePayload, 'key')
                ?? data_get($responsePayload, 'id'),
            throwOnInvalid: false,
        );

        if ($reference === null) {
            return $this->failed('External support desk did not return a ticket reference.');
        }

        $url = $this->normalizeUrl(
            data_get($responsePayload, 'ticket_url')
                ?? data_get($responsePayload, 'external_ticket_url')
                ?? data_get($responsePayload, 'url')
                ?? data_get($responsePayload, 'web_url')
                ?? data_get($responsePayload, 'html_url'),
            throwOnInvalid: false,
        ) ?? $this->urlFromTemplate($reference);

        return [
            'successful' => true,
            'external_ticket_reference' => $reference,
            'external_ticket_url' => $url,
            'failure_summary' => null,
        ];
    }

    /**
     * @return array{external_ticket_reference: string, external_ticket_url: ?string}
     */
    public function normalizeLinkedTicket(mixed $reference, mixed $url): array
    {
        $normalizedReference = $this->normalizeReference($reference, throwOnInvalid: true);

        if ($normalizedReference === null) {
            throw ValidationException::withMessages([
                'external_ticket_reference' => 'The external ticket reference field is required.',
            ]);
        }

        return [
            'external_ticket_reference' => $normalizedReference,
            'external_ticket_url' => $this->normalizeUrl($url, throwOnInvalid: true) ?? $this->urlFromTemplate($normalizedReference),
        ];
    }

    public function targetIsConfigured(): bool
    {
        return (bool) config('support_desk.target.enabled', false)
            && $this->createUrl() !== null;
    }

    public function targetName(): string
    {
        $name = config('support_desk.target.name', 'External support desk');

        return is_string($name) && trim($name) !== ''
            ? trim($name)
            : 'External support desk';
    }

    public function timeoutSeconds(): int
    {
        $configured = config('support_desk.target.timeout_seconds', self::MAX_TIMEOUT_SECONDS);

        $seconds = is_numeric($configured) ? (int) $configured : self::MAX_TIMEOUT_SECONDS;

        return max(1, min($seconds, self::MAX_TIMEOUT_SECONDS));
    }

    /**
     * @return array{successful: false, external_ticket_reference: null, external_ticket_url: null, failure_summary: string}
     */
    private function failed(string $summary): array
    {
        return [
            'successful' => false,
            'external_ticket_reference' => null,
            'external_ticket_url' => null,
            'failure_summary' => $this->boundedFailureSummary($summary),
        ];
    }

    private function createUrl(): ?string
    {
        return $this->normalizeUrl(config('support_desk.target.create_url'), throwOnInvalid: false);
    }

    /**
     * @return array<string, string>
     */
    private function headers(): array
    {
        $headers = [];
        $token = config('support_desk.target.api_token');

        if (is_string($token) && trim($token) !== '') {
            $headers['Authorization'] = 'Bearer '.trim($token);
        }

        return $headers;
    }

    /**
     * @return array<string, mixed>
     */
    private function payloadFor(SupportRequest $supportRequest): array
    {
        return [
            'support_request' => [
                'internal_reference' => $supportRequest->internal_reference,
                'severity' => $supportRequest->severity,
                'summary' => $supportRequest->summary,
                'reproduction_notes' => $supportRequest->reproduction_notes,
                'contact_name' => $supportRequest->contact_name,
                'contact_email' => $supportRequest->contact_email,
                'primary_context_type' => $supportRequest->primary_context_type,
                'primary_context_id' => $supportRequest->primary_context_type === SupportRequest::PRIMARY_CONTEXT_OPERATION_RUN
                    ? $supportRequest->operation_run_id
                    : $supportRequest->managed_environment_id,
                'workspace_id' => $supportRequest->workspace_id,
                'managed_environment_id' => $supportRequest->managed_environment_id,
                'operation_run_id' => $supportRequest->operation_run_id,
            ],
            'context_envelope' => $supportRequest->context_envelope,
        ];
    }

    private function normalizeReference(mixed $value, bool $throwOnInvalid): ?string
    {
        if (! is_string($value) && ! is_numeric($value)) {
            return null;
        }

        $reference = trim((string) $value);

        if ($reference === '') {
            return null;
        }

        if (mb_strlen($reference) > 128 || preg_match('/\A[A-Za-z0-9][A-Za-z0-9._:-]*\z/', $reference) !== 1) {
            if ($throwOnInvalid) {
                throw ValidationException::withMessages([
                    'external_ticket_reference' => 'The external ticket reference format is invalid.',
                ]);
            }

            return null;
        }

        return $reference;
    }

    private function normalizeUrl(mixed $value, bool $throwOnInvalid): ?string
    {
        if (! is_string($value)) {
            return null;
        }

        $url = trim($value);

        if ($url === '') {
            return null;
        }

        $scheme = parse_url($url, PHP_URL_SCHEME);

        if (! in_array($scheme, ['http', 'https'], true) || filter_var($url, FILTER_VALIDATE_URL) === false) {
            if ($throwOnInvalid) {
                throw ValidationException::withMessages([
                    'external_ticket_url' => 'The external ticket URL must be a valid HTTP or HTTPS URL.',
                ]);
            }

            return null;
        }

        return $url;
    }

    private function urlFromTemplate(string $reference): ?string
    {
        $template = config('support_desk.target.ticket_url_template');

        if (! is_string($template) || trim($template) === '') {
            return null;
        }

        $url = str_replace(
            ['{reference}', '{ticket}'],
            rawurlencode($reference),
            trim($template),
        );

        return $this->normalizeUrl($url, throwOnInvalid: false);
    }

    private function boundedFailureSummary(string $summary): string
    {
        $summary = trim(preg_replace('/\s+/', ' ', $summary) ?? $summary);

        return mb_substr($summary, 0, 500);
    }
}
