<?php

namespace App\Services\Ranking;

class CatalogProductRankingExplainer
{
    /**
     * @param  list<array<string, mixed>>  $ranked
     * @return list<array<string, mixed>>
     */
    public function explain(array $ranked): array
    {
        if ($ranked === []) {
            return [];
        }

        $explained = [];

        foreach ($ranked as $index => $product) {
            $nextRanked = $ranked[$index + 1] ?? null;

            $explained[] = array_merge($product, [
                'ranking_signals' => $this->signals($product),
                'ranking_reason' => $nextRanked !== null
                    ? $this->compareProducts($product, $nextRanked)
                    : $this->finalRankReason($product),
            ]);
        }

        return $explained;
    }

    /**
     * @param  array<string, mixed>  $product
     * @return array{
     *     keyword_search_volume: ?int,
     *     keyword_competition: ?string,
     *     matched_keyword: ?string,
     *     review_count: ?int,
     *     rating: ?float,
     *     primary_factor: ?string,
     * }
     */
    private function signals(array $product): array
    {
        $keywordVolume = filled($product['keyword_search_volume'] ?? null)
            ? (int) $product['keyword_search_volume']
            : null;
        $reviewCount = filled($product['review_count'] ?? null)
            ? (int) $product['review_count']
            : null;
        $rating = filled($product['rating'] ?? null)
            ? (float) $product['rating']
            : null;

        return [
            'keyword_search_volume' => $keywordVolume,
            'keyword_competition' => filled($product['keyword_competition'] ?? null)
                ? (string) $product['keyword_competition']
                : null,
            'matched_keyword' => filled($product['matched_keyword'] ?? null)
                ? (string) $product['matched_keyword']
                : null,
            'review_count' => $reviewCount,
            'rating' => $rating,
            'primary_factor' => $this->primaryFactor($keywordVolume, $reviewCount, $rating),
        ];
    }

    /**
     * @param  array<string, mixed>  $higher
     * @param  array<string, mixed>  $lower
     */
    private function compareProducts(array $higher, array $lower): string
    {
        $higherName = $this->productLabel($higher);
        $lowerName = $this->productLabel($lower);
        $lowerRank = (int) ($lower['rank'] ?? 0);
        $differences = $this->differences($higher, $lower);

        if ($differences === []) {
            return 'Ranked #'.(int) ($higher['rank'] ?? 1)." above {$lowerName} (#{$lowerRank}) with comparable listing and keyword signals.";
        }

        return sprintf(
            'Ranked #%d above %s (#%d) because %s.',
            (int) ($higher['rank'] ?? 1),
            $lowerName,
            $lowerRank,
            implode('; ', $differences),
        );
    }

    /**
     * @param  array<string, mixed>  $product
     */
    private function finalRankReason(array $product): string
    {
        $signals = $this->signals($product);
        $parts = $this->signalDescriptions($signals);

        if ($parts === []) {
            return 'Included in the final ranked list based on overall catalog fit.';
        }

        return sprintf(
            'Included at rank #%d based on %s.',
            (int) ($product['rank'] ?? 0),
            implode(', ', $parts),
        );
    }

