<?php

namespace App\Services\Keywords;

class KeywordCandidateEnricher
{
    public function __construct(
        private AiKeywordCandidateMatcher $aiMatcher,
        private HeuristicKeywordMatcher $heuristicMatcher,
        private ProductKeywordParser $keywordParser,
    ) {}

    /**
     * @param  list<array<string, mixed>>  $candidates
     * @param  list<array{term: string, avg_monthly_searches: int, competition: string, low_top_of_page_bid_micros?: int}>  $keywords
     * @return list<array<string, mixed>>
     */
    public function enrich(
        array $candidates,
        array $keywords,
        string $category = '',
        string $subcategory = '',
    ): array {
        if ($candidates === [] || $keywords === []) {
            return $candidates;
        }

        $keywordsByTerm = $this->keywordsByTerm($keywords);
        $aiMatches = $this->aiMatcher->match($candidates, $keywords, $category, $subcategory);

        $enriched = [];

        foreach ($candidates as $index => $candidate) {
            $importMatch = $this->matchImportedKeyword($candidate, $keywords);

            if ($importMatch !== null) {
                $enriched[] = $this->attachKeywordMatch(
                    $candidate,
                    $importMatch['keyword'],
                    'import',
                    $importMatch['reason'],
                );

                continue;
            }

            $aiMatch = $aiMatches[$index] ?? null;

            if ($aiMatch !== null) {
                $keyword = $keywordsByTerm[$this->normalizeTerm($aiMatch['term'])] ?? null;
                $enriched[] = $this->attachKeywordMatch(
                    $candidate,
                    $keyword,
                    'ai',
                    $aiMatch['match_reason'],
                );

                continue;
            }

            $heuristicMatch = $this->heuristicMatcher->match((string) ($candidate['name'] ?? ''), $keywords);
            $enriched[] = $this->attachKeywordMatch(
                $candidate,
                $heuristicMatch,
                'heuristic',
                $heuristicMatch !== null
                    ? 'Matched by token overlap between the product name and keyword text.'
                    : null,
            );
        }

        return $enriched;
    }

    /**
     * @param  list<array{term: string, avg_monthly_searches: int, competition: string, low_top_of_page_bid_micros?: int}>  $keywords
     * @return array<string, array{term: string, avg_monthly_searches: int, competition: string, low_top_of_page_bid_micros?: int}>
     */
    private function keywordsByTerm(array $keywords): array
    {
        $indexed = [];

        foreach ($keywords as $keyword) {
            $term = trim((string) ($keyword['term'] ?? ''));

            if ($term === '') {
                continue;
            }

            $indexed[$this->normalizeTerm($term)] = $keyword;
        }

        return $indexed;
    }

    /**
     * @param  array{term: string, avg_monthly_searches: int, competition: string, low_top_of_page_bid_micros?: int}|null  $keyword
     * @return array<string, mixed>
     */
    private function attachKeywordMatch(
        array $candidate,
        ?array $keyword,
        string $source,
        ?string $matchReason,
    ): array {
        if ($keyword === null) {
            return $candidate;
        }

        return array_merge($candidate, [
            'matched_keyword' => $keyword['term'],
            'keyword_search_volume' => $keyword['avg_monthly_searches'],
            'keyword_competition' => $keyword['competition'],
            'keyword_low_top_of_page_bid_micros' => $keyword['low_top_of_page_bid_micros'] ?? null,
            'keyword_match_source' => $source,
            'keyword_match_reason' => $matchReason,
        ]);
    }

    private function normalizeTerm(string $term): string
    {
        return strtolower(trim($term));
    }

    /**
     * @param  array<string, mixed>  $candidate
     * @param  list<array{term: string, avg_monthly_searches: int, competition: string, low_top_of_page_bid_micros?: int}>  $keywords
     * @return array{keyword: array{term: string, avg_monthly_searches: int, competition: string, low_top_of_page_bid_micros?: int}, reason: string}|null
     */
    private function matchImportedKeyword(array $candidate, array $keywords): ?array
    {
        $importedSeeds = $this->keywordParser->seedsFromCandidate($candidate);

        if ($importedSeeds === []) {
            return null;
        }

        $keywordsByTerm = $this->keywordsByTerm($keywords);

        foreach ($importedSeeds as $seed) {
            $exact = $keywordsByTerm[$this->normalizeTerm($seed)] ?? null;

            if ($exact !== null) {
                return [
                    'keyword' => $exact,
                    'reason' => 'Matched imported product keyword to Google Ads search volume data.',
                ];
            }
        }

        $productName = strtolower(trim((string) ($candidate['name'] ?? '')));

        foreach ($importedSeeds as $seed) {
            foreach ($keywords as $keyword) {
                $term = trim((string) ($keyword['term'] ?? ''));

                if ($term === '') {
                    continue;
                }

                $termLower = strtolower($term);

                if (str_contains($termLower, $seed) || str_contains($seed, $termLower) || str_contains($productName, $termLower)) {
                    return [
                        'keyword' => $keyword,
                        'reason' => 'Matched imported product keyword to the closest Google Ads keyword idea.',
                    ];
                }
            }
        }

        return null;
    }
}
