
­­­­­­­­­­­­­­­­­­
<!DOCTYPE html>
<html>
<?php
/**
 * TikTok Marketing API - FULL UPDATED SCRIPT
 *
 * ENDPOINTS:
 *   - ?endpoint=campaign  => Campaign DETAILS export
 *   - ?endpoint=adgroup   => Ad Group DETAILS export
 *   - ?endpoint=ad        => Ad DETAILS export
 *   - ?endpoint=creative  => Creative-like DETAILS export
 *   - ?endpoint=insights  => Ad-level INSIGHTS export
 *
 * REQUIRED POST:
 *   AUTH, from=YYYY-MM-DD, to=YYYY-MM-DD
 *
 * RULES:
 *   - Strictly filter to ONLY campaigns where campaign_name LIKE '%changan%' (case-insensitive)
 *   - For endpoint=insights only:
 *       multiply CPC, CPM, CPL, SPEND, BUDGET, CPV, CPA by MULTIPLIER
 */

declare(strict_types=1);

date_default_timezone_set('Asia/Riyadh');

$yesterday = new DateTime('yesterday');
$dttt= $yesterday->format('Y-m-d');


$_POST['from'] = $dttt;
$_POST['to'] = $dttt;

header('Content-Type: application/json; charset=utf-8');

const MULTIPLIER   = 2.5;
const BRAND_FILTER = 'honda';

