<?php

namespace App\Agent\Tools\Campaign;

use App\Agent\Tools\Campaign\Concerns\ActsOnFocusedCampaign;
use App\Agent\Workspace;
use App\Models\ArtifactFieldSource;
use App\Models\Campaign;
use App\Services\Meta\Meta;
use App\Services\Meta\MetaException;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

/**
 * Create a Meta pixel, which Events Manager now calls a dataset.
 *
 * Meta renamed the object in the interface and left the API alone, so a buyer
 * asking for a dataset means this. There is no /owned_data_sets path; adspixels
 * is still the only one.
 *
 * Separate from choose_pixel rather than folded into it, because creating one
 * is not a kind of choosing. A pixel cannot be deleted through the API, only
 * unshared, so every call leaves something behind permanently and the model
 * should have to mean it.
 *
 * Meta only. Google's equivalent is a conversion action, which is created
 * through a different API this has never exercised, and guessing at it would
 * leave a half-made conversion action nobody asked for.
 */
class CreatePixel implements Tool
{
    use ActsOnFocusedCampaign;

    public function __construct(
        private readonly Workspace $workspace,
        private readonly Meta $meta,
    ) {}

    public function description(): Stringable|string
    {
        return 'Create a new Meta pixel, called a dataset in Events Manager, on the campaign in focus '
            .'and select it. Meta only. Use choose_pixel first: this is for when none of the existing '
            .'pixels belong to the brand being advertised. A pixel cannot be deleted afterwards, so '
            .'confirm the name with the user before calling this.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'name' => $schema->string()
                ->description('What the pixel should be called. Name it after the brand, not the campaign.'),
        ];
    }

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

        if ($campaign->platform !== 'meta') {
            return $this->json([
                'ok' => false,
                'error' => sprintf(
                    'Pixels are a Meta object and this campaign is on %s. Google counts conversions through a '
                    .'conversion action, which has to be made in Google Ads.',
                    $campaign->platform,
                ),
            ]);
        }

        if (blank($campaign->ad_account_id)) {
            return $this->json([
                'ok' => false,
                'error' => 'Verify the ad account first. A pixel is created on an account, so there has to be one.',
            ]);
        }

        $name = trim((string) ($request->all()['name'] ?? ''));

        if ($name === '') {
            return $this->json([
                'ok' => false,
                'error' => 'Give the pixel a name. Name it after the brand being advertised, not the campaign, '
                    .'because it will outlive this campaign and be offered on every later one.',
            ]);
        }

        // An existing pixel of that name is offered back rather than duplicated.
        //
        // Two pixels called the same thing is a permanent mess: neither can be
        // removed, both appear in every later list, and the buyer has no way to
        // tell which one their site is firing.
        try {
            $existing = collect($this->meta->pixels($campaign->ad_account_id))
                ->first(fn (array $pixel): bool => strcasecmp(trim((string) ($pixel['name'] ?? '')), $name) === 0);
        } catch (MetaException $e) {
            return $this->json(['ok' => false, 'error' => $e->getMessage()]);
        }

        if ($existing) {
            $this->attach($campaign, (string) $existing['id']);

            return $this->json([
                'ok' => true,
                'pixel_id' => $existing['id'],
                'name' => $existing['name'] ?? $name,
                'created' => false,
                'note' => 'A pixel of that name already existed on this account, so it was selected rather than '
                    .'a second one made. Say so.',
            ]);
        }

        try {
            $created = $this->meta->createPixel($campaign->ad_account_id, $name);
        } catch (MetaException $e) {
            return $this->json(['ok' => false, 'error' => $e->getMessage()]);
        }

        if (blank($created['id'] ?? null)) {
            return $this->json([
                'ok' => false,
                'error' => 'Meta accepted the request but returned no pixel id, so nothing can be attached to the campaign.',
            ]);
        }

        $this->attach($campaign, (string) $created['id']);

        return $this->json([
            'ok' => true,
            'pixel_id' => $created['id'],
            'name' => $name,
            'created' => true,
            'note' => 'Created and selected. Tell the user it exists now and has to be installed on the site '
                .'before it records anything, and that a pixel cannot be deleted afterwards.',
        ]);
    }

    /**
     * Provenance is the platform's, not the user's.
     *
     * They named it, but the id came back from Meta and the id is the field.
     * The same call choose_pixel makes, for the same reason.
     */
    private function attach(Campaign $campaign, string $pixelId): void
    {
        $campaign->update(['pixel_id' => $pixelId]);
        $this->workspace->recordSource($campaign, 'pixel_id', ArtifactFieldSource::PLATFORM_RETURNED);
    }
}
