<?php

namespace App\Mcp\Tools\GoogleAds;

use App\Agent\Choices;
use App\Agent\Workspace;
use App\Models\Creative;
use App\Models\GoogleAdsConnection;
use App\Services\GoogleAdsService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Http\Request as HttpRequest;
use Illuminate\Support\Facades\Storage;
use Laravel\Ai\Models\Conversation;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;

#[Description('Show completed image creatives as selectable previews, then upload the selected image to Google Ads and return its asset resource name.')]
class SelectGoogleImageAsset extends Tool
{
    public function __construct(
        private readonly Workspace $workspace,
        private readonly Choices $choices,
        private readonly GoogleAdsService $googleAds,
        private readonly HttpRequest $httpRequest,
    ) {}

    public function schema(JsonSchema $schema): array
    {
        return [
            'creative_id' => $schema->integer()->description('Selected completed image creative ID; omit to show the picker.'),
            'image_field' => $schema->string()->description('Google Ads field: marketing_image_assets or square_marketing_image_assets')->required(),
        ];
    }

    public function handle(Request $request): Response
    {
        $data = $request->validate([
            'creative_id' => ['nullable', 'integer'],
            'image_field' => ['required', 'in:marketing_image_assets,square_marketing_image_assets'],
        ]);
        $user = $this->workspace->owner() ?: $request->user();
        if ($user === null) {
            return Response::error('Authentication is required to select a creative.');
        }
        $images = Creative::query()
            ->where('user_id', $user?->getKey())
            ->where('type', 'image')
            ->where('status', 'completed')
            ->whereNotNull('asset_id')
            ->with('asset')
            ->latest('id')
            ->limit(60)
            ->get()
            ->filter(fn (Creative $creative): bool => $creative->asset !== null && $creative->asset->kind === 'image');

        $requiredRatio = $data['image_field'] === 'square_marketing_image_assets' ? '1:1' : '1200x628';
        $dimensions = $requiredRatio === '1:1' ? '1200x1200' : '1200x628';
        $images = $images->filter(fn (Creative $creative): bool => $this->matchesRequiredDimensions($creative, $requiredRatio));

        if ($images->isEmpty()) {
            return Response::error('No completed '.$dimensions.' image creatives are available. Ask the user to create one in the creative module first.');
        }

        $creativeId = $data['creative_id'] ?? null;
        if ($creativeId === null) {
            $this->choices->offer(
                field: $data['image_field'],
                question: 'Select a '.$dimensions.' image creative to upload to Google Ads.',
                options: $images->map(fn (Creative $creative): array => [
                    'value' => (string) $creative->id,
                    'label' => $creative->title ?: 'Creative '.$creative->id,
                    'hint' => trim($this->imageDimensions($creative).' - '.$creative->asset->original_name),
                    'image' => route('assets.preview', $creative->asset),
                    'sends' => 'Select image creative '.$creative->id,
                ])->all(),
            );

            return Response::json(['ok' => true, 'offered' => true, 'message' => 'Image creatives are ready to select. Wait for the user to choose one.']);
        }

        $creative = $images->firstWhere('id', $creativeId);
        if (! $creative || ! $this->matchesRequiredDimensions($creative, $requiredRatio)) {
            return Response::error('That image creative is not available. Ask the user to select one of the shown images.');
        }

        $connection = GoogleAdsConnection::query()->where('user_id', $user?->getKey())->first();
        if ($connection === null || blank($connection->refresh_token) || blank($connection->customer_id)) {
            return Response::error('Connect Google Ads and select a child account before uploading an image.');
        }

        $asset = $creative->asset;
        $disk = Storage::disk($asset->disk ?: 'local');

        // Checked before reading, like GooglePublisher and AssetLibrary already
        // do for the same asset.
        //
        // Storage::get() returns null for a file that is not there, and
        // createImageAsset() takes a non-nullable string, so a missing file
        // became a TypeError. It reached the buyer verbatim on 22 Sep as
        // "createImageAsset(): Argument #2 ($contents) must be of type string,
        // null given, called in /var/www/php85/manoj/...", which tells them
        // nothing they can act on and prints the server's filesystem layout
        // into the chat.
        if (! $disk->exists($asset->path)) {
            return Response::error(
                'That image is no longer on disk, so it could not be uploaded. Ask the user to pick '
                .'another creative or import the image again.'
            );
        }

        $contents = $disk->get($asset->path);
        $resourceName = $this->googleAds->createImageAsset($connection, $contents, $asset->original_name ?: 'Google Ads image');
        $this->saveDraftImage($data['image_field'], $resourceName, $user);

        return Response::json([
            'ok' => true,
            'creative_id' => $creative->id,
            'image_field' => $data['image_field'],
            'asset_resource_name' => $resourceName,
            'message' => sprintf('Uploaded %s for %s.', $asset->original_name ?: 'the selected image', $data['image_field']),
        ]);
    }

    private function matchesRequiredDimensions(Creative $creative, string $requiredRatio): bool
    {
        $requiredSize = $requiredRatio === '1:1' ? [1200, 1200] : [1200, 628];
        if (in_array($creative->ratio, [$requiredRatio, $requiredRatio === '1:1' ? '1:1' : '1.91:1'], true)) {
            return true;
        }

        $asset = $creative->asset;
        $size = $this->imageSize($creative);

        return is_array($size) && [$size[0] ?? null, $size[1] ?? null] === $requiredSize;
    }

    private function imageDimensions(Creative $creative): string
    {
        $size = $this->imageSize($creative);

        return is_array($size) ? ($size[0].'x'.$size[1]) : ((string) ($creative->ratio ?: 'Unknown size'));
    }

    /** @return array{0: int, 1: int}|false */
    private function imageSize(Creative $creative): array|false
    {
        $asset = $creative->asset;
        $path = Storage::disk($asset->disk ?: 'local')->path($asset->path);

        return @getimagesize($path);
    }

    /**
     * Write the chosen image into this user's own campaign draft.
     *
     * The conversation id arrives as request input and was looked up on the id
     * alone. Anybody could therefore pass somebody else's Google conversation
     * and have their uploaded image written into that person's draft, where it
     * would be used the next time that campaign was created. The listing above
     * was scoped by user_id and this was not, so the read was safe and the
     * write was not.
     *
     * Scoped to the participant rather than filtered afterwards, so a
     * conversation belonging to someone else is simply not found.
     */
    private function saveDraftImage(string $field, string $resourceName, Authenticatable $user): void
    {
        $conversationId = $this->httpRequest->input('conversation');
        if (! is_string($conversationId)) {
            return;
        }

        $conversation = Conversation::query()
            ->whereKey($conversationId)
            ->where('chat_type', 'google_ads')
            ->where('participant_type', $user->getMorphClass())
            ->where('participant_id', $user->getAuthIdentifier())
            ->first();

        if (! $conversation) {
            return;
        }

        $draft = json_decode((string) $conversation->google_campaign_draft, true);
        $draft = is_array($draft) ? $draft : [];
        $draft[$field] = $resourceName;
        $conversation->update(['google_campaign_draft' => json_encode($draft)]);
    }
}