if (($_POST['AUTH'] ?? '') !== 'a2h1cnJhbS5kaGVkaGlAY29yZTNjb25zdWx0YW5jeS5jb206QkdDamVkZGFoQDY5') {
    http_response_code(401);
    echo json_encode(['ok' => false, 'error' => 'Authentication Error'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}

$ENDPOINT_MODE = strtolower(trim($_GET['endpoint'] ?? 'insights'));
if (!in_array($ENDPOINT_MODE, ['campaign', 'adgroup', 'ad', 'creative', 'insights'], true)) {
    http_response_code(400);
    echo json_encode(['ok' => false, 'error' => 'Invalid endpoint. Use campaign|adgroup|ad|creative|insights'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}

$SINCE = $_POST['from'] ?? '';
$UNTIL = $_POST['to'] ?? '';
if ($SINCE === '' || $UNTIL === '') {
    http_response_code(400);
    echo json_encode(['ok' => false, 'error' => 'Missing from/to date. Send POST: from=YYYY-MM-DD&to=YYYY-MM-DD'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}

/** ------------------- CONFIG ------------------- **/
$ACCESS_TOKEN  = getenv('TIKTOK_ACCESS_TOKEN') ?: 'd7b58ded21106349df16e911178ff653c42bcfbb';
$ADVERTISER_ID = getenv('TIKTOK_ADVERTISER_ID') ?: '7176627208012824577';
$API_VERSION   = getenv('TIKTOK_API_VERSION') ?: 'v1.3';
$API_BASE      = "https://business-api.tiktok.com/open_api/{$API_VERSION}";

$DB_HOST = getenv('DB_HOST') ?: 'localhost';
$DB_NAME = getenv('DB_NAME') ?: 'coreagen_marketing_insights';
$DB_USER = getenv('DB_USER') ?: 'coreagen_marketing_insights';
$DB_PASS = getenv('DB_PASS') ?: 'Vision@2050';
$DB_PORT = getenv('DB_PORT') ?: '3306';

$RUN_ID = date('Ymd_His') . '_' . bin2hex(random_bytes(4));

/** ------------------- HELPERS ------------------- **/

function export_dashboard_rows(string $dataType, string $dataDate, array $data, string $advertiserId): array {
    $rows = [];

    foreach ($data as $r) {
        $rows[] = [
            'source_system'      => 'tiktok',
            'data_type'          => $dataType,
            'data_date'          => $r['stat_time_day'] ?? $dataDate,
            'advertiser_id'      => $advertiserId,

            'campaign_id'        => $r['campaign_id'] ?? null,
            'campaign_name'      => $r['campaign_name'] ?? null,
            'adgroup_id'         => $r['adgroup_id'] ?? null,
            'adgroup_name'       => $r['adgroup_name'] ?? null,
            'ad_id'              => $r['ad_id'] ?? ($r['id'] ?? null),
            'ad_name'            => $r['ad_name'] ?? ($r['name'] ?? null),

            'campaign_status'    => $r['campaign_status'] ?? ($r['status'] ?? null),
            'adgroup_status'     => $r['adgroup_status'] ?? null,
            'ad_status'          => $r['ad_status'] ?? null,

            'objective_type'     => $r['objective_type'] ?? null,
            'optimization_goal'  => $r['optimization_goal'] ?? null,
            'budget_mode'        => $r['budget_mode'] ?? null,
            'schedule_type'      => $r['schedule_type'] ?? null,

            'campaign_budget'    => isset($r['campaign_budget']) ? (float)$r['campaign_budget'] : (isset($r['budget']) ? (float)$r['budget'] : null),
            'adgroup_budget'     => isset($r['adgroup_budget']) ? (float)$r['adgroup_budget'] : null,

            'impressions'        => isset($r['impressions']) ? (int)$r['impressions'] : null,
            'clicks'             => isset($r['clicks']) ? (int)$r['clicks'] : null,
            'spend'              => isset($r['spend']) ? (float)$r['spend'] : null,
            'ctr'                => isset($r['ctr']) ? (float)$r['ctr'] : null,
            'cpc'                => isset($r['cpc']) ? (float)$r['cpc'] : null,
            'cpm'                => isset($r['cpm']) ? (float)$r['cpm'] : null,

            'leads'              => isset($r['leads']) ? (float)$r['leads'] : null,
            'purchases'          => isset($r['purchases']) ? (float)$r['purchases'] : null,
            'video_actions'      => isset($r['video_actions']) ? (float)$r['video_actions'] : null,
            'cpl'                => isset($r['cpl']) ? (float)$r['cpl'] : null,
            'cpa'                => isset($r['cpa']) ? (float)$r['cpa'] : null,
            'cpv'                => isset($r['cpv']) ? (float)$r['cpv'] : null,

            'creative_id'        => $r['creative_id'] ?? null,
            'creative_name'      => $r['creative_name'] ?? null,
            'image_url'          => $r['image_url'] ?? null,
            'video_id'           => $r['video_id'] ?? null,
            'landing_page_url'   => $r['landing_page_url'] ?? null,
            'call_to_action'     => $r['call_to_action'] ?? null,

            'create_time'        => $r['create_time'] ?? null,
            'modify_time'        => $r['modify_time'] ?? null
        ];
    }

    return $rows;
}


function strtolower_safe($v): string {
    return strtolower((string)$v);
}

function is_changan_campaign(?string $campaignName): bool {
    $n = strtolower_safe($campaignName);
    return ($n !== '' && strpos($n, BRAND_FILTER) !== false);
}

function to_float($v): ?float {
    if ($v === null || $v === '') return null;
    if (is_numeric($v)) return (float)$v;
    return null;
}

function to_int($v): ?int {
    if ($v === null || $v === '') return null;
    if (is_numeric($v)) return (int)$v;
    return null;
}

function multiply_if_numeric(array &$arr, string $key, float $mult): void {
    if (!array_key_exists($key, $arr)) return;
    $f = to_float($arr[$key]);
    if ($f === null) return;
    $arr[$key] = $f * $mult;
}

function today_date(): string {
    return date('Y-m-d');
}

function iso_now_micro(): string {
    $micro = microtime(true);
    $dt = DateTime::createFromFormat('U.u', sprintf('%.6f', $micro));
    if (!$dt) return date('Y-m-d\TH:i:s');
    return $dt->format('Y-m-d\TH:i:s.u');
}

function safe_json($value): string {
    return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

function export_payload(string $dataType, string $dataDate, array $data): array {
    return [
        'extraction_time' => iso_now_micro(),
        'run_date'        => today_date(),
        'data_date'       => $dataDate,
        'source_system'   => 'tiktok',
        'data_type'       => $dataType,
        'record_count'    => count($data),
        'data'            => array_values($data),
    ];
}

function first_non_empty(array $values): ?string {
    foreach ($values as $v) {
        if (is_string($v) && trim($v) !== '') return $v;
    }
    return null;
}

function parse_page_info(array $pageInfo): array {
    $page       = (int)($pageInfo['page'] ?? 1);
    $pageSize   = (int)($pageInfo['page_size'] ?? 100);
    $total      = (int)($pageInfo['total_number'] ?? 0);
    $totalPages = ($pageSize > 0) ? (int)ceil($total / $pageSize) : 1;

    return [
        'page'        => max(1, $page),
        'page_size'   => max(1, $pageSize),
        'total'       => max(0, $total),
        'total_pages' => max(1, $totalPages),
    ];
}

function compute_tiktok_kpis(array &$row): void {
    $spend       = to_float($row['spend'] ?? null) ?? 0.0;
    $clicks      = to_float($row['clicks'] ?? null) ?? 0.0;
    $impressions = to_float($row['impressions'] ?? null) ?? 0.0;

    $leads = 0.0;
foreach (['submit_form', 'conversion', 'conversions', 'leads', 'form'] as $k) {
    if (array_key_exists($k, $row) && is_numeric($row[$k])) {
        $leads = (float)$row[$k];
        break;
    }
}

    $purchases = 0.0;
    foreach (['purchase', 'result'] as $k) {
        if (isset($row[$k]) && is_numeric($row[$k])) {
            $purchases = (float)$row[$k];
            break;
        }
    }

    $video = 0.0;
    foreach (['video_views_p100', 'video_play_actions', 'video_views'] as $k) {
        if (isset($row[$k]) && is_numeric($row[$k])) {
            $video = (float)$row[$k];
            break;
        }
    }

    if (!isset($row['ctr']) || !is_numeric($row['ctr'])) {
        $row['ctr'] = ($impressions > 0) ? (($clicks / $impressions) * 100) : null;
    }
    if (!isset($row['cpc']) || !is_numeric($row['cpc'])) {
        $row['cpc'] = ($clicks > 0) ? ($spend / $clicks) : null;
    }
    if (!isset($row['cpm']) || !is_numeric($row['cpm'])) {
        $row['cpm'] = ($impressions > 0) ? (($spend / $impressions) * 1000) : null;
    }

    $row['leads']         = $leads;
    $row['purchases']     = $purchases;
    $row['video_actions'] = $video;
    $row['cpl']           = ($leads > 0) ? ($spend / $leads) : null;
    $row['cpa']           = ($purchases > 0) ? ($spend / $purchases) : null;
    $row['cpv']           = ($video > 0) ? ($spend / $video) : null;
}

function apply_multiplier_and_kpis(array &$row, float $mult): void {
    foreach ([
        'spend', 'cpc', 'cpm', 'cpl', 'cpa', 'cpv',
        'campaign_budget', 'campaign_daily_budget', 'campaign_lifetime_budget',
        'adgroup_budget', 'adgroup_daily_budget'
    ] as $field) {
        multiply_if_numeric($row, $field, $mult);
    }

    compute_tiktok_kpis($row);
}

/** ------------------- LOGGER ------------------- **/
class Logger {
    private string $logDir;
    private string $runId;

    public function __construct(string $logDir, string $runId) {
        $this->logDir = rtrim($logDir, '/');
        $this->runId  = $runId;
        if (!is_dir($this->logDir)) mkdir($this->logDir, 0755, true);
    }

    public function info(string $message, array $context = []): void {
        $this->write('INFO', $message, $context);
    }

    public function error(string $message, array $context = []): void {
        $this->write('ERROR', $message, $context);
    }

    private function write(string $level, string $message, array $context): void {
        $file = $this->logDir . '/tiktok_insights_' . date('Ymd') . '.log';
        $line = [
            'ts'     => date('Y-m-d H:i:s'),
            'level'  => $level,
            'run_id' => $this->runId,
            'msg'    => $message,
            'ctx'    => $context
        ];
        file_put_contents($file, json_encode($line, JSON_UNESCAPED_UNICODE) . PHP_EOL, FILE_APPEND);
    }
}

/** ------------------- TIKTOK CLIENT ------------------- **/
class TikTokClient {
    private string $accessToken;
    private string $apiBase;
    private int $timeoutSec;

    public function __construct(string $accessToken, string $apiBase, int $timeoutSec = 60) {
        $this->accessToken = $accessToken;
        $this->apiBase     = rtrim($apiBase, '/');
        $this->timeoutSec  = $timeoutSec;
    }

    public function debugLog(string $message): void {
        error_log('[TikTokClient] ' . $message);
    }

    private function request(string $method, string $path, array $payload = [], array $query = []): array {
        $method = strtoupper($method);
        $url = $this->apiBase . $path;

        if (!empty($query)) {
            $url .= '?' . http_build_query($query);
        }

        $headers = [
            'Access-Token: ' . $this->accessToken,
            'Accept: application/json',
        ];

        $ch = curl_init($url);

        $options = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => $this->timeoutSec,
            CURLOPT_CONNECTTIMEOUT => 15,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_HTTPHEADER     => $headers,
        ];

        if ($method === 'POST') {
            $headers[] = 'Content-Type: application/json';
            $options[CURLOPT_HTTPHEADER] = $headers;
            $options[CURLOPT_POST] = true;
            $options[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        } elseif ($method !== 'GET') {
            $options[CURLOPT_CUSTOMREQUEST] = $method;
            if (!empty($payload)) {
                $headers[] = 'Content-Type: application/json';
                $options[CURLOPT_HTTPHEADER] = $headers;
                $options[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            }
        }

        curl_setopt_array($ch, $options);

        $this->debugLog('URL: ' . $url);
        $this->debugLog('METHOD: ' . $method);

        $body  = curl_exec($ch);
        $errno = curl_errno($ch);
        $err   = curl_error($ch);
        $http  = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($errno) {
            throw new RuntimeException("cURL error ({$errno}): {$err}");
        }

        if ($body === false || $body === '') {
            throw new RuntimeException("Empty response (HTTP {$http})");
        }

        $json = json_decode($body, true);
        if (!is_array($json)) {
            throw new RuntimeException("Invalid JSON response (HTTP {$http}): " . substr($body, 0, 500));
        }

        $code = (int)($json['code'] ?? -1);
        $message = $json['message'] ?? 'Unknown TikTok API error';
        $reqId = $json['request_id'] ?? ($json['request_id_str'] ?? '');

        if ($http >= 400 || $code !== 0) {
            throw new RuntimeException("TikTok API error: {$message} (http={$http}, code={$code}, request_id={$reqId})");
        }

        return $json;
    }

    public function getAdvertiserInfo(string $advertiserId): array {
        $resp = $this->request('GET', '/advertiser/info/', [], [
            'advertiser_ids' => json_encode([(string)$advertiserId], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
        ]);

        return $resp['data']['list'][0] ?? [];
    }

    private function fetchPagedList(string $path, array $baseParams, string $method = 'GET', string $listKey = 'list'): array {
        $all = [];
        $page = 1;
        $pageSize = (int)($baseParams['page_size'] ?? 100);

        do {
            $params = $baseParams;
            $params['page'] = $page;
            $params['page_size'] = $pageSize;

            if (strtoupper($method) === 'GET') {
                $resp = $this->request('GET', $path, [], $params);
            } else {
                $resp = $this->request('POST', $path, $params, []);
            }

            $data = $resp['data'] ?? [];
            $rows = $data[$listKey] ?? [];
            if (!is_array($rows)) $rows = [];

            $all = array_merge($all, $rows);

            $pageInfo = parse_page_info($data['page_info'] ?? [
                'page'         => $page,
                'page_size'    => $pageSize,
                'total_number' => count($rows),
            ]);

            $page++;
        } while ($page <= $pageInfo['total_pages']);

        return $all;
    }

    public function getCampaigns(string $advertiserId): array {
        return $this->fetchPagedList('/campaign/get/', [
            'advertiser_id' => (string)$advertiserId,
            'page_size'     => 100,
        ], 'GET');
    }

    public function getAdGroups(string $advertiserId): array {
        return $this->fetchPagedList('/adgroup/get/', [
            'advertiser_id' => (string)$advertiserId,
            'page_size'     => 100,
        ], 'GET');
    }

    public function getAds(string $advertiserId): array {
        return $this->fetchPagedList('/ad/get/', [
            'advertiser_id' => (string)$advertiserId,
            'page_size'     => 100,
        ], 'GET');
    }

 public function getReportIntegrated(string $advertiserId, string $since, string $until, int $page = 1, int $pageSize = 20): array {
    return $this->request('GET', '/report/integrated/get/', [], [
        'advertiser_id' => (string)$advertiserId,
        'service_type'  => 'AUCTION',
        'report_type'   => 'BASIC',
        'data_level'    => 'AUCTION_AD',
        'dimensions'    => '["stat_time_day","ad_id"]',
        'metrics'       => '["spend","impressions","clicks","conversion","cost_per_conversion"]',
        'start_date'    => $since,
        'end_date'      => $until,
        'page'          => $page,
        'page_size'     => $pageSize
    ]);
}

    public function getAllReportIntegrated(string $advertiserId, string $since, string $until): array {
        $all = [];
        $page = 1;
        $pageSize = 100;

        do {
            $resp = $this->getReportIntegrated($advertiserId, $since, $until, $page, $pageSize);
            $data = $resp['data'] ?? [];
            $rows = $data['list'] ?? [];
            if (!is_array($rows)) $rows = [];

            $all = array_merge($all, $rows);

            $pageInfo = parse_page_info($data['page_info'] ?? [
                'page'         => $page,
                'page_size'    => $pageSize,
                'total_number' => count($rows),
            ]);

            $page++;
        } while ($page <= $pageInfo['total_pages']);

        return $all;
    }
}

/** ------------------- REPO ------------------- **/
class Repo {
    private PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }

    public function ensureTables(): void {
        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS hondav2_tiktok_campaign_details (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                run_id VARCHAR(64) NOT NULL,
                advertiser_id VARCHAR(32) NOT NULL,
                campaign_id VARCHAR(64) NOT NULL,
                campaign_name VARCHAR(255) NULL,
                operation_status VARCHAR(64) NULL,
                secondary_status VARCHAR(64) NULL,
                objective_type VARCHAR(128) NULL,
                budget DECIMAL(18,6) NULL,
                budget_mode VARCHAR(64) NULL,
                create_time DATETIME NULL,
                modify_time DATETIME NULL,
                raw_json JSON NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uq_campaign (advertiser_id, campaign_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        ");

        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS hondav2_tiktok_adgroup_details (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                run_id VARCHAR(64) NOT NULL,
                advertiser_id VARCHAR(32) NOT NULL,
                campaign_id VARCHAR(64) NULL,
                campaign_name VARCHAR(255) NULL,
                adgroup_id VARCHAR(64) NOT NULL,
                adgroup_name VARCHAR(255) NULL,
                operation_status VARCHAR(64) NULL,
                secondary_status VARCHAR(64) NULL,
                optimization_goal VARCHAR(128) NULL,
                budget DECIMAL(18,6) NULL,
                budget_mode VARCHAR(64) NULL,
                schedule_type VARCHAR(64) NULL,
                create_time DATETIME NULL,
                modify_time DATETIME NULL,
                raw_json JSON NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uq_adgroup (advertiser_id, adgroup_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        ");

        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS hondav2_tiktok_ad_details (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                run_id VARCHAR(64) NOT NULL,
                advertiser_id VARCHAR(32) NOT NULL,
                campaign_id VARCHAR(64) NULL,
                campaign_name VARCHAR(255) NULL,
                adgroup_id VARCHAR(64) NULL,
                adgroup_name VARCHAR(255) NULL,
                ad_id VARCHAR(64) NOT NULL,
                ad_name VARCHAR(255) NULL,
                operation_status VARCHAR(64) NULL,
                secondary_status VARCHAR(64) NULL,
                creative_material_mode VARCHAR(64) NULL,
                identity_type VARCHAR(64) NULL,
                create_time DATETIME NULL,
                modify_time DATETIME NULL,
                raw_json JSON NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uq_ad (advertiser_id, ad_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        ");

        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS hondav2_tiktok_creative_details (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                run_id VARCHAR(64) NOT NULL,
                advertiser_id VARCHAR(32) NOT NULL,
                campaign_id VARCHAR(64) NULL,
                campaign_name VARCHAR(255) NULL,
                adgroup_id VARCHAR(64) NULL,
                ad_id VARCHAR(64) NOT NULL,
                ad_name VARCHAR(255) NULL,
                creative_id VARCHAR(64) NULL,
                creative_name VARCHAR(255) NULL,
                image_url TEXT NULL,
                video_id VARCHAR(64) NULL,
                landing_page_url TEXT NULL,
                call_to_action VARCHAR(128) NULL,
                raw_json JSON NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uq_creative (advertiser_id, ad_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        ");

        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS hondav2_tiktok_ad_insights (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                run_id VARCHAR(64) NOT NULL,
                advertiser_id VARCHAR(32) NOT NULL,
                stat_time_day DATE NOT NULL,
                campaign_id VARCHAR(64) NULL,
                campaign_name VARCHAR(255) NULL,
                adgroup_id VARCHAR(64) NULL,
                adgroup_name VARCHAR(255) NULL,
                ad_id VARCHAR(64) NOT NULL,
                ad_name VARCHAR(255) NULL,
                campaign_status VARCHAR(64) NULL,
                adgroup_status VARCHAR(64) NULL,
                ad_status VARCHAR(64) NULL,
                campaign_budget DECIMAL(18,6) NULL,
                adgroup_budget DECIMAL(18,6) NULL,
                impressions BIGINT NULL,
                clicks BIGINT NULL,
                spend DECIMAL(18,6) NULL,
                ctr DECIMAL(18,6) NULL,
                cpc DECIMAL(18,6) NULL,
                cpm DECIMAL(18,6) NULL,
                leads DECIMAL(18,6) NULL,
                purchases DECIMAL(18,6) NULL,
                video_actions DECIMAL(18,6) NULL,
                cpl DECIMAL(18,6) NULL,
                cpa DECIMAL(18,6) NULL,
                cpv DECIMAL(18,6) NULL,
                raw_json JSON NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uq_row (advertiser_id, stat_time_day, ad_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        ");
    }

    private function toSqlDateTime(?string $iso): ?string {
        if (!$iso) return null;
        $ts = strtotime($iso);
        if ($ts === false) return null;
        return date('Y-m-d H:i:s', $ts);
    }

    public function upsertCampaignDetails(string $runId, string $advertiserId, array $rows): int {
        $sql = "
            INSERT INTO hondav2_tiktok_campaign_details
            (run_id, advertiser_id, campaign_id, campaign_name, operation_status, secondary_status, objective_type, budget, budget_mode, create_time, modify_time, raw_json)
            VALUES
            (:run_id, :advertiser_id, :campaign_id, :campaign_name, :operation_status, :secondary_status, :objective_type, :budget, :budget_mode, :create_time, :modify_time, :raw_json)
            ON DUPLICATE KEY UPDATE
                run_id=VALUES(run_id),
                campaign_name=VALUES(campaign_name),
                operation_status=VALUES(operation_status),
                secondary_status=VALUES(secondary_status),
                objective_type=VALUES(objective_type),
                budget=VALUES(budget),
                budget_mode=VALUES(budget_mode),
                create_time=VALUES(create_time),
                modify_time=VALUES(modify_time),
                raw_json=VALUES(raw_json),
                updated_at=CURRENT_TIMESTAMP
        ";

        $stmt = $this->pdo->prepare($sql);
        $count = 0;

        $this->pdo->beginTransaction();
        try {
            foreach ($rows as $r) {
                if (empty($r['campaign_id'])) continue;

                $stmt->execute([
                    ':run_id'           => $runId,
                    ':advertiser_id'    => $advertiserId,
                    ':campaign_id'      => (string)$r['campaign_id'],
                    ':campaign_name'    => $r['campaign_name'] ?? null,
                    ':operation_status' => $r['operation_status'] ?? null,
                    ':secondary_status' => $r['secondary_status'] ?? null,
                    ':objective_type'   => $r['objective_type'] ?? null,
                    ':budget'           => to_float($r['budget'] ?? null),
                    ':budget_mode'      => $r['budget_mode'] ?? null,
                    ':create_time'      => $this->toSqlDateTime($r['create_time'] ?? null),
                    ':modify_time'      => $this->toSqlDateTime($r['modify_time'] ?? null),
                    ':raw_json'         => safe_json($r),
                ]);
                $count++;
            }
            $this->pdo->commit();
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }

        return $count;
    }

    public function upsertAdgroupDetails(string $runId, string $advertiserId, array $rows): int {
        $sql = "
            INSERT INTO hondav2_tiktok_adgroup_details
            (run_id, advertiser_id, campaign_id, campaign_name, adgroup_id, adgroup_name, operation_status, secondary_status, optimization_goal, budget, budget_mode, schedule_type, create_time, modify_time, raw_json)
            VALUES
            (:run_id, :advertiser_id, :campaign_id, :campaign_name, :adgroup_id, :adgroup_name, :operation_status, :secondary_status, :optimization_goal, :budget, :budget_mode, :schedule_type, :create_time, :modify_time, :raw_json)
            ON DUPLICATE KEY UPDATE
                run_id=VALUES(run_id),
                campaign_id=VALUES(campaign_id),
                campaign_name=VALUES(campaign_name),
                adgroup_name=VALUES(adgroup_name),
                operation_status=VALUES(operation_status),
                secondary_status=VALUES(secondary_status),
                optimization_goal=VALUES(optimization_goal),
                budget=VALUES(budget),
                budget_mode=VALUES(budget_mode),
                schedule_type=VALUES(schedule_type),
                create_time=VALUES(create_time),
                modify_time=VALUES(modify_time),
                raw_json=VALUES(raw_json),
                updated_at=CURRENT_TIMESTAMP
        ";

        $stmt = $this->pdo->prepare($sql);
        $count = 0;

        $this->pdo->beginTransaction();
        try {
            foreach ($rows as $r) {
                if (empty($r['adgroup_id'])) continue;

                $stmt->execute([
                    ':run_id'            => $runId,
                    ':advertiser_id'     => $advertiserId,
                    ':campaign_id'       => $r['campaign_id'] ?? null,
                    ':campaign_name'     => $r['campaign_name'] ?? null,
                    ':adgroup_id'        => (string)$r['adgroup_id'],
                    ':adgroup_name'      => $r['adgroup_name'] ?? null,
                    ':operation_status'  => $r['operation_status'] ?? null,
                    ':secondary_status'  => $r['secondary_status'] ?? null,
                    ':optimization_goal' => $r['optimization_goal'] ?? null,
                    ':budget'            => to_float($r['budget'] ?? null),
                    ':budget_mode'       => $r['budget_mode'] ?? null,
                    ':schedule_type'     => $r['schedule_type'] ?? null,
                    ':create_time'       => $this->toSqlDateTime($r['create_time'] ?? null),
                    ':modify_time'       => $this->toSqlDateTime($r['modify_time'] ?? null),
                    ':raw_json'          => safe_json($r),
                ]);
                $count++;
            }
            $this->pdo->commit();
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }

        return $count;
    }

    public function upsertAdDetails(string $runId, string $advertiserId, array $rows): int {
        $sql = "
            INSERT INTO hondav2_tiktok_ad_details
            (run_id, advertiser_id, campaign_id, campaign_name, adgroup_id, adgroup_name, ad_id, ad_name, operation_status, secondary_status, creative_material_mode, identity_type, create_time, modify_time, raw_json)
            VALUES
            (:run_id, :advertiser_id, :campaign_id, :campaign_name, :adgroup_id, :adgroup_name, :ad_id, :ad_name, :operation_status, :secondary_status, :creative_material_mode, :identity_type, :create_time, :modify_time, :raw_json)
            ON DUPLICATE KEY UPDATE
                run_id=VALUES(run_id),
                campaign_id=VALUES(campaign_id),
                campaign_name=VALUES(campaign_name),
                adgroup_id=VALUES(adgroup_id),
                adgroup_name=VALUES(adgroup_name),
                ad_name=VALUES(ad_name),
                operation_status=VALUES(operation_status),
                secondary_status=VALUES(secondary_status),
                creative_material_mode=VALUES(creative_material_mode),
                identity_type=VALUES(identity_type),
                create_time=VALUES(create_time),
                modify_time=VALUES(modify_time),
                raw_json=VALUES(raw_json),
                updated_at=CURRENT_TIMESTAMP
        ";

        $stmt = $this->pdo->prepare($sql);
        $count = 0;

        $this->pdo->beginTransaction();
        try {
            foreach ($rows as $r) {
                if (empty($r['ad_id'])) continue;

                $stmt->execute([
                    ':run_id'                 => $runId,
                    ':advertiser_id'          => $advertiserId,
                    ':campaign_id'            => $r['campaign_id'] ?? null,
                    ':campaign_name'          => $r['campaign_name'] ?? null,
                    ':adgroup_id'             => $r['adgroup_id'] ?? null,
                    ':adgroup_name'           => $r['adgroup_name'] ?? null,
                    ':ad_id'                  => (string)$r['ad_id'],
                    ':ad_name'                => $r['ad_name'] ?? null,
                    ':operation_status'       => $r['operation_status'] ?? null,
                    ':secondary_status'       => $r['secondary_status'] ?? null,
                    ':creative_material_mode' => $r['creative_material_mode'] ?? null,
                    ':identity_type'          => $r['identity_type'] ?? null,
                    ':create_time'            => $this->toSqlDateTime($r['create_time'] ?? null),
                    ':modify_time'            => $this->toSqlDateTime($r['modify_time'] ?? null),
                    ':raw_json'               => safe_json($r),
                ]);
                $count++;
            }
            $this->pdo->commit();
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }

        return $count;
    }

    public function upsertCreativeDetails(string $runId, string $advertiserId, array $rows): int {
        $sql = "
            INSERT INTO hondav2_tiktok_creative_details
            (run_id, advertiser_id, campaign_id, campaign_name, adgroup_id, ad_id, ad_name, creative_id, creative_name, image_url, video_id, landing_page_url, call_to_action, raw_json)
            VALUES
            (:run_id, :advertiser_id, :campaign_id, :campaign_name, :adgroup_id, :ad_id, :ad_name, :creative_id, :creative_name, :image_url, :video_id, :landing_page_url, :call_to_action, :raw_json)
            ON DUPLICATE KEY UPDATE
                run_id=VALUES(run_id),
                campaign_id=VALUES(campaign_id),
                campaign_name=VALUES(campaign_name),
                adgroup_id=VALUES(adgroup_id),
                ad_name=VALUES(ad_name),
                creative_id=VALUES(creative_id),
                creative_name=VALUES(creative_name),
                image_url=VALUES(image_url),
                video_id=VALUES(video_id),
                landing_page_url=VALUES(landing_page_url),
                call_to_action=VALUES(call_to_action),
                raw_json=VALUES(raw_json),
                updated_at=CURRENT_TIMESTAMP
        ";

        $stmt = $this->pdo->prepare($sql);
        $count = 0;

        $this->pdo->beginTransaction();
        try {
            foreach ($rows as $r) {
                if (empty($r['ad_id'])) continue;

                $stmt->execute([
                    ':run_id'           => $runId,
                    ':advertiser_id'    => $advertiserId,
                    ':campaign_id'      => $r['campaign_id'] ?? null,
                    ':campaign_name'    => $r['campaign_name'] ?? null,
                    ':adgroup_id'       => $r['adgroup_id'] ?? null,
                    ':ad_id'            => (string)$r['ad_id'],
                    ':ad_name'          => $r['ad_name'] ?? null,
                    ':creative_id'      => $r['creative_id'] ?? null,
                    ':creative_name'    => $r['creative_name'] ?? null,
                    ':image_url'        => $r['image_url'] ?? null,
                    ':video_id'         => $r['video_id'] ?? null,
                    ':landing_page_url' => $r['landing_page_url'] ?? null,
                    ':call_to_action'   => $r['call_to_action'] ?? null,
                    ':raw_json'         => safe_json($r),
                ]);
                $count++;
            }
            $this->pdo->commit();
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }

        return $count;
    }

    public function upsertAdInsightsRows(string $runId, string $advertiserId, array $rows): int {
        $sql = "
            INSERT INTO hondav2_tiktok_ad_insights
            (run_id, advertiser_id, stat_time_day, campaign_id, campaign_name, adgroup_id, adgroup_name, ad_id, ad_name, campaign_status, adgroup_status, ad_status, campaign_budget, adgroup_budget, impressions, clicks, spend, ctr, cpc, cpm, leads, purchases, video_actions, cpl, cpa, cpv, raw_json)
            VALUES
            (:run_id, :advertiser_id, :stat_time_day, :campaign_id, :campaign_name, :adgroup_id, :adgroup_name, :ad_id, :ad_name, :campaign_status, :adgroup_status, :ad_status, :campaign_budget, :adgroup_budget, :impressions, :clicks, :spend, :ctr, :cpc, :cpm, :leads, :purchases, :video_actions, :cpl, :cpa, :cpv, :raw_json)
            ON DUPLICATE KEY UPDATE
                run_id=VALUES(run_id),
                campaign_id=VALUES(campaign_id),
                campaign_name=VALUES(campaign_name),
                adgroup_id=VALUES(adgroup_id),
                adgroup_name=VALUES(adgroup_name),
                ad_name=VALUES(ad_name),
                campaign_status=VALUES(campaign_status),
                adgroup_status=VALUES(adgroup_status),
                ad_status=VALUES(ad_status),
                campaign_budget=VALUES(campaign_budget),
                adgroup_budget=VALUES(adgroup_budget),
                impressions=VALUES(impressions),
                clicks=VALUES(clicks),
                spend=VALUES(spend),
                ctr=VALUES(ctr),
                cpc=VALUES(cpc),
                cpm=VALUES(cpm),
                leads=VALUES(leads),
                purchases=VALUES(purchases),
                video_actions=VALUES(video_actions),
                cpl=VALUES(cpl),
                cpa=VALUES(cpa),
                cpv=VALUES(cpv),
                raw_json=VALUES(raw_json),
                updated_at=CURRENT_TIMESTAMP
        ";

        $stmt = $this->pdo->prepare($sql);
        $count = 0;

        $this->pdo->beginTransaction();
        try {
            foreach ($rows as $r) {
                if (empty($r['stat_time_day']) || empty($r['ad_id'])) continue;

                $stmt->execute([
                    ':run_id'          => $runId,
                    ':advertiser_id'   => $advertiserId,
                    ':stat_time_day'   => $r['stat_time_day'],
                    ':campaign_id'     => $r['campaign_id'] ?? null,
                    ':campaign_name'   => $r['campaign_name'] ?? null,
                    ':adgroup_id'      => $r['adgroup_id'] ?? null,
                    ':adgroup_name'    => $r['adgroup_name'] ?? null,
                    ':ad_id'           => (string)$r['ad_id'],
                    ':ad_name'         => $r['ad_name'] ?? null,
                    ':campaign_status' => $r['campaign_status'] ?? null,
                    ':adgroup_status'  => $r['adgroup_status'] ?? null,
                    ':ad_status'       => $r['ad_status'] ?? null,
                    ':campaign_budget' => to_float($r['campaign_budget'] ?? null),
                    ':adgroup_budget'  => to_float($r['adgroup_budget'] ?? null),
                    ':impressions'     => to_int($r['impressions'] ?? null),
                    ':clicks'          => to_int($r['clicks'] ?? null),
                    ':spend'           => to_float($r['spend'] ?? null),
                    ':ctr'             => to_float($r['ctr'] ?? null),
                    ':cpc'             => to_float($r['cpc'] ?? null),
                    ':cpm'             => to_float($r['cpm'] ?? null),
                    ':leads'           => to_float($r['leads'] ?? null),
                    ':purchases'       => to_float($r['purchases'] ?? null),
                    ':video_actions'   => to_float($r['video_actions'] ?? null),
                    ':cpl'             => to_float($r['cpl'] ?? null),
                    ':cpa'             => to_float($r['cpa'] ?? null),
                    ':cpv'             => to_float($r['cpv'] ?? null),
                    ':raw_json'        => safe_json($r),
                ]);
                $count++;
            }
            $this->pdo->commit();
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }

        return $count;
    }
}

/** ------------------- RUN ------------------- **/
$logger = new Logger(__DIR__ . '/logs', $RUN_ID);

try {
    if ($ACCESS_TOKEN === 'PASTE_TIKTOK_ACCESS_TOKEN' || $ADVERTISER_ID === 'YOUR_ADVERTISER_ID') {
        throw new RuntimeException('Set TIKTOK_ACCESS_TOKEN and TIKTOK_ADVERTISER_ID first.');
    }

    $dsn = "mysql:host={$DB_HOST};port={$DB_PORT};dbname={$DB_NAME};charset=utf8mb4";
    $pdo = new PDO($dsn, $DB_USER, $DB_PASS, [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);

    $repo = new Repo($pdo);
    $repo->ensureTables();

    $client = new TikTokClient($ACCESS_TOKEN, $API_BASE);
    $advertiserMeta = $client->getAdvertiserInfo($ADVERTISER_ID);
    $advertiserName = $advertiserMeta['name'] ?? null;

    $outDir = __DIR__ . '/output';
    if (!is_dir($outDir)) mkdir($outDir, 0755, true);

    /** 1) CAMPAIGNS **/
    $campaignsRaw = $client->getCampaigns($ADVERTISER_ID);
    $campaigns = [];
    $campaignMap = [];

    foreach ($campaignsRaw as $r) {
        $campaignId   = (string)($r['campaign_id'] ?? $r['id'] ?? '');
        $campaignName = $r['campaign_name'] ?? $r['name'] ?? null;

        if ($campaignId === '' || !is_changan_campaign($campaignName)) {
            continue;
        }

        $row = [
            'campaign_id'      => $campaignId,
            'campaign_name'    => $campaignName,
            'operation_status' => $r['operation_status'] ?? null,
            'secondary_status' => $r['secondary_status'] ?? null,
            'objective_type'   => $r['objective_type'] ?? null,
            'budget'           => $r['budget'] ?? null,
            'budget_mode'      => $r['budget_mode'] ?? null,
            'create_time'      => $r['create_time'] ?? null,
            'modify_time'      => $r['modify_time'] ?? null,
            'raw'              => $r,
        ];

        $campaigns[] = $row;
        $campaignMap[$campaignId] = $row;
    }

    if ($ENDPOINT_MODE === 'campaign') {
        $repo->upsertCampaignDetails($RUN_ID, $ADVERTISER_ID, $campaigns);

        $final = [];
        foreach ($campaigns as $r) {
            $final[] = [
                'id'              => $r['campaign_id'],
                'name'            => $r['campaign_name'],
                'status'          => $r['operation_status'],
                'secondary_status'=> $r['secondary_status'],
                'objective_type'  => $r['objective_type'],
                'budget'          => $r['budget'],
                'budget_mode'     => $r['budget_mode'],
                'create_time'     => $r['create_time'],
                'modify_time'     => $r['modify_time'],
                'advertiser_id'   => $ADVERTISER_ID,
                'advertiser_name' => $advertiserName
            ];
        }

        $payload = export_payload('campaigns', $UNTIL, $final);
        file_put_contents($outDir . "/tiktok_campaigns_{$RUN_ID}.json", json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        exit;
    }

    /** 2) ADGROUPS **/
    $adgroupsRaw = $client->getAdGroups($ADVERTISER_ID);
    $adgroups = [];
    $adgroupMap = [];

    foreach ($adgroupsRaw as $r) {
        $campaignId = (string)($r['campaign_id'] ?? '');
        if ($campaignId === '' || !isset($campaignMap[$campaignId])) {
            continue;
        }

        $adgroupId = (string)($r['adgroup_id'] ?? $r['id'] ?? '');
        if ($adgroupId === '') continue;

        $row = [
            'campaign_id'       => $campaignId,
            'campaign_name'     => $campaignMap[$campaignId]['campaign_name'] ?? null,
            'adgroup_id'        => $adgroupId,
            'adgroup_name'      => $r['adgroup_name'] ?? $r['name'] ?? null,
            'operation_status'  => $r['operation_status'] ?? null,
            'secondary_status'  => $r['secondary_status'] ?? null,
            'optimization_goal' => $r['optimization_goal'] ?? null,
            'budget'            => $r['budget'] ?? null,
            'budget_mode'       => $r['budget_mode'] ?? null,
            'schedule_type'     => $r['schedule_type'] ?? null,
            'create_time'       => $r['create_time'] ?? null,
            'modify_time'       => $r['modify_time'] ?? null,
            'raw'               => $r,
        ];

        $adgroups[] = $row;
        $adgroupMap[$adgroupId] = $row;
    }

    if ($ENDPOINT_MODE === 'adgroup') {
        $repo->upsertAdgroupDetails($RUN_ID, $ADVERTISER_ID, $adgroups);

        $final = [];
        foreach ($adgroups as $r) {
            $final[] = [
                'id'               => $r['adgroup_id'],
                'name'             => $r['adgroup_name'],
                'status'           => $r['operation_status'],
                'secondary_status' => $r['secondary_status'],
                'campaign_id'      => $r['campaign_id'],
                'campaign_name'    => $r['campaign_name'],
                'budget'           => $r['budget'],
                'budget_mode'      => $r['budget_mode'],
                'optimization_goal'=> $r['optimization_goal'],
                'create_time'      => $r['create_time'],
                'modify_time'      => $r['modify_time'],
                'advertiser_id'    => $ADVERTISER_ID,
                'advertiser_name'  => $advertiserName
            ];
        }

        $payload = export_payload('adgroups', $UNTIL, $final);
        file_put_contents($outDir . "/tiktok_adgroups_{$RUN_ID}.json", json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        exit;
    }

    /** 3) ADS **/
    $adsRaw = $client->getAds($ADVERTISER_ID);
    $ads = [];
    $adMap = [];

    foreach ($adsRaw as $r) {
        $campaignId = (string)($r['campaign_id'] ?? '');
        if ($campaignId === '' || !isset($campaignMap[$campaignId])) {
            continue;
        }

        $adId = (string)($r['ad_id'] ?? $r['id'] ?? '');
        if ($adId === '') continue;

        $adgroupId = (string)($r['adgroup_id'] ?? '');

        $row = [
            'campaign_id'            => $campaignId,
            'campaign_name'          => $campaignMap[$campaignId]['campaign_name'] ?? null,
            'adgroup_id'             => $adgroupId ?: null,
            'adgroup_name'           => $adgroupMap[$adgroupId]['adgroup_name'] ?? null,
            'ad_id'                  => $adId,
            'ad_name'                => $r['ad_name'] ?? $r['name'] ?? null,
            'operation_status'       => $r['operation_status'] ?? null,
            'secondary_status'       => $r['secondary_status'] ?? null,
            'creative_material_mode' => $r['creative_material_mode'] ?? null,
            'identity_type'          => $r['identity_type'] ?? null,
            'create_time'            => $r['create_time'] ?? null,
            'modify_time'            => $r['modify_time'] ?? null,
            'raw'                    => $r,
        ];

        $ads[] = $row;
        $adMap[$adId] = $row;
    }

    if ($ENDPOINT_MODE === 'ad') {
        $repo->upsertAdDetails($RUN_ID, $ADVERTISER_ID, $ads);

        $final = [];
        foreach ($ads as $r) {
            $final[] = [
                'id'                    => $r['ad_id'],
                'name'                  => $r['ad_name'],
                'status'                => $r['operation_status'],
                'secondary_status'      => $r['secondary_status'],
                'campaign_id'           => $r['campaign_id'],
                'campaign_name'         => $r['campaign_name'],
                'adgroup_id'            => $r['adgroup_id'],
                'adgroup_name'          => $r['adgroup_name'],
                'creative_material_mode'=> $r['creative_material_mode'],
                'identity_type'         => $r['identity_type'],
                'create_time'           => $r['create_time'],
                'modify_time'           => $r['modify_time'],
                'advertiser_id'         => $ADVERTISER_ID,
                'advertiser_name'       => $advertiserName
            ];
        }

        $payload = export_payload('ads', $UNTIL, $final);
        file_put_contents($outDir . "/tiktok_ads_{$RUN_ID}.json", json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        exit;
    }

    /** 4) CREATIVES **/
    if ($ENDPOINT_MODE === 'creative') {
        $creatives = [];
        foreach ($ads as $a) {
            $raw = $a['raw'] ?? [];

            $creative = [
                'campaign_id'       => $a['campaign_id'],
                'campaign_name'     => $a['campaign_name'],
                'adgroup_id'        => $a['adgroup_id'],
                'ad_id'             => $a['ad_id'],
                'ad_name'           => $a['ad_name'],
                'creative_id'       => first_non_empty([
                    isset($raw['creative_id']) ? (string)$raw['creative_id'] : null,
                    isset($raw['identity_id']) ? (string)$raw['identity_id'] : null
                ]),
                'creative_name'     => first_non_empty([
                    $raw['ad_name'] ?? null,
                    $raw['display_name'] ?? null,
                    $a['ad_name'] ?? null
                ]),
                'image_url'         => first_non_empty([
                    $raw['image_url'] ?? null,
                    $raw['image_urls'][0] ?? null,
                    $raw['image_info']['url'] ?? null
                ]),
                'video_id'          => first_non_empty([
                    isset($raw['video_id']) ? (string)$raw['video_id'] : null,
                    isset($raw['video_info']['id']) ? (string)$raw['video_info']['id'] : null
                ]),
                'landing_page_url'  => first_non_empty([
                    $raw['landing_page_url'] ?? null,
                    $raw['landing_page_urls'][0] ?? null,
                    $raw['tracking_url'] ?? null
                ]),
                'call_to_action'    => first_non_empty([
                    $raw['call_to_action'] ?? null,
                    $raw['cta'] ?? null
                ]),
                'raw'               => $raw
            ];

            $creatives[] = $creative;
        }

        $repo->upsertCreativeDetails($RUN_ID, $ADVERTISER_ID, $creatives);

        $final = [];
        foreach ($creatives as $r) {
            $final[] = [
                'id'               => $r['creative_id'] ?: $r['ad_id'],
                'name'             => $r['creative_name'],
                'ad_id'            => $r['ad_id'],
                'ad_name'          => $r['ad_name'],
                'campaign_id'      => $r['campaign_id'],
                'campaign_name'    => $r['campaign_name'],
                'image_url'        => $r['image_url'],
                'video_id'         => $r['video_id'],
                'landing_page_url' => $r['landing_page_url'],
                'call_to_action'   => $r['call_to_action'],
                'advertiser_id'    => $ADVERTISER_ID,
                'advertiser_name'  => $advertiserName
            ];
        }

        $payload = export_payload('creatives', $UNTIL, $final);
        file_put_contents($outDir . "/tiktok_creatives_{$RUN_ID}.json", json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        exit;
    }

    /** 5) INSIGHTS **/
    $insightsRaw = $client->getAllReportIntegrated($ADVERTISER_ID, $SINCE, $UNTIL);
    $insights = [];

    foreach ($insightsRaw as $r) {
       $adId = (string)($r['ad_id'] ?? ($r['dimensions']['ad_id'] ?? ''));
if ($adId === '' || !isset($adMap[$adId])) {
    continue;
}

$campaignId = (string)($adMap[$adId]['campaign_id'] ?? '');
$adgroupId  = (string)($adMap[$adId]['adgroup_id'] ?? '');

if ($campaignId === '' || !isset($campaignMap[$campaignId])) {
    continue;
}

        $metrics = $r['metrics'] ?? [];

        $row = [
            'stat_time_day'   => $r['stat_time_day'] ?? ($r['dimensions']['stat_time_day'] ?? null),
            'campaign_id'     => $campaignId,
            'campaign_name'   => $campaignMap[$campaignId]['campaign_name'] ?? null,
            'adgroup_id'      => $adgroupId ?: null,
            'adgroup_name'    => $adgroupMap[$adgroupId]['adgroup_name'] ?? null,
            'ad_id'           => $adId,
            'ad_name'         => $adMap[$adId]['ad_name'] ?? null,
            'campaign_status' => $campaignMap[$campaignId]['operation_status'] ?? null,
            'adgroup_status'  => $adgroupMap[$adgroupId]['operation_status'] ?? null,
            'ad_status'       => $adMap[$adId]['operation_status'] ?? null,
            'campaign_budget' => $campaignMap[$campaignId]['budget'] ?? null,
            'adgroup_budget'  => $adgroupMap[$adgroupId]['budget'] ?? null,
            'impressions'     => $metrics['impressions'] ?? ($r['impressions'] ?? null),
            'clicks'          => $metrics['clicks'] ?? ($r['clicks'] ?? null),
            'spend'           => $metrics['spend'] ?? ($r['spend'] ?? null),
            'ctr'             => $metrics['ctr'] ?? ($r['ctr'] ?? null),
            'cpc'             => $metrics['cpc'] ?? ($r['cpc'] ?? null),
            'cpm'             => $metrics['cpm'] ?? ($r['cpm'] ?? null),
'conversion'          => $metrics['conversion'] ?? ($r['conversion'] ?? null),
'cost_per_conversion' => $metrics['cost_per_conversion'] ?? ($r['cost_per_conversion'] ?? null),
            'video_play_actions'  => $metrics['video_play_actions'] ?? ($r['video_play_actions'] ?? null),
            'video_views_p100'    => $metrics['video_views_p100'] ?? ($r['video_views_p100'] ?? null),
            'raw'             => $r
        ];

        apply_multiplier_and_kpis($row, MULTIPLIER);
        $insights[] = $row;
    }

    $repo->upsertAdInsightsRows($RUN_ID, $ADVERTISER_ID, $insights);

    $final = [];
    foreach ($insights as $r) {
        $final[] = [
            'ad_id'         => $r['ad_id'],
            'ad_name'       => $r['ad_name'],
            'stat_time_day' => $r['stat_time_day'],
            'campaign_id'   => $r['campaign_id'],
            'campaign_name' => $r['campaign_name'],
            'adgroup_id'    => $r['adgroup_id'],
            'adgroup_name'  => $r['adgroup_name'],
            'impressions'   => $r['impressions'],
            'clicks'        => $r['clicks'],
            'spend'         => $r['spend'],
            'ctr'           => $r['ctr'],
            'cpc'           => $r['cpc'],
            'cpm'           => $r['cpm'],
            'leads'         => $r['leads'],
            'purchases'     => $r['purchases'],
            'video_actions' => $r['video_actions'],
            'cpl'           => $r['cpl'],
            'cpa'           => $r['cpa'],
            'cpv'           => $r['cpv']
        ];
    }

   $payload = export_dashboard_rows('insights', $UNTIL, $final, $ADVERTISER_ID);
file_put_contents($outDir . "/tiktok_insights_{$RUN_ID}.json", json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

} catch (Throwable $e) {
    try {
        $logger->error('FAILED', ['error' => $e->getMessage()]);
    } catch (Throwable $ignore) {}

    http_response_code(500);
    echo json_encode([
        'ok'     => false,
        'run_id' => $RUN_ID ?? null,
        'error'  => $e->getMessage()
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
