<?php

namespace App\Mcp\Tools\TikTok;

use App\Agent\Choices;
use App\Agent\Workspace;
use App\Models\Campaign;
use App\Models\TikTokConnection;
use App\Services\TikTokService;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Http\Request as HttpRequest;
use Illuminate\Support\Carbon;
use Laravel\Ai\Models\Conversation;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
use Throwable;

#[Description('Create a disabled TikTok campaign, ad group, and ad from a validated campaign draft. The tool derives safe platform defaults and returns all TikTok IDs. Use only after explicit user approval.')]
class CreateTikTokAd extends Tool
{
    public function __construct(
        private readonly TikTokService $tiktok,
        private readonly Workspace $workspace,
        private readonly HttpRequest $httpRequest,
        private readonly Choices $choices,
    ) {}

    public function schema(JsonSchema $schema): array
    {
        return [
            'campaign_name' => $schema->string()->description('Campaign name')->required(),
            'existing_campaign_id' => $schema->string()->description('Existing disabled TikTok campaign ID to resume after an ad-group failure'),
            'existing_adgroup_id' => $schema->string()->description('Existing disabled TikTok ad group ID to resume after an ad-creation failure'),
            'objective_type' => $schema->string()->description('Optional TikTok objective; defaults to TRAFFIC'),
            'adgroup_name' => $schema->string()->description('Optional ad group name; derived from campaign name'),
            'promotion_type' => $schema->string()->description('Optional promotion type; defaults to WEBSITE'),
            'optimization_goal' => $schema->string()->description('Optional optimization goal; defaults to CLICK'),
            'billing_event' => $schema->string()->description('Optional billing event; defaults to CPC'),
            'bid_amount' => $schema->number()->description('Positive CPM bid for Reach campaigns; defaults to configured TikTok CPM bid'),
            'cost_per_conversion' => $schema->number()->description('Positive cost per conversion for Lead Generation; defaults to configured value'),
            'frequency_cap' => $schema->integer()->description('Maximum Reach impressions per frequency period; defaults to configured daily cap'),
            'budget_mode' => $schema->string()->description('Optional ad-group budget mode; defaults to BUDGET_MODE_DAY'),
            'budget' => $schema->number()->description('Ad group budget in USD; converted to the configured TikTok account currency')->required(),
            'pacing' => $schema->string()->description('Optional pacing; defaults to PACING_MODE_SMOOTH'),
            'schedule_type' => $schema->string()->description('Optional schedule; defaults to SCHEDULE_FROM_NOW'),
            'schedule_start_time' => $schema->string()->description('Optional future advertiser-local start time; defaults to ten minutes from now'),
            'location_ids' => $schema->array()->description('Optional TikTok location IDs; defaults to configured market'),
            'placements' => $schema->array()->description('Manual TikTok placement; only PLACEMENT_TIKTOK is supported for this flow'),
            'identity_type' => $schema->string()->description('Optional persisted TikTok identity type override'),
            'identity_id' => $schema->string()->description('Optional persisted TikTok identity ID override'),
            'ad_name' => $schema->string()->description('Optional ad name; derived from campaign name'),
            'ad_format' => $schema->string()->description('Optional ad format; derived from selected media'),
            'ad_text' => $schema->string()->description('Primary ad text')->required(),
            'call_to_action' => $schema->string()->description('Optional call to action; defaults to LEARN_MORE'),
            'landing_page_url' => $schema->string()->description('Landing page URL')->required(),
            'pixel_id' => $schema->string()->description('TikTok conversion pixel ID for website conversion objectives'),
            'conversion_event' => $schema->string()->description('TikTok conversion event for the selected pixel'),
            'video_id' => $schema->string()->description('Deprecated for remote creation; authorized TikTok posts are used instead'),
            'image_ids' => $schema->array()->description('Deprecated for remote creation; authorized TikTok posts are used instead'),
        ];
    }

