// --- Facebook Marketing API helpers (Graph API v19) ---

const GRAPH_VERSION = 'v19.0';
const BASE = `https://graph.facebook.com/${GRAPH_VERSION}`;

function normalizeError(payload) {
    if (!payload?.error) return new Error('Unknown API error');
    const e = payload.error;
    return new Error(e.error_user_msg || e.message || String(e.code || 'Graph error'));
}

export function normalizeAdAccountId(id) {
    if (id == null || id === '') return null;
    const s = String(id).trim();
    if (!s) return null;
    const digits = s.replace(/^act_/i, '');
    return `act_${digits}`;
}

async function graphGet(path, token, params = {}) {
    const p = path.startsWith('/') ? path : `/${path}`;
    const url = new URL(`${BASE}${p}`);
    url.searchParams.set('access_token', token);
    Object.entries(params).forEach(([k, v]) => {
        if (v != null && v !== '') url.searchParams.set(k, String(v));
    });
    const res = await fetch(url.toString());
    const json = await res.json();
    if (json.error) throw normalizeError(json);
    return json;
}

async function graphPost(path, token, bodyObj) {
    const p = path.startsWith('/') ? path : `/${path}`;
    const url = new URL(`${BASE}${p}`);
    url.searchParams.set('access_token', token);
    const body = new URLSearchParams();
    Object.entries(bodyObj).forEach(([k, v]) => {
        if (v != null) body.set(k, String(v));
    });
    const res = await fetch(url.toString(), {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: body.toString()
    });
    const json = await res.json();
    if (json.error) throw normalizeError(json);
    return json;
}

export async function fetchAdAccounts(token) {
    const json = await graphGet('/me/adaccounts', token, {
        fields: 'name,account_id,currency',
        limit: '50'
    });
    return json.data || [];
}

const CAMPAIGN_FIELDS = [
    'id',
    'name',
    'status',
    'effective_status',
    'insights.date_preset(today){spend,impressions,clicks,cpc,ctr,actions}'
].join(',');

/** Meta insight action_type values that count as purchase (today preset). */
const PURCHASE_ACTION_TYPES = new Set([
    'offsite_conversion.fb_pixel_purchase',
    'offsite_conversion.purchase',
    'purchase',
    'omni_purchase',
    'onsite_conversion.purchase'
]);

export function purchaseCountFromInsightActions(actions) {
    if (!Array.isArray(actions)) return 0;
    let n = 0;
    for (const a of actions) {
        const t = String(a?.action_type || '');
        if (PURCHASE_ACTION_TYPES.has(t)) {
            n += parseInt(String(a.value || '0'), 10) || 0;
        }
    }
    return n;
}

export async function fetchCampaignsWithInsights(token, accountId) {
    const act = normalizeAdAccountId(accountId);
    if (!act) throw new Error('Missing ad account id');
    const json = await graphGet(`/${act}/campaigns`, token, {
        fields: CAMPAIGN_FIELDS,
        limit: '100'
    });
    return json.data || [];
}

function firstInsightRow(campaign) {
    const data = campaign.insights?.data;
    if (!data || !data.length) return null;
    return data[0];
}

export function normalizeCampaignRows(campaigns) {
    return campaigns.map((c) => {
        const ins = firstInsightRow(c);
        const spend = ins ? parseFloat(ins.spend || '0') || 0 : 0;
        const impressions = ins ? parseInt(ins.impressions || '0', 10) || 0 : 0;
        const clicks = ins ? parseInt(ins.clicks || '0', 10) || 0 : 0;
        let cpc = ins && ins.cpc != null ? parseFloat(ins.cpc) : null;
        if ((cpc == null || Number.isNaN(cpc)) && clicks > 0) cpc = spend / clicks;
        const purchases = purchaseCountFromInsightActions(ins?.actions);
        return {
            id: c.id,
            name: c.name || '(unnamed)',
            status: c.status || '',
            effectiveStatus: c.effective_status || c.status || '',
            spend,
            impressions,
            clicks,
            cpc: cpc != null && !Number.isNaN(cpc) ? cpc : 0,
            ctr: ins && ins.ctr != null ? parseFloat(ins.ctr) : null,
            purchases
        };
    });
}

export function aggregateTotals(rows) {
    let spend = 0;
    let impressions = 0;
    let clicks = 0;
    let purchases = 0;
    rows.forEach((r) => {
        spend += r.spend;
        impressions += r.impressions;
        clicks += r.clicks;
        purchases += r.purchases || 0;
    });
    const avgCpc = clicks > 0 ? spend / clicks : 0;
    return { spend, impressions, clicks, avgCpc, purchases };
}

export async function resolveAdAccountId(token, configuredId) {
    const normalized = normalizeAdAccountId(configuredId);
    if (normalized) return { accountId: normalized, accounts: null };
    const accounts = await fetchAdAccounts(token);
    if (!accounts.length) throw new Error('No ad accounts found for this token.');
    const first = accounts[0];
    return { accountId: normalizeAdAccountId(first.account_id), accounts };
}

export async function fetchAdAccountCurrency(token, accountId) {
    const act = normalizeAdAccountId(accountId);
    if (!act) return 'USD';
    try {
        const json = await graphGet(`/${act}`, token, { fields: 'currency' });
        return json.currency || 'USD';
    } catch {
        return 'USD';
    }
}

export async function setCampaignStatus(token, campaignId, status) {
    if (!campaignId) throw new Error('Missing campaign id');
    if (status !== 'PAUSED' && status !== 'ACTIVE') throw new Error('Invalid status');
    return graphPost(`/${campaignId}`, token, { status });
}

export function formatMoney(amount, currencyCode = 'USD') {
    try {
        return new Intl.NumberFormat('en-US', {
            style: 'currency',
            currency: currencyCode,
            minimumFractionDigits: 2,
            maximumFractionDigits: 2
        }).format(amount);
    } catch {
        return `${currencyCode} ${amount.toFixed(2)}`;
    }
}
