<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

/**
 * Everything needed to take a campaign from a chat to a live ad platform.
 *
 * Two separations are deliberate:
 *
 *   assets vs ads   An upload is bytes. It becomes advertising when it is
 *                   paired with copy. The same asset can back several ads.
 *
 *   asset vs its    The same image uploaded to Meta and to TikTok gets a
 *   platform ref    different id from each, so the id cannot live on the asset.
 */
return new class extends Migration
{
    public function up(): void
    {
        // Delivery settings. Kept on the campaign rather than in a side table
        // because they are one-to-one with it and the publish gate reads them
        // on every turn.
        Schema::table('campaigns', function (Blueprint $table): void {
            $table->string('landing_url')->nullable()->after('objective');
            $table->string('ad_account_id')->nullable()->after('platform');
            $table->string('page_id')->nullable()->after('ad_account_id');
            $table->string('pixel_id')->nullable()->after('page_id');
            $table->string('optimization_goal')->nullable()->after('objective');
            $table->string('bid_strategy')->nullable()->after('budget');
            $table->decimal('bid_amount', 12, 2)->nullable()->after('bid_strategy');
            $table->json('external_ids')->nullable()->after('external_campaign_id');
            $table->text('last_error')->nullable()->after('external_ids');
        });

        // Raw bytes. What the user dropped in the chat, or what a generator
        // produced. Not yet advertising.
        Schema::create('assets', function (Blueprint $table): void {
            $table->id();
            $table->nullableMorphs('owner');
            $table->string('kind', 16)->default('image');       // image|video
            $table->string('source', 24)->default('upload');    // upload|generated|crawled
            $table->string('path');
            $table->string('original_name')->nullable();
            $table->string('mime')->nullable();
            $table->unsignedBigInteger('bytes')->nullable();
            $table->string('checksum', 64)->nullable()->index();
            $table->timestamps();
        });

        // Where an asset ended up on a given platform and account. One asset
        // has as many of these as the accounts it has been pushed to.
        Schema::create('asset_platform_refs', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('asset_id')->constrained()->cascadeOnDelete();
            $table->string('platform', 24);
            $table->string('ad_account_id');
            $table->string('external_hash')->nullable();   // images
            $table->string('external_id')->nullable();     // videos
            $table->timestamps();

            $table->unique(['asset_id', 'platform', 'ad_account_id'], 'asset_platform_account_unique');
        });

        // One ad: an asset plus the words around it. Rows are created in the
        // chat and carry their platform ids once published, which is what the
        // rollback path unwinds.
        Schema::create('campaign_ads', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('campaign_id')->constrained()->cascadeOnDelete();
            $table->foreignId('asset_id')->nullable()->constrained()->nullOnDelete();
            $table->string('headline');
            $table->text('primary_text');
            $table->string('description')->nullable();
            $table->string('cta', 32);
            $table->string('external_creative_id')->nullable();
            $table->string('external_ad_id')->nullable();
            $table->string('status', 16)->default('draft');   // draft|published|failed
            $table->timestamps();
        });

        // Every call to a platform, whichever transport made it. Written even
        // when the call fails, because a failed mutation may still have created
        // something that needs removing.
        Schema::create('api_calls', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('campaign_id')->nullable()->constrained()->nullOnDelete();
            $table->string('actor')->nullable();
            $table->string('platform', 24)->default('meta');
            $table->string('transport', 16);                  // graph|mcp
            $table->string('method', 16);
            $table->string('endpoint');
            $table->string('ad_account_id')->nullable()->index();
            $table->boolean('mutating')->default(false);
            $table->boolean('dry_run')->default(false);
            $table->json('request')->nullable();
            $table->json('response')->nullable();
            $table->unsignedSmallInteger('status')->nullable();
            $table->boolean('ok')->default(true);
            $table->string('object_id')->nullable();
            $table->text('error')->nullable();
            $table->unsignedInteger('duration_ms')->nullable();
            $table->timestamps();

            // The hourly call budget counts rows in a window per account.
            $table->index(['ad_account_id', 'created_at'], 'api_calls_budget_index');
        });

        // A proposal, and what became of it. The agent may only propose;
        // a human approves, and the approval is recorded separately from
        // the thing it approved.
        Schema::create('decisions', function (Blueprint $table): void {
            $table->id();
            $table->string('origin', 16)->default('agent');
            $table->string('action', 48);
            $table->string('subject_type', 48);
            $table->string('subject_id', 48);
            $table->json('params')->nullable();
            $table->json('evidence')->nullable();
            $table->json('result')->nullable();
            $table->string('state', 16)->default('proposed'); // proposed|blocked|applied
            $table->string('actor')->nullable();
            $table->text('reason')->nullable();
            $table->timestamp('applied_at')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('decisions');
        Schema::dropIfExists('api_calls');
        Schema::dropIfExists('campaign_ads');
        Schema::dropIfExists('asset_platform_refs');
        Schema::dropIfExists('assets');

        Schema::table('campaigns', function (Blueprint $table): void {
            $table->dropColumn([
                'landing_url', 'ad_account_id', 'page_id', 'pixel_id',
                'optimization_goal', 'bid_strategy', 'bid_amount',
                'external_ids', 'last_error',
            ]);
        });
    }
};
