<?php

namespace App\Agent\Tools\Campaign;

use App\Agent\Choices;
use App\Agent\Tools\Campaign\Concerns\ActsOnFocusedCampaign;
use App\Agent\Workspace;
use App\Campaigns\Targeting\LocationDiscovery;
use App\Campaigns\Targeting\TargetingSelections;
use App\Models\ArtifactFieldSource;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Http\Client\ConnectionException;
use InvalidArgumentException;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use RuntimeException;
use Stringable;

class ChooseLocation implements Tool
{
    use ActsOnFocusedCampaign;

    public function __construct(
        private readonly Workspace $workspace,
        private readonly Choices $choices,
        private readonly LocationDiscovery $discovery,
        private readonly TargetingSelections $selections,
    ) {}

    public function description(): Stringable|string
    {
        return 'For a Meta or Google draft campaign, search cities or states/regions within a selected country '
            .'and offer the exact matching places for the user to choose. Call with country_code, kind '
            .'and query to search; pass an external_id from those results to select it. Never guess a place ID. '
            .'A selected city or region narrows its country; call again to add another place. '
            .'Omit query to list selected places for removal, then pass remove_external_id to remove one. '
            .'For Google, refine common names with a comma and state or province, e.g. Springfield, Illinois.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'country_code' => $schema->string()->description('Selected two-letter country code, e.g. US.')->required(),
            'kind' => $schema->string()->description('city or region (state/province); required when searching.'),
            'query' => $schema->string()->description('At least two characters of the city or region name; omit to list current selections.'),
            'page' => $schema->integer()->description('Meta search page 1 to 4. Google supports only page 1; refine its query instead.'),
            'external_id' => $schema->string()->description('Exact provider key from a search result. Omit to offer choices.'),
            'remove_external_id' => $schema->string()->description('Exact provider ID of a currently selected city or region to remove.'),
            'expected_revision' => $schema->integer()->description('Targeting revision returned by the search, to reject stale selections.'),
        ];
    }

    public function handle(Request $request): Stringable|string
    {
        if (! $campaign = $this->focused()) {
            return $this->noCampaign();
        }

        if (! in_array($campaign->platform, ['meta', 'google'], true) || $campaign->isPublished()) {
            return $this->json(['ok' => false, 'error' => 'City and region selection is available for Meta and Google draft campaigns only.']);
        }

        if (blank($campaign->ad_account_id)) {
            return $this->json(['ok' => false, 'error' => 'Verify the ad account before searching locations.']);
        }

        $args = $request->all();
        $platform = (string) $campaign->platform;
        $platformName = $platform === 'meta' ? 'Meta' : 'Google Ads';
        $countryCode = strtoupper(trim((string) ($args['country_code'] ?? '')));
        $requestedKind = strtolower(trim((string) ($args['kind'] ?? '')));
        $kind = match ($requestedKind) {
            'state', 'province' => 'region',
            default => $requestedKind,
        };
        $term = trim((string) ($args['query'] ?? ''));
        $page = (int) ($args['page'] ?? 1);

        if (! preg_match('/^[A-Z]{2}$/D', $countryCode)) {
            return $this->json(['ok' => false, 'error' => 'Provide a two-letter country code.']);
        }

        try {
            if (filled($args['remove_external_id'] ?? null)) {
                $set = $this->selections->removeCampaignLocation($campaign, $countryCode, (string) $args['remove_external_id'], isset($args['expected_revision']) ? (int) $args['expected_revision'] : null);

                return $this->json([
                    'ok' => true,
                    'revision' => $set->revision,
                    'note' => 'Location removed. If this was the last selected city or region in the country, the whole country is targeted again.',
                ]);
            }

            if ($term === '') {
                $selected = $this->selections->selectedCampaignLocations($campaign, $countryCode);

                if ($selected === []) {
                    return $this->json(['ok' => true, 'selected' => [], 'note' => 'No cities or regions are selected in this country. The whole country is targeted.']);
                }

                $revision = $campaign->targetingSet()->value('revision') ?? 0;
                $this->choices->offer(
                    field: $platform.'_location_removal',
                    question: 'Which city or region should be removed?',
                    options: array_map(fn (array $place): array => [
                        'value' => $place['external_id'],
                        'label' => $place['label'],
                        'sends' => sprintf('Remove %s location ID %s from %s at targeting revision %d', $platformName, $place['external_id'], $countryCode, $revision),
                    ], $selected),
                );

                return $this->json(['ok' => true, 'selected' => $selected, 'revision' => $revision]);
            }

            if (! in_array($countryCode, $this->selections->countries($campaign), true)) {
                return $this->json(['ok' => false, 'error' => 'Choose the country on this campaign before selecting one of its cities or regions.']);
            }

            if (filled($args['external_id'] ?? null)) {
                $mapping = $this->discovery->select($platform, $kind, $countryCode, $term, (string) $args['external_id'], $campaign->ad_account_id, $page);
                $set = $this->selections->addCampaignLocation($campaign, $mapping, isset($args['expected_revision']) ? (int) $args['expected_revision'] : null);
                $this->workspace->recordSource($campaign, 'locations', $this->workspace->wasOffered($platform.'_location')
                    ? ArtifactFieldSource::USER_STATED
                    : ArtifactFieldSource::MODEL_INFERRED);

                return $this->json([
                    'ok' => true,
                    'selected' => $mapping->provider_name,
                    'country_code' => $countryCode,
                    'revision' => $set->revision,
                    'note' => 'This country is now narrowed to its selected cities or regions. Search again to add another place.',
                ]);
            }

            $matches = $this->discovery->search($platform, $kind, $countryCode, $term, $campaign->ad_account_id, $page);

            if ($matches === []) {
                return $this->json(['ok' => false, 'error' => 'No matching places on this page. Try a more specific search or an earlier page.']);
            }

            $revision = $campaign->targetingSet()->value('revision') ?? 0;
            $this->choices->offer(
                field: $platform.'_location',
                question: 'Which '.($kind === 'city' ? 'city' : 'state or region').' should this campaign target?',
                options: array_map(fn (array $match): array => [
                    'value' => $match['external_id'],
                    'label' => $match['label'],
                    'hint' => $platformName.' ID '.$match['external_id'],
                    'sends' => sprintf(
                        'Target %s %s (%s ID %s) from search "%s" in %s, page %d, targeting revision %d',
                        $kind,
                        $match['label'],
                        $platformName,
                        $match['external_id'],
                        $term,
                        $countryCode,
                        $page,
                        $revision,
                    ),
                ], $matches),
            );

            return $this->json([
                'ok' => true,
                'matches' => count($matches),
                'page' => $page,
                'revision' => $revision,
                'note' => count($matches) === 25
                    ? ($platform === 'meta' && $page < 4
                        ? 'Matching places are on screen. If the right one is missing, search the next page or refine the name.'
                        : 'Matching places are on screen. If the right one is missing, refine the name; for Google, add the state or province after a comma.')
                    : 'Matching places are on screen. Ask the user to choose the exact place.',
            ]);
        } catch (RuntimeException|InvalidArgumentException|ConnectionException $exception) {
            return $this->json(['ok' => false, 'error' => $exception->getMessage()]);
        }
    }
}
