<?php

declare(strict_types=1);

namespace App\Services\Supplier\Channel;

use App\Exceptions\Supplier\SupplierAuthException;
use App\Exceptions\Supplier\SupplierClientException;
use App\Exceptions\Supplier\SupplierTransientException;
use App\Mail\SupplierCriticalAlertMail;
use App\Models\Project;
use App\Models\SupplierManualSyncQueue;
use App\Services\Supplier\Channel\Exceptions\TierEscalatedException;
use App\Services\Supplier\Channel\Exceptions\WindowDeferredException;
use App\Services\Supplier\Dto\SupplierProjectDto;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Support\Facades\Log;
use Throwable;

/**
 * Декоратор-оркестратор: ярус 1 (AJAX) → ярус 2 (form-driving) → ярус 3 (manual queue).
 *
 * Spec: docs/superpowers/specs/2026-05-19-supplier-project-channel-failover-design.md §4.4
 *
 * Bridge-методы createProjectForLiderra/updateProjectForLiderra принимают Project
 * (нужен для project_id в очереди яруса 3). Прямые createProject/updateProject
 * сохраняются для интерфейс-совместимости (без эскалации).
 */
final class FailoverProjectChannel implements SupplierProjectChannel
{
    public function __construct(
        private readonly SupplierProjectChannel $tier1,
        private readonly SupplierProjectChannel $tier2,
        private readonly Mailer $mailer,
    ) {}

    public function createProject(SupplierProjectDto $dto): int
    {
        return $this->tier1->createProject($dto);
    }

    public function updateProject(int $externalId, SupplierProjectDto $dto): void
    {
        $this->tier1->updateProject($externalId, $dto);
    }

    public function listProjects(): array
    {
        return $this->tier1->listProjects();
    }

    /**
     * Create с эскалацией: использует Project для project_id в очереди яруса 3.
     */
    public function createProjectForLiderra(Project $project, SupplierProjectDto $dto): int
    {
        // Spec §4.4 шаг 2: портальная сверка через listProjects() до любого create.
        // Защита от дубля при полу-успехе яруса 1 в прошлом запуске.
        try {
            $existing = $this->findOnPortal($dto);
            if ($existing !== null) {
                return $existing;
            }
        } catch (Throwable $e) {
            // listProjects недоступен — продолжаем (ярус-эскалация покроет сбой),
            // но провал дедупа логируем: иначе при полу-успехе яруса 1 в прошлом
            // прогоне молча создастся дубль rt-проекта.
            Log::warning('FailoverProjectChannel: dedup-сверка listProjects провалена', [
                'platform' => $dto->platform,
                'unique_key' => $dto->uniqueKey,
                'error' => $e->getMessage(),
            ]);
        }

        try {
            return $this->tier1->createProject($dto);
        } catch (WindowDeferredException $e) {
            throw $e;
        } catch (SupplierTransientException $e) {
            $this->escalateToTier3($project, 'create', null, $dto, 'portal_unreachable', $e);
        } catch (SupplierClientException|SupplierAuthException $e) {
            try {
                $id = $this->tier2->createProject($dto);
                $this->alertFailoverToForm($project, 'create', $e);

                return $id;
            } catch (Throwable $tier2Error) {
                $this->escalateToTier3(
                    $project, 'create', null, $dto,
                    $this->classifyTier2Failure($tier2Error), $tier2Error,
                );
            }
        }
        // Все ветки выше терминируют (return / throw / escalateToTier3(): never) —
        // явный «unreachable»-throw не нужен (deadCode.unreachable).
    }

    public function updateProjectForLiderra(Project $project, int $externalId, SupplierProjectDto $dto): void
    {
        try {
            $this->tier1->updateProject($externalId, $dto);

            return;
        } catch (WindowDeferredException $e) {
            throw $e;
        } catch (SupplierTransientException $e) {
            $this->escalateToTier3($project, 'update', $externalId, $dto, 'portal_unreachable', $e);
        } catch (SupplierClientException|SupplierAuthException $e) {
            try {
                $this->tier2->updateProject($externalId, $dto);
                $this->alertFailoverToForm($project, 'update', $e);

                return;
            } catch (Throwable $tier2Error) {
                $this->escalateToTier3(
                    $project, 'update', $externalId, $dto,
                    $this->classifyTier2Failure($tier2Error), $tier2Error,
                );
            }
        }
    }

    private function escalateToTier3(
        Project $project,
        string $operation,
        ?int $externalId,
        SupplierProjectDto $dto,
        string $reason,
        Throwable $cause,
    ): never {
        $row = SupplierManualSyncQueue::create([
            'project_id' => $project->id,
            'platform' => $dto->platform,
            'operation' => $operation,
            'external_id' => $externalId !== null ? (string) $externalId : null,
            'payload_snapshot' => [
                'signal_type' => $dto->signalType,
                'unique_key' => $dto->uniqueKey,
                'limit' => $dto->limit,
                'workdays' => $dto->workdays,
                'regions' => $dto->regions,
                'regions_reverse' => $dto->regionsReverse,
                'status' => $dto->status,
            ],
            'failure_reason' => $reason,
            'status' => 'pending',
            'created_at' => now(),
        ]);

        $this->mailer->to((string) config('services.supplier.alert_email'))
            ->queue(new SupplierCriticalAlertMail(
                alertType: 'manual_required',
                details: "Project #{$project->id} ({$dto->platform}/{$dto->uniqueKey}) — {$operation} queued #{$row->id}, reason: {$reason}. Cause: ".mb_substr($cause->getMessage(), 0, 300),
            ));

        throw new TierEscalatedException($row->id, $reason);
    }

    private function alertFailoverToForm(Project $project, string $operation, Throwable $cause): void
    {
        $this->mailer->to((string) config('services.supplier.alert_email'))
            ->queue(new SupplierCriticalAlertMail(
                alertType: 'failover_to_form',
                details: "Project #{$project->id} {$operation}: Tier 1 (AJAX) failed, Tier 2 (browser) succeeded. Cause: ".mb_substr($cause->getMessage(), 0, 300),
            ));
    }

    /**
     * Портальная сверка: ищет уже существующий проект на портале по тройке
     * (platform, signal_type, unique_key). Возвращает external_id найденного
     * или null. Spec §4.4 шаг 2, §7.
     */
    private function findOnPortal(SupplierProjectDto $dto): ?int
    {
        foreach ($this->tier1->listProjects() as $row) {
            if (
                ($row['platform'] ?? null) === $dto->platform
                && ($row['signal_type'] ?? null) === $dto->signalType
                && ($row['unique_key'] ?? null) === $dto->uniqueKey
            ) {
                return (int) ($row['id'] ?? 0) ?: null;
            }
        }

        return null;
    }

    private function classifyTier2Failure(Throwable $e): string
    {
        $msg = mb_strtolower($e->getMessage());
        if (str_contains($msg, 'auth') || str_contains($msg, 'login')) {
            return 'auth_failure';
        }
        if (str_contains($msg, 'selector') || str_contains($msg, 'form')) {
            return 'form_selector_break';
        }

        return 'form_save_error';
    }
}
