<?php

namespace App\Agent\Tools\Campaign;

use App\Agent\Choices;
use App\Agent\Tools\Campaign\Concerns\ActsOnFocusedCampaign;
use App\Agent\Workspace;
use App\Campaigns\NameMatch;
use App\Models\ArtifactFieldSource;
use App\Models\Campaign;
use App\Models\GoogleAdsConnection;
use App\Services\GoogleAdsService;
use App\Services\Meta\Meta;
use App\Services\Meta\MetaException;
use Carbon\Carbon;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use RuntimeException;
use Stringable;

/**
 * List conversion pixels, or attach one to the campaign in focus.
 *
 * The list merges the account's own pixels with those owned by its parent
 * business, because the one an advertiser means is frequently business owned
 * and shared rather than assigned to the account.
 */
class ChoosePixel implements Tool
{
    use ActsOnFocusedCampaign;

    public function __construct(
        private readonly Workspace $workspace,
        private readonly Meta $meta,
        private readonly Choices $choices,
        private readonly GoogleAdsService $google,
    ) {}

    public function description(): Stringable|string
    {
        return 'List available conversion pixels, or set one on the campaign in focus by passing '
            .'pixel_id. Sales and leads objectives need a pixel; traffic does not.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'pixel_id' => $schema->string()
                ->description('Set this pixel on the campaign. Omit to list the available pixels instead.'),
        ];
    }

    /**
     * What counts a conversion on this platform, as one shape.
     *
     * Meta calls it a pixel and Google calls it a conversion action. They are
     * the same answer to the same question, they are both account scoped, and
     * both end up in the campaign's pixel_id, so the tool that asks is shared
     * and only the lookup differs.
     *
     * A Google resource name is the full customers/123/conversionActions/456
     * path, because that is what the API takes; it is never shown, the
     * conversion action's own name is.
     *
     * @return list<array{id: string, name: string, hint?: ?string}>
     */
    private function candidates(Campaign $campaign): array
    {
        if ($campaign->platform !== 'google') {
            return array_map(fn (array $pixel): array => [
                'id' => (string) $pixel['id'],
                'name' => (string) ($pixel['name'] ?? $pixel['id']),
                // Last fired says more about whether it is the right pixel than
                // the name does, since names repeat across a business.
                'hint' => isset($pixel['last_fired_time'])
                    ? 'last fired '.Carbon::parse($pixel['last_fired_time'])->diffForHumans()
                    : null,
            ], $this->meta->on($campaign->id, 'agent')->pixels($campaign->ad_account_id));
        }

        // The shared account, not the campaign owner's. Conversion actions
        // belong to the account being bought on, which is the same one for
        // everybody here.
        $connection = GoogleAdsConnection::shared();

        if ($connection === null) {
            throw new RuntimeException(GoogleAdsConnection::missingReason());
        }

        return array_map(fn (array $action): array => [
            'id' => $action['resource_name'],
            'name' => $action['name'],
            // The category is what tells a purchase apart from a page view when
            // several actions share a name, which they routinely do.
            'hint' => strtolower(str_replace('_', ' ', $action['category'])),
        ], $this->google->conversionActionOptions($connection));
    }

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

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

        $pixelId = $request->all()['pixel_id'] ?? null;

        try {
            $pixels = $this->candidates($campaign);
        } catch (MetaException|RuntimeException $e) {
            return $this->json(['ok' => false, 'error' => $e->getMessage()]);
        }

        if (blank($pixelId)) {
            // A business often has one pixel per site, named after it, and the
            // buyer was left to find theirs in a list with nothing marked. The
            // landing page is the evidence, exactly as it is for the Page.
            $ranked = NameMatch::suggestFirst($pixels, [$campaign->landing_url]);

            $google = $campaign->platform === 'google';

            $this->choices->offer(
                field: $google ? 'conversion_action_resource_name' : 'pixel_id',
                question: $google
                    ? 'Which conversion action should this campaign count?'
                    : 'Which pixel should conversions be attributed to?',
                options: array_map(fn (array $pixel): array => [
                    'value' => (string) $pixel['id'],
                    'label' => $pixel['name'],
                    'hint' => $pixel['hint'] ?? null,
                    'badge' => ($pixel['suggested'] ?? false) ? 'Suggested' : null,
                    'sends' => $google
                        ? 'Count the '.$pixel['name'].' conversion action'
                        : 'Use pixel '.$pixel['id'],
                ], $ranked),
            );

            return $this->json([
                'ok' => true,
                'pixels' => $pixels,
                'note' => 'The pixels are on screen for the user to pick. Say one short line and wait.',
            ]);
        }

        $chosen = collect($pixels)->firstWhere('id', (string) $pixelId);

        if (! $chosen) {
            return $this->json([
                'ok' => false,
                'error' => $campaign->platform === 'google'
                    ? "Conversion action {$pixelId} is not enabled on this Google Ads account."
                    : "Pixel {$pixelId} is not available on this account or its business.",
                'pixels' => $pixels,
            ]);
        }

        $campaign->update(['pixel_id' => $chosen['id']]);
        $this->workspace->recordSource($campaign, 'pixel_id', ArtifactFieldSource::PLATFORM_RETURNED);

        return $this->json(['ok' => true, 'pixel_id' => $chosen['id'], 'name' => $chosen['name'] ?? null]);
    }
}