    /**
     * @param  array<string, mixed>  $higher
     * @param  array<string, mixed>  $lower
     * @return list<string>
     */
    private function differences(array $higher, array $lower): array
    {
        $differences = [];

        if (config('google-ads.ranking_use_keywords', true)) {
            $higherVolume = (int) ($higher['keyword_search_volume'] ?? 0);
            $lowerVolume = (int) ($lower['keyword_search_volume'] ?? 0);

            if ($higherVolume !== $lowerVolume) {
                if ($higherVolume > 0 || $lowerVolume > 0) {
                    $differences[] = sprintf(
                        'higher keyword demand (%s vs %s monthly searches%s)',
                        $this->formatVolume($higherVolume),
                        $this->formatVolume($lowerVolume),
                        $this->matchedKeywordSuffix($higher),
                    );
                }
            } elseif ($this->competitionRank($higher) !== $this->competitionRank($lower)) {
                $differences[] = sprintf(
                    'more favorable keyword competition (%s vs %s%s)',
                    $this->formatCompetition($higher),
                    $this->formatCompetition($lower),
                    $this->matchedKeywordSuffix($higher),
                );
            }
        }

        $higherReviews = (int) ($higher['review_count'] ?? 0);
        $lowerReviews = (int) ($lower['review_count'] ?? 0);

        if ($higherReviews !== $lowerReviews) {
            $differences[] = sprintf(
                'more reviews (%s vs %s)',
                number_format($higherReviews),
                number_format($lowerReviews),
            );
        }

        $higherRating = (float) ($higher['rating'] ?? 0);
        $lowerRating = (float) ($lower['rating'] ?? 0);

        if ($higherRating !== $lowerRating) {
            $differences[] = sprintf(
                'stronger rating (%s vs %s stars)',
                $this->formatRating($higherRating),
                $this->formatRating($lowerRating),
            );
        }

        $selectionReason = trim((string) ($higher['selection_reason'] ?? ''));

        if ($selectionReason !== '' && ($higher['ranking_source'] ?? null) === 'ai') {
            $differences[] = "AI selection note: {$selectionReason}";
        }

        $keywordMatchReason = trim((string) ($higher['keyword_match_reason'] ?? ''));

        if ($keywordMatchReason !== '') {
            $differences[] = "Keyword match ({$higher['keyword_match_source']}): {$keywordMatchReason}";
        }

        return $differences;
    }

    /**
     * @param  array{
     *     keyword_search_volume: ?int,
     *     keyword_competition: ?string,
     *     matched_keyword: ?string,
     *     review_count: ?int,
     *     rating: ?float,
     *     primary_factor: ?string,
     * }  $signals
     * @return list<string>
     */
    private function signalDescriptions(array $signals): array
    {
        $parts = [];

        if ($signals['keyword_search_volume'] !== null) {
            $parts[] = number_format($signals['keyword_search_volume']).' monthly searches'
                .($signals['matched_keyword'] !== null ? " for \"{$signals['matched_keyword']}\"" : '');
        }

        if ($signals['review_count'] !== null) {
            $parts[] = number_format($signals['review_count']).' reviews';
        }

        if ($signals['rating'] !== null) {
            $parts[] = $this->formatRating($signals['rating']).' stars';
        }

        return $parts;
    }

    private function primaryFactor(?int $keywordVolume, ?int $reviewCount, ?float $rating): ?string
    {
        if (config('google-ads.ranking_use_keywords', true) && $keywordVolume !== null && $keywordVolume > 0) {
            return 'keyword_search_volume';
        }

        if ($reviewCount !== null && $reviewCount > 0) {
            return 'review_count';
        }

        if ($rating !== null && $rating > 0) {
            return 'rating';
        }

        return null;
    }

    /**
     * @param  array<string, mixed>  $product
     */
    private function productLabel(array $product): string
    {
        $name = trim((string) ($product['name'] ?? ''));

        return $name !== '' ? $name : (string) ($product['url'] ?? 'next product');
    }

    /**
     * @param  array<string, mixed>  $product
     */
    private function matchedKeywordSuffix(array $product): string
    {
        $keyword = trim((string) ($product['matched_keyword'] ?? ''));

        return $keyword !== '' ? " for \"{$keyword}\"" : '';
    }

    /**
     * @param  array<string, mixed>  $product
     */
    private function formatCompetition(array $product): string
    {
        $competition = trim((string) ($product['keyword_competition'] ?? ''));

        return $competition !== '' ? $competition : 'UNKNOWN';
    }

    /**
     * @param  array<string, mixed>  $product
     */
    private function competitionRank(array $product): int
    {
        return match (strtoupper((string) ($product['keyword_competition'] ?? ''))) {
            'LOW' => 1,
            'MEDIUM' => 2,
            'HIGH' => 3,
            default => 0,
        };
    }

    private function formatVolume(int $volume): string
    {
        return $volume > 0 ? number_format($volume) : 'none reported';
    }

    private function formatRating(float $rating): string
    {
        return rtrim(rtrim(number_format($rating, 1, '.', ''), '0'), '.');
    }
}
