<?php

declare(strict_types=1);

namespace App\Services;

use InvalidArgumentException;

/**
 * Резолв phone → federal district bit для region_mask на projects.
 *
 * Битовое представление 8 ФО (синхронно с projects.region_mask):
 *   1   = Центральный
 *   2   = Северо-Западный
 *   4   = Южный
 *   8   = Северо-Кавказский
 *   16  = Приволжский
 *   32  = Уральский
 *   64  = Сибирский
 *   128 = Дальневосточный
 *   255 = все 8 округов (мобильные / неизвестный код)
 *
 * MVP-стратегия (Plan 2/5):
 *   - Мобильные (9XX) → 255 (мобильник может быть в любом округе по факту;
 *     передаём all-bits, чтобы любой include-фильтр прошёл).
 *   - Известные коды городов — мапим на ФО.
 *   - Неизвестный → 255 (defensive — лучше пропустить, чем потерять).
 *
 * Полный справочник Минсвязи DEF-codes (40K+ строк) — отложен на Plan 5 / future.
 *
 * Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §6 step 2
 */
class PhonePrefixService
{
    private const DISTRICT_CENTRAL = 1;

    private const DISTRICT_NORTHWEST = 2;

    private const DISTRICT_SOUTH = 4;

    private const DISTRICT_NORTH_CAUCASUS = 8;

    private const DISTRICT_VOLGA = 16;

    private const DISTRICT_URAL = 32;

    private const DISTRICT_SIBERIA = 64;

    private const DISTRICT_FAR_EAST = 128;

    private const ALL_DISTRICTS = 0xFF;

    /**
     * 3- или 4-значный код после '7' → district bit.
     *
     * NB: ключи объявлены как numeric strings ('495'), но PHP автоматически
     * приводит numeric-string keys к int. Поэтому фактический тип array<int, int>;
     * lookup через `isset(self::CITY_CODE_MAP[$str])` работает прозрачно
     * (PHP делает обратное приведение string→int при поиске).
     *
     * @var array<int, int>
     */
    private const CITY_CODE_MAP = [
        // Центральный
        '495' => self::DISTRICT_CENTRAL,
        '498' => self::DISTRICT_CENTRAL,
        '499' => self::DISTRICT_CENTRAL,
        '496' => self::DISTRICT_CENTRAL,
        '4922' => self::DISTRICT_CENTRAL, // Владимир (4-digit)
        '484' => self::DISTRICT_CENTRAL,  // Калуга
        // Северо-Западный
        '812' => self::DISTRICT_NORTHWEST,
        '813' => self::DISTRICT_NORTHWEST,
        '814' => self::DISTRICT_NORTHWEST,
        '815' => self::DISTRICT_NORTHWEST,
        '816' => self::DISTRICT_NORTHWEST,
        '818' => self::DISTRICT_NORTHWEST,
        // Южный
        '861' => self::DISTRICT_SOUTH,
        '862' => self::DISTRICT_SOUTH,
        '863' => self::DISTRICT_SOUTH,
        '865' => self::DISTRICT_SOUTH,
        // Северо-Кавказский
        '871' => self::DISTRICT_NORTH_CAUCASUS,
        '872' => self::DISTRICT_NORTH_CAUCASUS,
        '873' => self::DISTRICT_NORTH_CAUCASUS,
        // Приволжский
        '831' => self::DISTRICT_VOLGA,
        '843' => self::DISTRICT_VOLGA,
        '846' => self::DISTRICT_VOLGA,
        '347' => self::DISTRICT_VOLGA,
        '342' => self::DISTRICT_VOLGA,
        // Уральский
        '343' => self::DISTRICT_URAL,
        '345' => self::DISTRICT_URAL,
        '351' => self::DISTRICT_URAL,
        // Сибирский
        '383' => self::DISTRICT_SIBERIA,
        '391' => self::DISTRICT_SIBERIA,
        '381' => self::DISTRICT_SIBERIA,
        '382' => self::DISTRICT_SIBERIA,
        // Дальневосточный
        '423' => self::DISTRICT_FAR_EAST,
        '421' => self::DISTRICT_FAR_EAST,
        '411' => self::DISTRICT_FAR_EAST,
    ];

    /**
     * @throws InvalidArgumentException если phone не соответствует формату 11 цифр с лидирующей 7.
     */
    public function resolveDistrictBit(string $phone): int
    {
        if (! preg_match('/^7\d{10}$/', $phone)) {
            throw new InvalidArgumentException(
                "Phone must be 11 digits starting with 7 (got: {$phone})"
            );
        }

        $codeArea = substr($phone, 1, 3);

        if ($codeArea[0] === '9') {
            return self::ALL_DISTRICTS;
        }

        $code4 = substr($phone, 1, 4);
        if (isset(self::CITY_CODE_MAP[$code4])) {
            return self::CITY_CODE_MAP[$code4];
        }

        return self::CITY_CODE_MAP[$codeArea] ?? self::ALL_DISTRICTS;
    }

    /**
     * @param  int  $regionMask  битмаска projects.region_mask (0..255).
     * @param  string  $regionMode  'include' | 'exclude'.
     */
    public function phoneMatchesRegions(string $phone, int $regionMask, string $regionMode): bool
    {
        $phoneBits = $this->resolveDistrictBit($phone);
        $intersect = $phoneBits & $regionMask;

        return match ($regionMode) {
            'include' => $intersect !== 0,
            'exclude' => $intersect === 0,
            default => throw new InvalidArgumentException("Unknown region_mode: {$regionMode}"),
        };
    }
}
