<?php

declare(strict_types=1);

namespace App\Services\Dto;

use App\Models\SupplierLead;

/**
 * Результат резолва региона лида (LeadRegionResolver, spec §3.3).
 *
 * `subjectCode` — итоговый код субъекта (используется маршрутизатором);
 * `actualSubjectCode` — настоящий резолв (для лога actual_subject_code; на этапе
 * резолва равен subjectCode, подмена региона — концерн RouteSupplierLeadJob §3.10).
 * `source` ∈ dadata|rossvyaz|tag|unknown — ранг см. SOURCE_RANK (CSV-merge §3.12).
 */
final readonly class RegionResolution
{
    /** @var array<string, int> ранг источника для CSV-merge (выше = достовернее) */
    public const SOURCE_RANK = [
        'dadata' => 4,
        'rossvyaz' => 3,
        'tag' => 2,
        'unknown' => 1,
    ];

    /**
     * @param  array<string, mixed>|null  $dadataResponseMasked
     */
    public function __construct(
        public ?int $subjectCode,
        public ?int $actualSubjectCode,
        public string $source,
        public ?string $phoneOperator,
        public ?int $qc,
        public bool $cacheHit,
        public ?array $dadataResponseMasked,
        public ?int $durationMs,
        public bool $rossvyazMatched,
    ) {}

    /**
     * @param  array<string, mixed>|null  $dadataMasked
     */
    public static function make(
        ?int $subjectCode,
        string $source,
        ?string $operator = null,
        ?int $qc = null,
        bool $cacheHit = false,
        ?array $dadataMasked = null,
        ?int $durationMs = null,
        bool $rossvyazMatched = false,
    ): self {
        return new self(
            subjectCode: $subjectCode,
            actualSubjectCode: $subjectCode,
            source: $source,
            phoneOperator: $operator,
            qc: $qc,
            cacheHit: $cacheHit,
            dadataResponseMasked: $dadataMasked,
            durationMs: $durationMs,
            rossvyazMatched: $rossvyazMatched,
        );
    }

    public static function fromTag(?int $subjectCode): self
    {
        return self::make($subjectCode, $subjectCode !== null ? 'tag' : 'unknown');
    }

    /**
     * Восстановление из persistent state лида (retry-идемпотентность §3.11) — без DaData-вызова.
     */
    public static function fromSupplierLead(SupplierLead $lead): self
    {
        return self::make(
            subjectCode: $lead->resolved_subject_code !== null ? (int) $lead->resolved_subject_code : null,
            source: (string) ($lead->region_source ?? 'unknown'),
            operator: $lead->phone_operator,
            qc: $lead->dadata_qc !== null ? (int) $lead->dadata_qc : null,
        );
    }

    public function withCacheHit(bool $hit): self
    {
        return new self(
            subjectCode: $this->subjectCode,
            actualSubjectCode: $this->actualSubjectCode,
            source: $this->source,
            phoneOperator: $this->phoneOperator,
            qc: $this->qc,
            cacheHit: $hit,
            dadataResponseMasked: null, // §3.11: cache-hit лог не несёт masked-ответ
            durationMs: $this->durationMs,
            rossvyazMatched: $this->rossvyazMatched,
        );
    }

    /**
     * Версия для записи в кэш (§7.3): без per-call полей (masked-ответ, длительность, cache-флаг).
     */
    public function forCache(): self
    {
        return new self(
            subjectCode: $this->subjectCode,
            actualSubjectCode: $this->actualSubjectCode,
            source: $this->source,
            phoneOperator: $this->phoneOperator,
            qc: $this->qc,
            cacheHit: false,
            dadataResponseMasked: null,
            durationMs: null,
            rossvyazMatched: $this->rossvyazMatched,
        );
    }
}