    public function handle(Request $request): Response
    {
        $user = $request->user();
        if ($user === null) {
            return Response::error('Authentication is required.');
        }

        $data = $request->validate([
            'campaign_name' => ['required', 'string', 'max:512'],
            'existing_campaign_id' => ['nullable', 'string', 'max:255'],
            'existing_adgroup_id' => ['nullable', 'string', 'max:255'],
            'objective_type' => ['nullable', 'string', 'max:100'],
            'adgroup_name' => ['nullable', 'string', 'max:512'],
            'promotion_type' => ['nullable', 'string', 'max:100'],
            'optimization_goal' => ['nullable', 'string', 'max:100'],
            'billing_event' => ['nullable', 'string', 'max:100'],
            'bid_amount' => ['nullable', 'numeric', 'min:0.01'],
            'cost_per_conversion' => ['nullable', 'numeric', 'min:0.01'],
            'frequency_cap' => ['nullable', 'integer', 'min:1'],
            'budget_mode' => ['nullable', 'string', 'max:100'],
            'budget' => ['required', 'numeric', 'min:0.01'],
            'pacing' => ['nullable', 'string', 'max:100'],
            'schedule_type' => ['nullable', 'string', 'max:100'],
            'schedule_start_time' => ['nullable', 'date_format:Y-m-d H:i:s'],
            'location_ids' => ['nullable', 'array', 'min:1'],
            'location_ids.*' => ['required', 'string'],
            'placements' => ['nullable', 'array'],
            'placements.*' => ['required', 'string'],
            'identity_type' => ['nullable', 'string', 'max:100'],
            'identity_id' => ['nullable', 'string', 'max:255'],
            'ad_name' => ['nullable', 'string', 'max:512'],
            'ad_format' => ['nullable', 'string', 'max:100'],
            'ad_text' => ['required', 'string', 'max:1000'],
            'call_to_action' => ['nullable', 'string', 'max:100'],
            'landing_page_url' => ['required', 'url', 'max:2048'],
            'pixel_id' => ['nullable', 'string', 'max:255'],
            'conversion_event' => ['nullable', 'string', 'max:100'],
            'video_id' => ['nullable', 'string'],
            'image_ids' => ['nullable', 'array', 'min:1'],
            'image_ids.*' => ['required', 'string'],
        ]);

        $connection = TikTokConnection::query()->where('user_id', $user->getAuthIdentifier())->first();
        if ($connection === null || blank($connection->access_token) || blank($connection->advertiser_id)) {
            return Response::error('Connect TikTok Ads and select an advertiser before creating an ad.');
        }

        $conversationId = $this->httpRequest->input('conversation');
        $conversation = null;
        $draft = [];
        $remoteImage = false;
        $remoteVideo = false;
        $videoId = null;
        $existingCampaignId = null;
        $existingAdGroupId = null;
        $draftCostPerConversion = null;
        if (is_string($conversationId) && filled($conversationId)) {
            $conversation = Conversation::query()
                ->whereKey($conversationId)
                ->where('chat_type', 'tiktok_ads')
                ->where('participant_type', $user->getMorphClass())
                ->where('participant_id', $user->getKey())
                ->first();
            if ($conversation === null) {
                return Response::error('TikTok chat conversation not found.');
            }
            $draft = json_decode((string) $conversation?->tiktok_campaign_draft, true);
            $draft = is_array($draft) ? $draft : [];
            $existingCampaignId = $data['existing_campaign_id'] ?? ($draft['created_campaign_id'] ?? null);
            $existingAdGroupId = $data['existing_adgroup_id'] ?? ($draft['created_adgroup_id'] ?? null);
            if (preg_match('/([0-9]+(?:\.[0-9]+)?)/', (string) ($draft['bid_cap'] ?? ''), $matches)) {
                $draftCostPerConversion = (float) $matches[1];
            }
            $imageIds = $data['image_ids'] ?? (is_array($draft['image_ids'] ?? null) ? $draft['image_ids'] : []);
            if ($imageIds === [] && filled($draft['creative_asset_ids'] ?? null)) {
                $imageIds = array_values(array_filter(array_map('trim', explode(',', (string) $draft['creative_asset_ids']))));
            }
            $videoId = $data['video_id'] ?? ($draft['video_id'] ?? null);
            if (($draft['validation_status'] ?? null) !== 'passed') {
                return Response::error('Validate the complete TikTok campaign draft before creating it.');
            }
            $remoteImage = strtolower((string) ($draft['creative_source'] ?? '')) === 'upload_image'
                && strtolower((string) ($draft['creative_format'] ?? '')) === 'single_image'
                && strtolower((string) ($draft['identity_mode'] ?? '')) === 'non_spark'
                && strtoupper((string) $connection->identity_type) === 'CUSTOMIZED_USER'
                && $imageIds !== [];
            $remoteVideo = strtolower((string) ($draft['creative_source'] ?? '')) === 'upload_video'
                && strtolower((string) ($draft['creative_format'] ?? '')) === 'single_video'
                && strtolower((string) ($draft['identity_mode'] ?? '')) === 'non_spark'
                && filled($videoId);
            $remoteSpark = strtolower((string) ($draft['creative_source'] ?? '')) === 'tiktok_post'
                && strtolower((string) ($draft['creative_format'] ?? '')) === 'single_video'
                && strtolower((string) ($draft['identity_mode'] ?? '')) === 'spark';
            if (strtolower((string) ($draft['campaign_type'] ?? '')) !== 'manual'
                || strtolower((string) ($draft['promotion_type'] ?? '')) !== 'website'
                || (! $remoteSpark && ! $remoteImage && ! $remoteVideo)) {
                return Response::error('Only manual website campaigns with a connected Spark post, uploaded image, or uploaded video can be created remotely.');
            }
        }
        $placements = $this->placements($data['placements'] ?? []);
        if ($placements === null) {
            return Response::error('This campaign supports the TikTok manual placement only. Select TikTok to continue.');
        }
        $accountCurrency = strtoupper((string) config('services.tiktok.account_currency', 'USD'));
        $accountBudget = $this->accountCurrencyAmount((float) $data['budget']);
        if ($accountBudget === null) {
            return Response::error('Configure a positive TikTok USD-to-account-currency conversion rate before creating an ad.');
        }
        $objectiveType = strtoupper((string) ($data['objective_type'] ?? $draft['objective'] ?? 'TRAFFIC'));
        if (in_array($objectiveType, ['AWARENESS', 'REACH_FREQUENCY'], true)) {
            $objectiveType = 'REACH';
        }
        $optimizationGoal = $this->optimizationGoal((string) ($data['optimization_goal'] ?? $draft['optimization_goal'] ?? 'CLICK'));
        if ($optimizationGoal === 'REACH') {
            $objectiveType = 'REACH';
        }
        if ($objectiveType === 'REACH') {
            $optimizationGoal = 'REACH';
        }
        if ($objectiveType === 'LEAD_GENERATION') {
            $optimizationGoal = 'LEAD_GENERATION';
        }
        if ($objectiveType === 'PRODUCT_SALES') {
            $optimizationGoal = 'CONVERT';
        }
        $isReach = $objectiveType === 'REACH';
        $promotionType = $objectiveType === 'LEAD_GENERATION'
            ? 'LEAD_GENERATION'
            : strtoupper((string) ($data['promotion_type'] ?? $draft['promotion_type'] ?? 'WEBSITE'));
        if ($objectiveType === 'SALES') {
            $objectiveType = $promotionType === 'WEBSITE' ? 'WEB_CONVERSIONS' : 'PRODUCT_SALES';
            $optimizationGoal = 'CONVERT';
        }
        $billingEvent = $isReach ? 'CPM' : ($optimizationGoal === 'CLICK' ? 'CPC' : strtoupper((string) ($data['billing_event'] ?? 'OCPM')));
        $bidAmount = $isReach ? (float) ($data['bid_amount'] ?? config('services.tiktok.default_cpm_bid', 1)) : null;
        $frequencyCap = $isReach
            ? (int) ($data['frequency_cap'] ?? $draft['frequency_cap'] ?? config('services.tiktok.default_frequency_cap', 3))
            : null;
        $isLeadGeneration = $objectiveType === 'LEAD_GENERATION';
        $costPerConversion = $isLeadGeneration
            ? (float) ($data['cost_per_conversion'] ?? $draftCostPerConversion ?? config('services.tiktok.default_cost_per_conversion', 5))
            : null;
        if ($isReach && $bidAmount <= 0) {
            return Response::error('Reach campaigns require a positive CPM bid.');
        }
        if ($isReach && $frequencyCap <= 0) {
            return Response::error('Reach campaigns require a positive frequency cap.');
        }
        if ($isLeadGeneration && $costPerConversion <= 0) {
            return Response::error('Lead Generation campaigns require a positive cost per conversion.');
        }
        $campaignName = $data['campaign_name'];
        $adGroupName = $data['adgroup_name'] ?? $campaignName.' - Ad Group';
        $adName = $data['ad_name'] ?? $campaignName.' - Ad';
        $locationIds = $data['location_ids'] ?? [(string) config('services.tiktok.default_location_id', '6252001')];
        $scheduleStartTime = $this->scheduleStartTime($data['schedule_start_time'] ?? null);
        $adFormat = $data['ad_format'] ?? 'SINGLE_VIDEO';
        $trackingPixelId = $data['pixel_id'] ?? ($draft['pixel_id'] ?? null);
        $trackingEvent = $data['conversion_event'] ?? ($draft['conversion_event'] ?? null);

        if ($remoteImage && (strtoupper((string) $connection->identity_type) !== 'CUSTOMIZED_USER' || blank($connection->identity_id))) {
            return Response::error('Select a TikTok custom identity before creating an uploaded image ad.');
        }
        if (! $remoteImage && ($connection->identity_type !== 'BC_AUTH_TT' || blank($connection->identity_authorized_bc_id))) {
            return Response::error('Authorize a TikTok account through Business Center before creating a Spark ad.');
        }
        if (! $remoteImage && blank($connection->tiktok_item_id)) {
            try {
                $posts = $this->tiktok->identityPosts($connection);
            } catch (Throwable $exception) {
                report($exception);

                return Response::error($exception->getMessage());
            }
            if (count($posts) !== 1) {
                return Response::error('Select one authorized TikTok post before creating the ad.');
            }
            $connection->forceFill(['tiktok_posts' => $posts, 'tiktok_item_id' => $posts[0]['item_id']])->save();
        }

        try {
            $creative = array_filter([
                'ad_name' => $adName,
                'ad_format' => $adFormat,
                'ad_text' => $data['ad_text'],
                'call_to_action' => strtoupper((string) ($data['call_to_action'] ?? 'LEARN_MORE')),
                'landing_page_url' => $data['landing_page_url'],
                'identity_type' => $connection->identity_type,
                'identity_id' => $connection->identity_id,
                'identity_authorized_bc_id' => $remoteImage ? null : $connection->identity_authorized_bc_id,
                'tiktok_item_id' => $remoteImage ? null : $connection->tiktok_item_id,
                'image_ids' => $remoteImage ? $imageIds : null,
                'video_id' => $remoteVideo ? $videoId : null,
                'tracking_pixel_id' => $trackingPixelId,
                'tracking_event' => $trackingEvent,
            ], fn (mixed $value): bool => $value !== null);
            $ids = $this->tiktok->createPausedAd(
                $connection,
                [
                    'campaign_name' => $campaignName,
                    'objective_type' => $objectiveType,
                    'budget_mode' => 'BUDGET_MODE_INFINITE',
                    'budget_optimize_on' => false,
                ],
                array_filter([
                    'adgroup_name' => $adGroupName,
                    'promotion_type' => $promotionType,
                    'optimization_goal' => $optimizationGoal,
                    'pixel_id' => $trackingPixelId,
                    'conversion_event' => $trackingEvent,
                    'billing_event' => $billingEvent,
                    'bid_type' => ($isReach || $isLeadGeneration) ? 'BID_TYPE_CUSTOM' : ($optimizationGoal === 'CLICK' ? 'BID_TYPE_NO_BID' : null),
                    'bid_price' => $isReach ? $bidAmount : ($isLeadGeneration ? $costPerConversion : null),
                    'conversion_bid_price' => $isLeadGeneration ? $costPerConversion : null,
                    'frequency_control_mode' => $isReach ? 'FREQUENCY_CONTROL_MODE_STANDARD' : null,
                    'frequency_control_period' => $isReach ? 'FREQUENCY_CONTROL_PERIOD_DAY' : null,
                    'frequency_control_count' => $frequencyCap,
                    'budget_mode' => strtoupper((string) ($data['budget_mode'] ?? 'BUDGET_MODE_DAY')),
                    'budget' => $accountBudget,
                    'pacing' => strtoupper((string) ($data['pacing'] ?? 'PACING_MODE_SMOOTH')),
                    'schedule_type' => strtoupper((string) ($data['schedule_type'] ?? 'SCHEDULE_FROM_NOW')),
                    'schedule_start_time' => $scheduleStartTime,
                    'location_ids' => $locationIds,
                    'placement_type' => 'PLACEMENT_TYPE_NORMAL',
                    'placements' => $placements,
                ], fn (mixed $value): bool => $value !== null),
                $creative,
                $existingCampaignId ?? $data['existing_campaign_id'] ?? null,
                $existingAdGroupId ?? $data['existing_adgroup_id'] ?? null,
            );
        } catch (Throwable $exception) {
            report($exception);

            $message = $exception->getMessage();
            if (preg_match('/created disabled campaign ([^,]+), but ad group creation failed:/', $message, $matches) === 1) {
                $campaignId = $matches[1];
                $this->rememberPartialCreation($conversation, $draft, $campaignId);
                $this->choices->offer(
                    field: 'tiktok_creation_retry',
                    question: 'The TikTok campaign was created, but the ad group failed. What should I do?',
                    options: [[
                        'value' => 'retry_ad_group',
                        'label' => 'Retry ad group creation',
                        'hint' => 'Resume the existing disabled campaign without creating a duplicate.',
                        'sends' => "Retry TikTok ad group creation with existing_campaign_id {$campaignId}, objective_type {$objectiveType}, and frequency_cap {$frequencyCap}.",
                    ], [
                        'value' => 'stop_creation',
                        'label' => 'Stop and review the error',
                        'hint' => 'Leave the disabled campaign available for later.',
                        'sends' => 'Stop TikTok creation and show me the error details.',
                    ]],
                );
            } elseif (preg_match('/created disabled campaign ([^ ]+) and ad group ([^,]+), but ad creation failed:/', $message, $matches) === 1) {
                $campaignId = $matches[1];
                $adGroupId = $matches[2];
                $this->rememberPartialCreation($conversation, $draft, $campaignId, $adGroupId);
                $this->choices->offer(
                    field: 'tiktok_creation_retry',
                    question: 'The TikTok campaign and ad group were created, but the ad failed. What should I do?',
                    options: [[
                        'value' => 'retry_ad',
                        'label' => 'Retry ad creation',
                        'hint' => 'Resume both existing disabled parent objects without creating duplicates.',
                        'sends' => "Retry TikTok ad creation with existing_campaign_id {$campaignId}, existing_adgroup_id {$adGroupId}, objective_type {$objectiveType}, and frequency_cap {$frequencyCap}.",
                    ], [
                        'value' => 'stop_creation',
                        'label' => 'Stop and review the error',
                        'hint' => 'Leave the disabled campaign and ad group available for later.',
                        'sends' => 'Stop TikTok creation and show me the error details.',
                    ]],
                );
            }

            return Response::error($message);
        }

        $campaign = Campaign::query()->create([
            'user_id' => $user->getAuthIdentifier(),
            'name' => $campaignName,
            'platform' => 'tiktok',
            'objective' => $objectiveType,
            'budget' => $accountBudget,
            'currency' => $accountCurrency,
            'status' => 'paused',
            'ad_account_id' => $connection->advertiser_id,
            'landing_url' => $data['landing_page_url'],
            'optimization_goal' => $optimizationGoal,
            'locations' => $locationIds,
            'placements' => $placements,
            'external_campaign_id' => $ids['campaign_id'],
            'external_ids' => [
                'campaign_id' => $ids['campaign_id'],
                'adgroup_id' => $ids['adgroup_id'],
                'ad_id' => $ids['ad_id'],
            ],
            'meta' => [
                'created_on_tiktok' => true,
                'operation_status' => 'DISABLE',
                'adgroup_name' => $adGroupName,
                'ad_name' => $adName,
                'input_budget_usd' => (float) $data['budget'],
                'account_budget' => $accountBudget,
                'account_currency' => $accountCurrency,
                'frequency_cap' => $frequencyCap,
            ],
        ]);
        $this->workspace->produced($campaign);

        if ($conversation !== null) {
            $draft = json_decode((string) $conversation->tiktok_campaign_draft, true);
            $draft = is_array($draft) ? $draft : [];
            $draft['created_campaign_id'] = $ids['campaign_id'];
            $draft['created_adgroup_id'] = $ids['adgroup_id'];
            $draft['created_ad_id'] = $ids['ad_id'];
            $draft['api_status'] = 'paused';
            $conversation->update(['tiktok_campaign_draft' => json_encode($draft)]);
        }

        return Response::json([
            'ok' => true,
            'created_on_tiktok' => true,
            'operation_status' => 'DISABLE',
            'budget' => $accountBudget,
            'currency' => $accountCurrency,
            ...$ids,
            'local_campaign_id' => $campaign->getKey(),
            'next' => $conversation !== null ? 'publish_action' : null,
            'publish_options' => $conversation !== null ? ['publish', 'keep_paused', 'edit'] : [],
        ]);
    }

