<?php

declare(strict_types=1);

namespace Tests\Unit;

use App\Enums\MatchStatus;
use App\Models\Store;
use App\Models\StoreProductTarget;
use App\Services\DataForSeo\NormalizedResult;
use App\Services\DataForSeo\TargetProductMatcher;
use App\Services\DataForSeo\UrlNormalizer;
use Tests\TestCase;

class TargetProductMatcherTest extends TestCase
{
    /**
     * @param  array<string, list<string>>  $knownSellerAliases
     */
    private function makeMatcher(): TargetProductMatcher
    {
        return new TargetProductMatcher(new UrlNormalizer);
    }

    /** @param array<string, mixed> $overrides */
    private function makeNullUrlResult(array $overrides = []): NormalizedResult
    {
        return new NormalizedResult(
            title: $overrides['title'] ?? 'Tortilla de maíz por kilo',
            seller: $overrides['seller'] ?? 'Bodega Aurrera Despensa a tu Casa',
            domain: $overrides['domain'] ?? null,
            directUrl: $overrides['directUrl'] ?? null,
            shoppingUrl: $overrides['shoppingUrl'] ?? 'https://google.com.mx/search?ibp=oshop&prds=headlineOfferDocid:1712851062477582571',
            url: $overrides['url'] ?? 'https://google.com.mx/search?ibp=oshop&prds=headlineOfferDocid:1712851062477582571',
            normalizedUrl: $overrides['normalizedUrl'] ?? null,
            imageUrl: null,
            price: $overrides['price'] ?? 15.5,
            previousPrice: null,
            currency: $overrides['currency'] ?? 'MXN',
            position: $overrides['position'] ?? 2,
            rankGroup: $overrides['rankGroup'] ?? 2,
            rankAbsolute: $overrides['rankAbsolute'] ?? 2,
            dataDocid: $overrides['dataDocid'] ?? '1712851062477582571',
            productId: $overrides['productId'] ?? null,
            gid: $overrides['gid'] ?? null,
            resultType: $overrides['resultType'] ?? 'google_shopping_serp',
            rawPayload: [],
        );
    }

    /** @param array<string, mixed> $overrides */
    private function makeDirectUrlResult(array $overrides = []): NormalizedResult
    {
        return new NormalizedResult(
            title: $overrides['title'] ?? 'Tortilla de maíz por kilo',
            seller: $overrides['seller'] ?? 'Chedraui',
            domain: $overrides['domain'] ?? 'chedraui.com.mx',
            directUrl: $overrides['directUrl'] ?? 'https://www.chedraui.com.mx/tortilla-de-maiz-por-kilo/p?utm_source=google',
            shoppingUrl: $overrides['shoppingUrl'] ?? null,
            url: $overrides['url'] ?? 'https://www.chedraui.com.mx/tortilla-de-maiz-por-kilo/p?utm_source=google',
            normalizedUrl: $overrides['normalizedUrl'] ?? 'https://chedraui.com.mx/tortilla-de-maiz-por-kilo/p',
            imageUrl: null,
            price: $overrides['price'] ?? 24.90,
            previousPrice: null,
            currency: $overrides['currency'] ?? 'MXN',
            position: $overrides['position'] ?? 3,
            rankGroup: $overrides['rankGroup'] ?? 1,
            rankAbsolute: $overrides['rankAbsolute'] ?? 3,
            dataDocid: $overrides['dataDocid'] ?? null,
            productId: $overrides['productId'] ?? null,
            gid: $overrides['gid'] ?? null,
            resultType: $overrides['resultType'] ?? 'google_shopping_serp',
            rawPayload: [],
        );
    }

