<?php

declare(strict_types=1);

namespace App\Support\SupportRequests;

use App\Models\OperationRun;
use App\Models\SupportRequest;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Auth\CapabilityResolver;
use App\Support\Auth\Capabilities;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

final class SupportRequestSubmissionService
{
    public function __construct(
        private readonly CapabilityResolver $capabilityResolver,
        private readonly SupportRequestContextBuilder $supportRequestContextBuilder,
        private readonly SupportRequestReferenceGenerator $supportRequestReferenceGenerator,
        private readonly ExternalSupportDeskHandoffService $externalSupportDeskHandoffService,
        private readonly WorkspaceAuditLogger $workspaceAuditLogger,
    ) {}

    /**
     * @param  array<string, mixed>  $data
     */
    public function submitForTenant(ManagedEnvironment $tenant, User $actor, array $data): SupportRequest
    {
        $this->authorizeCreation($tenant, $actor);

        return $this->submit(
            tenant: $tenant,
            actor: $actor,
            data: $data,
            primaryContextType: SupportRequest::PRIMARY_CONTEXT_TENANT,
            operationRun: null,
        );
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function submitForOperationRun(OperationRun $run, User $actor, array $data): SupportRequest
    {
        $run->loadMissing('tenant.workspace');

        $tenant = $run->tenant;

        if (! $tenant instanceof ManagedEnvironment) {
            abort(404);
        }

        $this->authorizeCreation($tenant, $actor);

        return $this->submit(
            tenant: $tenant,
            actor: $actor,
            data: $data,
            primaryContextType: SupportRequest::PRIMARY_CONTEXT_OPERATION_RUN,
            operationRun: $run,
        );
    }

    private function authorizeCreation(ManagedEnvironment $tenant, User $actor): void
    {
        if (! $this->capabilityResolver->isMember($actor, $tenant)) {
            abort(404);
        }

        if (! $this->capabilityResolver->can($actor, $tenant, Capabilities::SUPPORT_REQUESTS_CREATE)) {
            abort(403);
        }
    }

    /**
     * @param  array<string, mixed>  $data
     */
    private function submit(
        ManagedEnvironment $tenant,
        User $actor,
        array $data,
        string $primaryContextType,
        ?OperationRun $operationRun,
    ): SupportRequest {
        $validated = $this->validate($data);
        $attachDiagnosticSnapshot = $this->capabilityResolver->can($actor, $tenant, Capabilities::SUPPORT_DIAGNOSTICS_VIEW);

        $contextEnvelope = $operationRun instanceof OperationRun
            ? $this->supportRequestContextBuilder->forOperationRun($operationRun, $actor, $attachDiagnosticSnapshot)
            : $this->supportRequestContextBuilder->forTenant($tenant, $actor, $attachDiagnosticSnapshot);

        $contactName = $validated['contact_name'] ?? $this->normalizeNullableString($actor->name) ?? $this->normalizeNullableString($actor->email);
        $contactEmail = $validated['contact_email'] ?? $this->normalizeNullableString($actor->email);
        $connection = SupportRequest::query()->getModel()->getConnection();

        $supportRequest = $connection->transaction(function () use (
            $actor,
            $contactEmail,
            $contactName,
            $contextEnvelope,
            $operationRun,
            $primaryContextType,
            $tenant,
            $validated,
        ): SupportRequest {
            $supportRequest = SupportRequest::query()->create([
                'workspace_id' => (int) $tenant->workspace_id,
                'managed_environment_id' => (int) $tenant->getKey(),
                'operation_run_id' => $operationRun instanceof OperationRun ? (int) $operationRun->getKey() : null,
                'initiated_by_user_id' => (int) $actor->getKey(),
                'internal_reference' => $this->supportRequestReferenceGenerator->generate(),
                'primary_context_type' => $primaryContextType,
                'attachment_mode' => (string) data_get($contextEnvelope, 'attachment_mode', SupportRequest::ATTACHMENT_MODE_CANONICAL_CONTEXT_ONLY),
                'severity' => $validated['severity'],
                'summary' => $validated['summary'],
                'reproduction_notes' => $validated['reproduction_notes'],
                'contact_name' => $contactName,
                'contact_email' => $contactEmail,
                'context_envelope' => $contextEnvelope,
            ]);

            $supportRequest->loadMissing(['tenant.workspace']);

            $this->workspaceAuditLogger->logSupportRequestCreated($supportRequest, $actor);

            return $supportRequest;
        });

        return $this->finalizeExternalHandoff($supportRequest, $actor, $validated);
    }

    /**
     * @param  array{
     *     external_handoff_mode: string,
     *     external_ticket_reference: ?string,
     *     external_ticket_url: ?string
     * }  $validated
     */
    private function finalizeExternalHandoff(SupportRequest $supportRequest, User $actor, array $validated): SupportRequest
    {
        $mode = $validated['external_handoff_mode'];

        if ($mode === SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY) {
            $supportRequest->forceFill([
                'external_handoff_mode' => SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY,
                'external_ticket_reference' => null,
                'external_ticket_url' => null,
                'external_handoff_failure_summary' => null,
            ])->save();

            return $supportRequest->refresh();
        }

        if ($mode === SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET) {
            $linkedTicket = $this->externalSupportDeskHandoffService->normalizeLinkedTicket(
                $validated['external_ticket_reference'],
                $validated['external_ticket_url'],
            );

            $supportRequest->forceFill([
                'external_handoff_mode' => SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET,
                'external_ticket_reference' => $linkedTicket['external_ticket_reference'],
                'external_ticket_url' => $linkedTicket['external_ticket_url'],
                'external_handoff_failure_summary' => null,
            ])->save();

            $supportRequest = $supportRequest->refresh();
            $this->workspaceAuditLogger->logSupportRequestExternalTicketLinked($supportRequest, $actor);

            return $supportRequest;
        }

        $createdTicket = $this->externalSupportDeskHandoffService->createTicket($supportRequest);

        if ($createdTicket['successful']) {
            $supportRequest->forceFill([
                'external_handoff_mode' => SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET,
                'external_ticket_reference' => $createdTicket['external_ticket_reference'],
                'external_ticket_url' => $createdTicket['external_ticket_url'],
                'external_handoff_failure_summary' => null,
            ])->save();

            $supportRequest = $supportRequest->refresh();
            $this->workspaceAuditLogger->logSupportRequestExternalTicketCreated($supportRequest, $actor);

            return $supportRequest;
        }

        $supportRequest->forceFill([
            'external_handoff_mode' => SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET,
            'external_ticket_reference' => null,
            'external_ticket_url' => null,
            'external_handoff_failure_summary' => $createdTicket['failure_summary'],
        ])->save();

        $supportRequest = $supportRequest->refresh();
        $this->workspaceAuditLogger->logSupportRequestExternalHandoffFailed($supportRequest, $actor);

        return $supportRequest;
    }

    /**
     * @return array{
     *     internal_reference: string,
     *     primary_context_type: string,
     *     primary_context_id: int|null,
     *     submitted_at: ?string,
     *     external_handoff_mode: string,
     *     external_ticket_reference: ?string,
     *     external_ticket_url: ?string,
     *     external_handoff_failure_summary: ?string,
     *     has_external_link: bool,
     *     has_failure: bool
     * }|null
     */
    public function latestTenantHandoffSummary(ManagedEnvironment $tenant, User $actor): ?array
    {
        $this->authorizeCreation($tenant, $actor);

        $supportRequest = SupportRequest::query()
            ->where('workspace_id', (int) $tenant->workspace_id)
            ->where('managed_environment_id', (int) $tenant->getKey())
            ->where('primary_context_type', SupportRequest::PRIMARY_CONTEXT_TENANT)
            ->latest('created_at')
            ->latest('id')
            ->first();

        return $supportRequest instanceof SupportRequest
            ? $this->summaryFor($supportRequest)
            : null;
    }

    /**
     * @return array{
     *     internal_reference: string,
     *     primary_context_type: string,
     *     primary_context_id: int|null,
     *     submitted_at: ?string,
     *     external_handoff_mode: string,
     *     external_ticket_reference: ?string,
     *     external_ticket_url: ?string,
     *     external_handoff_failure_summary: ?string,
     *     has_external_link: bool,
     *     has_failure: bool
     * }|null
     */
    public function latestOperationRunHandoffSummary(OperationRun $run, User $actor): ?array
    {
        $run->loadMissing('tenant.workspace');

        $tenant = $run->tenant;

        if (! $tenant instanceof ManagedEnvironment) {
            abort(404);
        }

        $this->authorizeCreation($tenant, $actor);

        $supportRequest = SupportRequest::query()
            ->where('workspace_id', (int) $run->workspace_id)
            ->where('managed_environment_id', (int) $tenant->getKey())
            ->where('primary_context_type', SupportRequest::PRIMARY_CONTEXT_OPERATION_RUN)
            ->where('operation_run_id', (int) $run->getKey())
            ->latest('created_at')
            ->latest('id')
            ->first();

        return $supportRequest instanceof SupportRequest
            ? $this->summaryFor($supportRequest)
            : null;
    }

    /**
     * @return array{
     *     internal_reference: string,
     *     primary_context_type: string,
     *     primary_context_id: int|null,
     *     submitted_at: ?string,
     *     external_handoff_mode: string,
     *     external_ticket_reference: ?string,
     *     external_ticket_url: ?string,
     *     external_handoff_failure_summary: ?string,
     *     has_external_link: bool,
     *     has_failure: bool
     * }
     */
    private function summaryFor(SupportRequest $supportRequest): array
    {
        return [
            'internal_reference' => (string) $supportRequest->internal_reference,
            'primary_context_type' => (string) $supportRequest->primary_context_type,
            'primary_context_id' => $supportRequest->primary_context_type === SupportRequest::PRIMARY_CONTEXT_OPERATION_RUN
                ? (is_numeric($supportRequest->operation_run_id) ? (int) $supportRequest->operation_run_id : null)
                : (is_numeric($supportRequest->managed_environment_id) ? (int) $supportRequest->managed_environment_id : null),
            'submitted_at' => $supportRequest->created_at?->toIso8601String(),
            'external_handoff_mode' => (string) ($supportRequest->external_handoff_mode ?? SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY),
            'external_ticket_reference' => $this->normalizeNullableString($supportRequest->external_ticket_reference),
            'external_ticket_url' => $this->normalizeNullableString($supportRequest->external_ticket_url),
            'external_handoff_failure_summary' => $this->normalizeNullableString($supportRequest->external_handoff_failure_summary),
            'has_external_link' => $supportRequest->hasExternalTicket(),
            'has_failure' => $supportRequest->hasExternalHandoffFailure(),
        ];
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array{
     *     severity: string,
     *     summary: string,
     *     reproduction_notes: ?string,
     *     contact_name: ?string,
     *     contact_email: ?string,
     *     external_handoff_mode: string,
     *     external_ticket_reference: ?string,
     *     external_ticket_url: ?string,
     * }
     */
    private function validate(array $data): array
    {
        $requestedHandoffMode = $this->normalizeNullableString($data['external_handoff_mode'] ?? null)
            ?? SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY;

        if (! $this->externalSupportDeskHandoffService->targetIsConfigured()) {
            $requestedHandoffMode = SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY;
        }

        $validated = validator(
            [
                'severity' => $data['severity'] ?? SupportRequest::SEVERITY_NORMAL,
                'summary' => $data['summary'] ?? null,
                'reproduction_notes' => $data['reproduction_notes'] ?? null,
                'contact_name' => $data['contact_name'] ?? null,
                'contact_email' => $data['contact_email'] ?? null,
                'external_handoff_mode' => $requestedHandoffMode,
                'external_ticket_reference' => $data['external_ticket_reference'] ?? null,
                'external_ticket_url' => $data['external_ticket_url'] ?? null,
            ],
            [
                'severity' => ['required', 'string', Rule::in(SupportRequest::severityValues())],
                'summary' => ['required', 'string'],
                'reproduction_notes' => ['nullable', 'string'],
                'contact_name' => ['nullable', 'string'],
                'contact_email' => ['nullable', 'email'],
                'external_handoff_mode' => ['required', 'string', Rule::in(SupportRequest::externalHandoffModeValues())],
                'external_ticket_reference' => ['nullable', 'string', 'max:255'],
                'external_ticket_url' => ['nullable', 'url', 'max:2048'],
            ],
        )->validate();

        $validated['summary'] = trim((string) $validated['summary']);

        if ($validated['summary'] === '') {
            throw ValidationException::withMessages([
                'summary' => 'The summary field is required.',
            ]);
        }

        $validated['reproduction_notes'] = $this->normalizeNullableString($validated['reproduction_notes'] ?? null);
        $validated['contact_name'] = $this->normalizeNullableString($validated['contact_name'] ?? null);
        $validated['contact_email'] = $this->normalizeNullableString($validated['contact_email'] ?? null);
        $validated['external_ticket_reference'] = $this->normalizeNullableString($validated['external_ticket_reference'] ?? null);
        $validated['external_ticket_url'] = $this->normalizeNullableString($validated['external_ticket_url'] ?? null);

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

        if ($validated['external_handoff_mode'] === SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET) {
            $this->externalSupportDeskHandoffService->normalizeLinkedTicket(
                $validated['external_ticket_reference'],
                $validated['external_ticket_url'],
            );
        }

        if ($validated['external_handoff_mode'] !== SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET) {
            $validated['external_ticket_reference'] = null;
            $validated['external_ticket_url'] = null;
        }

        return $validated;
    }

    private function normalizeNullableString(mixed $value): ?string
    {
        if (! is_string($value)) {
            return null;
        }

        $normalized = trim($value);

        return $normalized === '' ? null : $normalized;
    }
}