    private function rememberPartialCreation(?Conversation $conversation, array $draft, string $campaignId, ?string $adGroupId = null): void
    {
        if ($conversation === null) {
            return;
        }

        $draft['created_campaign_id'] = $campaignId;
        if ($adGroupId !== null) {
            $draft['created_adgroup_id'] = $adGroupId;
        }
        $draft['api_status'] = 'partial';
        $conversation->update(['tiktok_campaign_draft' => json_encode($draft)]);
    }

    private function optimizationGoal(string $value): string
    {
        return match (strtoupper($value)) {
            'LANDING_PAGE_VIEW', 'TRAFFIC_LANDING_PAGE_VIEW' => 'CLICK',
            'FOCUSED_VIEW_6S' => 'ENGAGED_VIEW',
            'FOCUSED_VIEW_15S' => 'ENGAGED_VIEW_FIFTEEN',
            'CONVERSION' => 'CONVERT',
            'FOLLOW' => 'FOLLOWERS',
            'PROFILE_VISIT' => 'PROFILE_VIEWS',
            default => strtoupper($value),
        };
    }

    private function accountCurrencyAmount(float $usdAmount): ?float
    {
        $accountCurrency = strtoupper((string) config('services.tiktok.account_currency', 'USD'));
        $rate = $accountCurrency === 'USD' ? 1.0 : (float) config('services.tiktok.usd_to_account_rate', 0);

        return $rate > 0 ? round($usdAmount * $rate, 2) : null;
    }

    private function scheduleStartTime(?string $requested): string
    {
        $timezone = (string) config('services.tiktok.account_timezone', 'UTC');
        $minimum = now($timezone)->addMinutes(10)->startOfMinute();
        if (filled($requested)) {
            $requestedTime = Carbon::createFromFormat('Y-m-d H:i:s', $requested, $timezone);
            if ($requestedTime->greaterThan($minimum)) {
                return $requestedTime->format('Y-m-d H:i:s');
            }
        }

        return $minimum->format('Y-m-d H:i:s');
    }

    /** @param list<string> $placements
     * @return list<string>|null
     */
    private function placements(array $placements): ?array
    {
        if ($placements === []) {
            return ['PLACEMENT_TIKTOK'];
        }

        $normalized = array_values(array_unique(array_map(
            fn (string $placement): string => strtoupper($placement) === 'TIKTOK' ? 'PLACEMENT_TIKTOK' : strtoupper($placement),
            $placements,
        )));

        return $normalized === ['PLACEMENT_TIKTOK'] ? $normalized : null;
    }
}