    /**
     * @param  list<string>  $expectedUrlContains
     * @param  array<string, mixed>  $extra
     */
    private function makeBodegaTarget(
        ?string $expectedUrl = null,
        array $expectedUrlContains = ['bodegaaurrera.com.mx', 'tortilla-de-maiz'],
        array $extra = [],
    ): StoreProductTarget {
        $store = new Store([
            'name' => 'Bodega Aurrera',
            'slug' => 'bodega-aurrera',
            'domains' => ['bodegaaurrera.com.mx'],
            'aliases' => [
                'Bodega Aurrera',
                'Bodega Aurrera Despensa a tu Casa',
                'Bodega Aurrera en línea',
            ],
            'is_active' => true,
        ]);

        $target = new StoreProductTarget(array_merge([
            'name' => 'Tortilla de maíz por kilo - Bodega Aurrera',
            'search_query' => 'bodega aurrera tortillas de maiz 1 kg',
            'expected_url' => $expectedUrl,
            'expected_url_contains' => $expectedUrlContains,
            'expected_title' => 'Tortilla de maíz por kilo',
            'expected_seller' => 'Bodega Aurrera Despensa a tu Casa',
            'expected_domain' => 'bodegaaurrera.com.mx',
            'expected_position' => 2,
            'expected_rank_group' => 2,
            'expected_data_docid' => '1712851062477582571',
            'expected_product_id' => null,
            'expected_gid' => null,
            'expected_shopping_url_contains' => ['headlineOfferDocid:1712851062477582571'],
            'required_keywords' => ['tortilla', 'maiz', 'kilo'],
            'optional_keywords' => ['masa'],
            'negative_keywords' => ['harina', 'tostadas', 'totopos', 'tortillinas', 'paquete'],
            'selection_strategy' => 'url_weighted_match',
            'min_match_score' => 80,
            'is_active' => true,
        ], $extra));

        $target->setRelation('store', $store);

        return $target;
    }

    private function makeChedrauiTarget(
        ?string $expectedUrl = 'https://www.chedraui.com.mx/tortilla-de-maiz-por-kilo/p',
        array $expectedUrlContains = ['chedraui.com.mx', 'tortilla-de-maiz', '/p'],
    ): StoreProductTarget {
        $store = new Store([
            'name' => 'Chedraui',
            'slug' => 'chedraui',
            'domains' => ['chedraui.com.mx'],
            'aliases' => ['Chedraui', 'Chedraui.com.mx', 'Chedraui en línea'],
            'is_active' => true,
        ]);

        $target = new StoreProductTarget([
            'name' => 'Tortilla de maíz por kilo - Chedraui',
            'search_query' => 'chedraui kilo de tortillas de maiz',
            'expected_url' => $expectedUrl,
            'expected_url_contains' => $expectedUrlContains,
            'expected_title' => 'Tortilla de maíz por kilo',
            'expected_seller' => 'Chedraui',
            'expected_domain' => 'chedraui.com.mx',
            'expected_position' => 3,
            'expected_rank_group' => null,
            'expected_data_docid' => null,
            'expected_product_id' => null,
            'expected_gid' => null,
            'expected_shopping_url_contains' => null,
            'required_keywords' => ['tortilla', 'maiz', 'kilo'],
            'optional_keywords' => [],
            'negative_keywords' => ['harina', 'totopos', 'tortillinas', 'paquete'],
            'selection_strategy' => 'url_weighted_match',
            'min_match_score' => 80,
            'is_active' => true,
        ]);

        $target->setRelation('store', $store);

        return $target;
    }

    /** @return array<string, list<string>> */
    private function makeKnownSellerAliases(): array
    {
        return [
            'bodega-aurrera' => [
                'Bodega Aurrera',
                'Bodega Aurrera Despensa a tu Casa',
                'Bodega Aurrera en línea',
            ],
            'soriana' => [
                'Soriana',
                'Soriana.com',
                'Soriana Súper',
                'Soriana Mercado',
            ],
            'chedraui' => [
                'Chedraui',
                'Chedraui.com.mx',
                'Chedraui en línea',
            ],
        ];
    }

    // ── Casos de match con directUrl null (Merchant API) ──────────────────────

    public function test_it_matches_bodega_aurrera_product_when_url_and_domain_are_null(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget();
        $result = $this->makeNullUrlResult();

        $matchResult = $matcher->match($target, [$result], $this->makeKnownSellerAliases());

        $this->assertSame(MatchStatus::Matched, $matchResult->status);
        $this->assertSame('Tortilla de maíz por kilo', $matchResult->selectedResult?->title);
        $this->assertGreaterThanOrEqual(80, $matchResult->score);
    }

