<?php

namespace App\Campaigns\Targeting;

use App\Models\GoogleAdsConnection;
use App\Models\Location;
use App\Models\PlatformLocation;
use App\Services\GoogleAdsService;
use App\Services\Meta\GraphApi;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class LocationDiscovery
{
    public function __construct(
        private readonly GoogleAdsService $google,
        private readonly GraphApi $meta,
    ) {}

    /**
     * @return list<array{platform: string, external_id: string, kind: string, name: string, label: string, country_code: string, parent_external_id: string|null}>
     */
    public function search(string $platform, string $kind, string $countryCode, string $term, ?string $accountId = null, int $page = 1): array
    {
        $cacheKey = 'location-search:'.hash('sha256', implode('|', [
            $platform, $kind, $countryCode, mb_strtolower(trim($term)), $accountId ?? '', (string) $page,
        ]));

        return Cache::remember($cacheKey, now()->addDay(), fn (): array => $this->providerSearch($platform, $kind, $countryCode, $term, $accountId, $page));
    }

    /**
     * Verify a chosen provider ID again before saving it. A similarly named
     * location or an old search result cannot silently become the selection.
     */
    public function select(string $platform, string $kind, string $countryCode, string $term, string $externalId, ?string $accountId = null, int $page = 1): PlatformLocation
    {
        $matches = array_values(array_filter(
            $this->providerSearch($platform, $kind, $countryCode, $term, $accountId, $page),
            fn (array $candidate): bool => $candidate['external_id'] === $externalId,
        ));

        if (count($matches) !== 1) {
            throw new RuntimeException('The chosen location is no longer available. Search again before selecting it.');
        }

        $chosen = $matches[0];

        return DB::transaction(function () use ($chosen): PlatformLocation {
            $country = Location::query()->where('type', 'country')->where('iso_code', $chosen['country_code'])->first()
                ?? throw new RuntimeException('Seed the country catalog before saving a location.');

            $existing = PlatformLocation::query()
                ->where('platform', $chosen['platform'])
                ->where('external_id', $chosen['external_id'])
                ->first();

            if ($existing !== null) {
                if ($existing->location->type !== $chosen['kind'] || $existing->location->country_code !== $chosen['country_code']) {
                    throw new RuntimeException('This provider location is already mapped to a different place.');
                }

                $existing->update(['status' => 'verified', 'verified_at' => now()]);

                return $existing;
            }

            $parent = PlatformLocation::query()
                ->where('platform', $chosen['platform'])
                ->where('external_id', $chosen['parent_external_id'])
                ->first()?->location;

            $location = Location::query()->firstOrCreate(
                ['type' => $chosen['kind'], 'source' => $chosen['platform'], 'source_key' => $chosen['external_id']],
                [
                    'name' => $chosen['name'],
                    'country_code' => $chosen['country_code'],
                    'parent_id' => $parent?->getKey() ?? $country->getKey(),
                ],
            );

            return PlatformLocation::query()->create([
                'location_id' => $location->getKey(),
                'platform' => $chosen['platform'],
                'external_id' => $chosen['external_id'],
                'provider_name' => $chosen['label'],
                'status' => 'verified',
                'verified_at' => now(),
            ]);
        });
    }

    /**
     * @return list<array{platform: string, external_id: string, kind: string, name: string, label: string, country_code: string, parent_external_id: string|null}>
     */
    private function providerSearch(string $platform, string $kind, string $countryCode, string $term, ?string $accountId, int $page): array
    {
        if (! in_array($kind, ['city', 'region'], true) || ! preg_match('/^[A-Z]{2}$/D', $countryCode)) {
            throw new RuntimeException('Choose a city or region and a valid country.');
        }

        if ($platform === 'google') {
            if ($page !== 1) {
                throw new RuntimeException('Google city and region search does not support additional pages yet. Refine the search term.');
            }

            $connection = GoogleAdsConnection::shared()
                ?? throw new RuntimeException(GoogleAdsConnection::missingReason());

            return array_map(fn (array $candidate): array => [
                'platform' => 'google',
                'external_id' => $candidate['id'],
                'kind' => $candidate['kind'],
                'name' => $candidate['name'],
                'label' => $candidate['label'],
                'country_code' => $candidate['country_code'],
                'parent_external_id' => $candidate['parent_id'],
            ], $this->google->geoTargetCandidates($connection, $kind, $countryCode, $term, $accountId));
        }

        if ($platform === 'meta') {
            $results = [];

            foreach ($this->meta->locationCandidates($kind, $countryCode, $term, $accountId, $page) as $candidate) {
                if (($candidate['type'] ?? null) !== $kind
                    || ($candidate['country_code'] ?? null) !== $countryCode
                    || blank($candidate['key'] ?? null)
                    || blank($candidate['name'] ?? null)) {
                    continue;
                }

                $region = (string) ($candidate['region'] ?? '');
                $results[] = [
                    'platform' => 'meta',
                    'external_id' => (string) $candidate['key'],
                    'kind' => $kind,
                    'name' => (string) ($candidate['name'] ?? ''),
                    'label' => implode(', ', array_filter([(string) ($candidate['name'] ?? ''), $region, $countryCode])),
                    'country_code' => $countryCode,
                    'parent_external_id' => filled($candidate['region_id'] ?? null) ? (string) $candidate['region_id'] : null,
                ];
            }

            return $results;
        }

        throw new RuntimeException('City and region search is not verified for '.$platform.'.');
    }
}
