<?php

namespace App\Campaigns\Publishing;

use App\Campaigns\Platforms\TikTokRules;
use App\Campaigns\TrackedUrl;
use App\Models\Campaign;
use App\Models\TikTokConnection;
use App\Services\TikTokService;
use Illuminate\Support\Facades\Log;
use RuntimeException;
use Throwable;

class TikTokPublisher implements PlatformPublisher
{
    public function __construct(
        private readonly TikTokService $tiktok,
        private readonly TikTokRules $rules,
    ) {}

    /**
     * @param  array<string, mixed>  $created
     * @return array<string, mixed>
     */
    public function create(Campaign $campaign, string $actor, array &$created): array
    {
        $connection = $this->connection();
        $advertiserId = (string) ($campaign->ad_account_id ?: $connection->advertiser_id);

        if ($advertiserId === '' || (string) $connection->advertiser_id !== $advertiserId) {
            throw new RuntimeException('The connected TikTok advertiser does not match this campaign.');
        }

        if (blank($connection->identity_type) || blank($connection->identity_id)) {
            throw new RuntimeException('Authorize a TikTok identity before publishing.');
        }

        $ad = $campaign->ads()->with('asset')->orderBy('id')->first();
        if ($ad === null || $ad->asset === null) {
            throw new RuntimeException('TikTok requires at least one creative asset.');
        }

        $creative = [
            'ad_name' => $campaign->name.' - Ad',
            'ad_format' => strtoupper($ad->asset->kind) === 'VIDEO' ? 'SINGLE_VIDEO' : 'SINGLE_IMAGE',
            'ad_text' => $ad->primary_text ?: $ad->description ?: $ad->headline,
            'call_to_action' => strtoupper($ad->cta ?: 'LEARN_MORE'),
            'landing_page_url' => TrackedUrl::for($campaign),
            'identity_type' => $connection->identity_type,
            'identity_id' => $connection->identity_id,
            'identity_authorized_bc_id' => $connection->identity_authorized_bc_id,
            'tiktok_item_id' => $connection->tiktok_item_id,
            'tracking_pixel_id' => $campaign->pixel_id,
            'tracking_event' => $this->rules->conversionEvent((string) $campaign->objective),
        ];

        if ($connection->identity_type !== 'BC_AUTH_TT') {
            $creative['image_ids'] = [$this->tiktok->uploadCreative($connection, $ad->asset)];
            $creative['identity_authorized_bc_id'] = null;
            $creative['tiktok_item_id'] = null;
        } elseif (blank($creative['tiktok_item_id'])) {
            throw new RuntimeException('Select an authorized TikTok post before publishing a Spark ad.');
        }

        $objective = strtoupper((string) $campaign->objective);
        $isReach = in_array($objective, ['REACH', 'AWARENESS', 'OUTCOME_REACH', 'OUTCOME_AWARENESS'], true);
        $objectiveType = $isReach ? 'REACH' : ($objective === 'SALES' ? 'WEB_CONVERSIONS' : $objective);
        $optimizationGoal = $isReach ? 'REACH' : ($this->rules->optimizationGoal($objective) ?: 'CLICK');
        $accountBudget = (float) $campaign->budget * (float) config('services.tiktok.usd_to_account_rate', 1);
        $scheduleStartTime = $campaign->starts_at?->format('Y-m-d H:i:s')
            ?? now()->addMinutes(5)->format('Y-m-d H:i:s');
        $locationIds = array_values(array_filter(array_map(
            'strval',
            (array) data_get($campaign->locations, 'location_ids', [config('services.tiktok.default_location_id')]),
        )));
        $remoteCampaignName = mb_substr($campaign->name, 0, 480).' [chat '.$campaign->id.']';

        $ids = $this->tiktok->createPausedAd(
            $connection,
            [
                'campaign_name' => $remoteCampaignName,
                'objective_type' => $objectiveType,
                'budget_mode' => 'BUDGET_MODE_INFINITE',
                'budget_optimize_on' => false,
            ],
            array_filter([
                'adgroup_name' => $remoteCampaignName.' - Ad Group',
                'promotion_type' => $objective === 'LEAD_GENERATION' ? 'LEAD_GENERATION' : 'WEBSITE',
                'optimization_goal' => $optimizationGoal,
                'pixel_id' => $campaign->pixel_id,
                'conversion_event' => $this->rules->conversionEvent((string) $campaign->objective),
                'billing_event' => $isReach ? 'CPM' : ($optimizationGoal === 'CLICK' ? 'CPC' : 'OCPM'),
                'bid_type' => $isReach ? 'BID_TYPE_CUSTOM' : ($campaign->bid_strategy ?: 'BID_TYPE_NO_BID'),
                'bid_price' => $isReach
                    ? (float) ($campaign->bid_amount ?: config('services.tiktok.default_cpm_bid', 1))
                    : ($campaign->bid_amount ? (float) $campaign->bid_amount : null),
                'conversion_bid_price' => $objectiveType === 'LEAD_GENERATION' && $campaign->bid_amount
                    ? (float) $campaign->bid_amount
                    : null,
                'frequency_control_mode' => $isReach ? 'FREQUENCY_CONTROL_MODE_STANDARD' : null,
                'frequency_control_period' => $isReach ? 'FREQUENCY_CONTROL_PERIOD_DAY' : null,
                'frequency_control_count' => $isReach
                    ? (int) data_get($campaign->meta, 'frequency_cap', config('services.tiktok.default_frequency_cap', 3))
                    : null,
                'budget_mode' => 'BUDGET_MODE_DAY',
                'budget' => $accountBudget,
                'pacing' => 'PACING_MODE_SMOOTH',
                'schedule_type' => $campaign->starts_at ? 'SCHEDULE_START_END' : 'SCHEDULE_FROM_NOW',
                'schedule_start_time' => $scheduleStartTime,
                'location_ids' => $locationIds,
                'placement_type' => 'PLACEMENT_TYPE_NORMAL',
                'placements' => ['PLACEMENT_TIKTOK'],
            ], fn (mixed $value): bool => $value !== null),
            $creative,
            null,
            null,
            $created,
        );

        $created['campaign_id'] = $ids['campaign_id'];
        $created['adgroup_id'] = $ids['adgroup_id'];
        $created['objective'] = $objectiveType;
        $created['input_text'] = $ad->primary_text ?: $ad->description ?: $ad->headline;
        $created['ads'] = [[
            'ad_row' => $ad->id,
            'creative_id' => $creative['image_ids'][0] ?? null,
            'ad_id' => $ids['ad_id'],
        ]];
        unset($created['ad_id']);

        $campaign->update([
            'external_campaign_id' => $ids['campaign_id'],
            'external_ids' => [
                'campaign_id' => $ids['campaign_id'],
                'adgroup_id' => $ids['adgroup_id'],
                'ads' => $created['ads'],
            ],
        ]);

        $ad->update([
            'external_creative_id' => $creative['image_ids'][0] ?? null,
            'external_ad_id' => $ids['ad_id'],
            'status' => 'published',
        ]);

        foreach ($campaign->ads()->with('asset')->orderBy('id')->get()->skip(1) as $index => $additionalAd) {
            $additionalCreative = [
                'ad_name' => $campaign->name.' - Ad '.($index + 2),
                'ad_format' => strtoupper($additionalAd->asset->kind) === 'VIDEO' ? 'SINGLE_VIDEO' : 'SINGLE_IMAGE',
                'ad_text' => $additionalAd->primary_text ?: $additionalAd->description ?: $additionalAd->headline,
                'call_to_action' => strtoupper($additionalAd->cta ?: 'LEARN_MORE'),
                'landing_page_url' => TrackedUrl::for($campaign),
                'identity_type' => $connection->identity_type,
                'identity_id' => $connection->identity_id,
                'identity_authorized_bc_id' => $connection->identity_authorized_bc_id,
                'tiktok_item_id' => $connection->tiktok_item_id,
            ];

            if ($connection->identity_type !== 'BC_AUTH_TT') {
                $additionalCreative['image_ids'] = [$this->tiktok->uploadCreative($connection, $additionalAd->asset)];
                $additionalCreative['identity_authorized_bc_id'] = null;
                $additionalCreative['tiktok_item_id'] = null;
            }

            $additionalCreated = [];
            try {
                $additionalIds = $this->tiktok->createPausedAd(
                    $connection,
                    [
                        'campaign_name' => $remoteCampaignName,
                        'objective_type' => $objectiveType,
                        'budget_mode' => 'BUDGET_MODE_INFINITE',
                        'budget_optimize_on' => false,
                    ],
                    [
                        'adgroup_name' => $remoteCampaignName.' - Ad Group',
                        'promotion_type' => 'WEBSITE',
                        'optimization_goal' => $optimizationGoal,
                        'billing_event' => $optimizationGoal === 'CLICK' ? 'CPC' : 'OCPM',
                        'bid_type' => $campaign->bid_strategy ?: 'BID_TYPE_NO_BID',
                        'budget_mode' => 'BUDGET_MODE_DAY',
                        'budget' => $accountBudget,
                        'pacing' => 'PACING_MODE_SMOOTH',
                        'location_ids' => $locationIds,
                        'placement_type' => 'PLACEMENT_TYPE_NORMAL',
                        'placements' => ['PLACEMENT_TIKTOK'],
                    ],
                    $additionalCreative,
                    $ids['campaign_id'],
                    $ids['adgroup_id'],
                    $additionalCreated,
                );
            } catch (Throwable $exception) {
                if (filled($additionalCreated['ad_id'] ?? null)) {
                    $created['ads'][] = ['ad_row' => $additionalAd->id, 'ad_id' => $additionalCreated['ad_id']];
                }

                throw $exception;
            }

            $created['ads'][] = [
                'ad_row' => $additionalAd->id,
                'creative_id' => $additionalCreative['image_ids'][0] ?? null,
                'ad_id' => $additionalIds['ad_id'],
            ];
            $additionalAd->update([
                'external_creative_id' => $additionalCreative['image_ids'][0] ?? null,
                'external_ad_id' => $additionalIds['ad_id'],
                'status' => 'published',
            ]);
        }

        $campaign->update(['external_ids' => [
            'campaign_id' => $ids['campaign_id'],
            'adgroup_id' => $ids['adgroup_id'],
            'ads' => $created['ads'],
        ]]);

        return $created;
    }

    /** @param array<string, mixed> $created */
    public function rollBack(Campaign $campaign, array $created, string $actor): void
    {
        try {
            $connection = $this->connection();
            $this->tiktok->deleteCreated($connection, $created);
        } catch (Throwable $exception) {
            Log::warning('TikTok rollback failed', [
                'campaign' => $campaign->id,
                'error' => $exception->getMessage(),
            ]);
        }

        $campaign->ads()->update([
            'external_creative_id' => null,
            'external_ad_id' => null,
            'status' => 'failed',
        ]);
        $campaign->update(['external_campaign_id' => null, 'external_ids' => null]);
    }

    public function update(Campaign $campaign, array $changes, string $actor): void
    {
        throw new RuntimeException('TikTok live campaign updates are not supported yet.');
    }

    private function connection(): TikTokConnection
    {
        $userId = auth()->id();
        $connection = TikTokConnection::query()->where('user_id', $userId)->first();

        if ($connection === null || blank($connection->access_token)) {
            throw new RuntimeException('Connect TikTok Ads before publishing.');
        }

        return $connection;
    }
}