    public function test_it_does_not_penalize_expected_url_when_direct_url_is_null(): void
    {
        $matcher = $this->makeMatcher();
        // Target tiene expected_url configurada, pero el resultado no tiene directUrl
        $target = $this->makeBodegaTarget(
            expectedUrl: 'https://www.bodegaaurrera.com.mx/tortilla-de-maiz/p',
            expectedUrlContains: ['bodegaaurrera.com.mx', 'tortilla-de-maiz'],
        );
        $result = $this->makeNullUrlResult(); // directUrl = null

        $matchResult = $matcher->match($target, [$result], $this->makeKnownSellerAliases());

        // Debe hacer match a pesar de que expected_url esté configurado y directUrl sea null
        $this->assertSame(MatchStatus::Matched, $matchResult->status);
        // El score debe ser positivo (no penalizado por la URL faltante)
        $this->assertGreaterThan(0, $matchResult->score);
    }

    // ── expected_position usa rank_absolute, expected_rank_group usa rank_group ─

    public function test_expected_position_compares_against_rank_absolute_not_position_string(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget(extra: ['expected_position' => 2]);
        // rankAbsolute = 2, coincide; position string sería "left" pero se ignora
        $result = $this->makeNullUrlResult(['rankAbsolute' => 2, 'rankGroup' => 2, 'position' => 2]);

        $matchResult = $matcher->match($target, [$result], $this->makeKnownSellerAliases());

        $this->assertSame(MatchStatus::Matched, $matchResult->status);
        $this->assertStringContainsString('rank_absolute', implode(' ', $matchResult->bestCandidate()?->reasons ?? []));
    }

    public function test_expected_rank_group_compares_against_rank_group(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget(extra: ['expected_rank_group' => 2]);
        $result = $this->makeNullUrlResult(['rankGroup' => 2]);

        $matchResult = $matcher->match($target, [$result], $this->makeKnownSellerAliases());

        $this->assertSame(MatchStatus::Matched, $matchResult->status);
        $this->assertStringContainsString('rank_group', implode(' ', $matchResult->bestCandidate()?->reasons ?? []));
    }

    // ── expected_data_docid / shopping_url_contains ───────────────────────────

    public function test_it_scores_data_docid_match(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget(extra: ['expected_data_docid' => '1712851062477582571']);
        $result = $this->makeNullUrlResult(['dataDocid' => '1712851062477582571']);

        $matchResult = $matcher->match($target, [$result], $this->makeKnownSellerAliases());
        $reasons = implode(' ', $matchResult->bestCandidate()?->reasons ?? []);

        $this->assertSame(MatchStatus::Matched, $matchResult->status);
        $this->assertStringContainsString('data_docid', $reasons);
    }

    public function test_it_scores_expected_shopping_url_contains(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget(extra: [
            'expected_shopping_url_contains' => ['headlineOfferDocid:1712851062477582571'],
        ]);
        $result = $this->makeNullUrlResult([
            'shoppingUrl' => 'https://google.com.mx/search?ibp=oshop&prds=headlineOfferDocid:1712851062477582571',
        ]);

        $matchResult = $matcher->match($target, [$result], $this->makeKnownSellerAliases());
        $reasons = implode(' ', $matchResult->bestCandidate()?->reasons ?? []);

        $this->assertSame(MatchStatus::Matched, $matchResult->status);
        $this->assertStringContainsString('expected_shopping_url_contains', $reasons);
    }

    // ── Rechazo por tienda equivocada ─────────────────────────────────────────

    public function test_it_rejects_candidate_from_wrong_store_with_known_aliases(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget();

        // Candidato con seller de Soriana (tienda equivocada) pero título parecido
        $wrongStoreSeller = $this->makeNullUrlResult([
            'seller' => 'Soriana.com',
            'title' => 'Tortilla de maíz por kilo',
            'dataDocid' => null,
            'shoppingUrl' => null,
        ]);

        $matchResult = $matcher->match($target, [$wrongStoreSeller], $this->makeKnownSellerAliases());

        $this->assertSame(MatchStatus::NotMatched, $matchResult->status);
        $reasons = implode(' ', $matchResult->bestCandidate()?->reasons ?? []);
        $this->assertStringContainsString('tienda conocida', $reasons);
    }

    public function test_it_rejects_chedraui_candidate_for_bodega_target(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget();

        $chedrauiResult = $this->makeNullUrlResult([
            'seller' => 'Chedraui en línea',
            'title' => 'Tortilla de maíz por kilo',
            'dataDocid' => null,
        ]);

        $matchResult = $matcher->match($target, [$chedrauiResult], $this->makeKnownSellerAliases());

        $this->assertSame(MatchStatus::NotMatched, $matchResult->status);
    }

    // ── Negative keywords ──────────────────────────────────────────────────────

    public function test_it_penalizes_negative_keywords(): void
    {
        $matcher = $this->makeMatcher();
        // Target sin expected_data_docid, expected_rank_group, ni expected_shopping_url_contains
        // para que esos campos no compensen la penalización de negative_keywords.
        $target = $this->makeBodegaTarget(extra: [
            'expected_data_docid' => null,
            'expected_rank_group' => null,
            'expected_shopping_url_contains' => null,
            'expected_position' => null,
            'negative_keywords' => ['azul', 'amarilla', 'taquera', 'mission', 'harina', '750g', 'paquete'],
        ]);

        // Resultado con 4 keywords negativas y sin identificadores que sumen score.
        $wrongResult = $this->makeNullUrlResult([
            'title' => 'Tortilla azul de maíz taquera 750g paquete',
            'seller' => 'Bodega Aurrera Despensa a tu Casa',
            'dataDocid' => null,
            'shoppingUrl' => null,
            'url' => null,
            'rankGroup' => null,
            'rankAbsolute' => null,
            'position' => null,
        ]);

        $matchResult = $matcher->match($target, [$wrongResult], $this->makeKnownSellerAliases());

        // Scoring: +45 (seller) + 0 (title, sin match exacto) + ~10 (required) - 60 (negative) + 0 (domain, sin docid ni rank) + ~15 (domain)
        // = 45 + 10 - 60 + 15 = 10 → por debajo de min_match_score=80
        $this->assertSame(MatchStatus::NotMatched, $matchResult->status);
    }

    public function test_it_rejects_candidates_with_negative_keywords_and_low_score(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeChedrauiTarget();

        $result = $this->makeDirectUrlResult([
            'title' => 'Tortillinas de harina paquete',
            'seller' => 'Chedraui',
            'directUrl' => 'https://chedraui.com.mx/tortillinas-harina/p',
            'normalizedUrl' => 'https://chedraui.com.mx/tortillinas-harina/p',
            'url' => 'https://chedraui.com.mx/tortillinas-harina/p',
        ]);

        $matchResult = $matcher->match($target, [$result], $this->makeKnownSellerAliases());

        $this->assertSame(MatchStatus::NotMatched, $matchResult->status);
        $this->assertNull($matchResult->selectedResult);
    }

    // ── expected_url solo contra directUrl ────────────────────────────────────

    public function test_it_matches_using_expected_url_against_direct_url(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeChedrauiTarget(
            expectedUrl: 'https://www.chedraui.com.mx/tortilla-de-maiz-por-kilo/p',
            expectedUrlContains: ['chedraui.com.mx', 'tortilla-de-maiz', '/p'],
        );

        $result = $this->makeDirectUrlResult();

        $matchResult = $matcher->match($target, [$result, $this->makeDirectUrlResult([
            'title' => 'Tortillas de harina 1 kg',
            'seller' => 'Chedraui',
            'directUrl' => 'https://chedraui.com.mx/tortillas-de-harina/p',
            'normalizedUrl' => 'https://chedraui.com.mx/tortillas-de-harina/p',
        ])], $this->makeKnownSellerAliases());

        $this->assertSame(MatchStatus::Matched, $matchResult->status);
        $this->assertSame('Tortilla de maíz por kilo', $matchResult->selectedResult?->title);
        $this->assertGreaterThanOrEqual(80, $matchResult->score);
    }

    // ── Deduplicación SERP vs carousel_element ─────────────────────────────────

    public function test_it_deduplicates_serp_and_carousel_element_with_same_data_docid(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget();

        $serpResult = $this->makeNullUrlResult([
            'dataDocid' => 'abc-docid-1',
            'resultType' => 'google_shopping_serp',
        ]);
        $carouselResult = $this->makeNullUrlResult([
            'dataDocid' => 'abc-docid-1',
            'resultType' => 'google_shopping_carousel_element',
        ]);

        // Los dos representan el mismo producto; no debe marcar ambiguous
        $matchResult = $matcher->match($target, [$serpResult, $carouselResult], $this->makeKnownSellerAliases());

        // No debe ser ambiguous porque es el mismo producto deduplicado
        $this->assertNotSame(MatchStatus::Ambiguous, $matchResult->status);
        // Debe elegir el de tipo google_shopping_serp
        $this->assertSame('google_shopping_serp', $matchResult->selectedResult?->resultType);
    }

    public function test_it_deduplicates_by_title_seller_price_when_no_ids(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeBodegaTarget(extra: ['expected_data_docid' => null]);

        $resultA = $this->makeNullUrlResult([
            'dataDocid' => null,
            'productId' => null,
            'gid' => null,
            'price' => 15.5,
            'resultType' => 'google_shopping_serp',
        ]);
        $resultB = $this->makeNullUrlResult([
            'dataDocid' => null,
            'productId' => null,
            'gid' => null,
            'price' => 15.5,
            'resultType' => 'google_shopping_carousel_element',
        ]);

        $matchResult = $matcher->match($target, [$resultA, $resultB], $this->makeKnownSellerAliases());

        $this->assertNotSame(MatchStatus::Ambiguous, $matchResult->status);
    }

    // ── Ambiguous solo con dos candidatos realmente distintos ──────────────────

    public function test_it_marks_ambiguous_when_two_truly_distinct_candidates_have_similar_score(): void
    {
        $matcher = $this->makeMatcher();
        $target = $this->makeChedrauiTarget(expectedUrl: null, expectedUrlContains: ['chedraui.com.mx', 'tortilla', 'maiz']);
        $target->expected_title = null;
        $target->expected_position = null;
        $target->expected_rank_group = null;
        $target->expected_data_docid = null;

        $resultA = new NormalizedResult(
            title: 'Tortilla de maíz por kilo A',
            seller: 'Chedraui',
            domain: 'chedraui.com.mx',
            directUrl: 'https://chedraui.com.mx/tortilla-maiz-a/p',
            shoppingUrl: null,
            url: 'https://chedraui.com.mx/tortilla-maiz-a/p',
            normalizedUrl: 'https://chedraui.com.mx/tortilla-maiz-a/p',
            imageUrl: null,
            price: 25.00,
            previousPrice: null,
            currency: 'MXN',
            position: 2,
            rankGroup: 1,
            rankAbsolute: 2,
            dataDocid: 'docid-a',
            productId: null,
            gid: null,
            resultType: 'google_shopping_serp',
            rawPayload: [],
        );
        $resultB = new NormalizedResult(
            title: 'Tortilla maíz kilo tradicional B',
            seller: 'Chedraui',
            domain: 'chedraui.com.mx',
            directUrl: 'https://chedraui.com.mx/tortilla-maiz-b/p',
            shoppingUrl: null,
            url: 'https://chedraui.com.mx/tortilla-maiz-b/p',
            normalizedUrl: 'https://chedraui.com.mx/tortilla-maiz-b/p',
            imageUrl: null,
            price: 25.50,
            previousPrice: null,
            currency: 'MXN',
            position: 3,
            rankGroup: 1,
            rankAbsolute: 3,
            dataDocid: 'docid-b', // distinto data_docid → no deduplicado
            productId: null,
            gid: null,
            resultType: 'google_shopping_serp',
            rawPayload: [],
        );

        $matchResult = $matcher->match($target, [$resultA, $resultB], $this->makeKnownSellerAliases());

        $this->assertSame(MatchStatus::Ambiguous, $matchResult->status);
        $this->assertNull($matchResult->selectedResult);
        $this->assertCount(2, $matchResult->candidates);
    }
}
