import { readFile, writeFile, cp } from 'node:fs/promises';
import path, { resolve } from 'node:path';
import { getTokenPath } from './home.js';
import { refreshToken } from './auth.js';
import { readUmilocal, saveToken, writeUmilocale } from '../auth.js';
import { writeDraftXlsx, validateDraft } from '../xlsx/write.js';
import { t } from '../i18n/translate.js';
import { detectCliLocale } from '../i18n/locale.js';
import { aeLocaleToCli, cliLocaleToAE } from '../i18n/ae-locale.js';
import { TEClient } from '../client.js';
import { fetchPlan } from '../te-plan/fetch.js';
import { injectAutotrackEvents } from '../plan/draft.js';
import { validateAndFix, fixDraft, extractErrors } from '../plan/fix.js';
import { detectConflicts, formatConflictReport } from '../plan/conflict.js';
import { readdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { getPackageRoot } from './package-root.js';
const DEFAULT_HOST = 'https://web-ta-demo.thinkingdata.cn';
const MAX_FIX_RETRIES = 3;
const TEMPLATE_DIRS = [
    resolve(getPackageRoot(), 'tracking-plan-template'),
    resolve(process.env.HOME ?? '', '.ae-tracking', 'templates'),
];
/**
 * 获取 token（支持自动刷新）
 * @param host AE host
 * @param autoRefresh token 过期时自动刷新（macOS 从 Chrome 获取）
 */
async function getToken(host, autoRefresh = false) {
    let cache = {};
    try {
        cache = JSON.parse(await readFile(getTokenPath(), 'utf8'));
    }
    catch { /* ignore */ }
    const tok = cache[host]?.token;
    if (!tok) {
        if (autoRefresh && process.platform === 'darwin') {
            console.log(t('plan.no_cached_token_auth'));
            return refreshToken(host);
        }
        throw new Error(t('auth.no_cached_token', { host: host }));
    }
    return tok;
}
export function registerPlan(cmd) {
    cmd.description('manage AE tracking plans');
    cmd.command('draft')
        .requiredOption('--in <path>', 'draft JSON input')
        .requiredOption('--out <path>', 'xlsx output')
        .option('--fix', 'auto fix validation errors', false)
        .action(async (opts) => {
        const draft = JSON.parse(await readFile(opts.in, 'utf8'));
        const locale = draft.meta.lang ?? detectCliLocale();
        // 注入 SDK 自动采集事件
        injectAutotrackEvents(draft, locale);
        // 校验和自动修复
        if (opts.fix) {
            const fixed = validateAndFix(draft);
            if (fixed.length > 0) {
                console.log(t('plan.draft_autofixed', { items: fixed.join(', ') }));
                await writeFile(opts.in, JSON.stringify(draft, null, 2), 'utf8');
            }
        }
        // 尝试校验，如果失败且开启了 fix，循环修复后重试
        try {
            validateDraft(draft);
        }
        catch (e) {
            if (!opts.fix) {
                throw e;
            }
            console.log(t('plan.draft_validation_failed_attempting_fix'));
            for (let retry = 0; retry < MAX_FIX_RETRIES; retry++) {
                const fixed = validateAndFix(draft);
                if (fixed.length === 0) {
                    // 无法自动修复，报错退出
                    console.error(t('plan.no_auto_fix_available'));
                    console.error(e instanceof Error ? e.message : String(e));
                    process.exit(1);
                }
                console.log(t('plan.draft_fixed', { items: fixed.join(', ') }));
                await writeFile(opts.in, JSON.stringify(draft, null, 2), 'utf8');
                try {
                    validateDraft(draft);
                    break; // 修复成功
                }
                catch (retryErr) {
                    if (retry === MAX_FIX_RETRIES - 1) {
                        // 达到最大重试次数，输出剩余错误并退出
                        console.error(t('plan.validate_still_errors_after_fix', {
                            error: retryErr instanceof Error ? retryErr.message : String(retryErr),
                        }));
                        console.error(t('plan.max_fix_retries'));
                        process.exit(1);
                    }
                    // 继续下一轮修复
                }
            }
        }
        await writeDraftXlsx(draft, opts.out, locale);
        const counts = `events=${draft.events.length}, event_props=${draft.event_properties.length}, common=${draft.common_event_properties.length}, user=${draft.user_properties.length}`;
        console.log(t('plan.wrote_xlsx', { path: opts.out, counts }));
    });
    cmd.command('validate')
        .requiredOption('--in <path>', 'draft JSON input')
        .option('--fix', 'auto fix validation errors', false)
        .action(async (opts) => {
        const draft = JSON.parse(await readFile(opts.in, 'utf8'));
        try {
            validateDraft(draft);
            console.log(t('plan.validate_passed'));
        }
        catch (e) {
            const errorMsg = e instanceof Error ? e.message : String(e);
            console.log(t('plan.validate_failed') + '\n' + errorMsg);
            if (opts.fix) {
                console.log('\n' + t('plan.attempting_auto_fix') + '...');
                const fixed = validateAndFix(draft);
                if (fixed.length > 0) {
                    console.log(t('plan.fixed_items', { items: fixed.join(', ') }));
                    await writeFile(opts.in, JSON.stringify(draft, null, 2), 'utf8');
                    console.log(t('plan.updated_file', { path: opts.in }));
                    // 重新校验
                    try {
                        validateDraft(draft);
                        console.log(t('plan.validate_passed_after_fix'));
                    }
                    catch (e2) {
                        console.log(t('plan.validate_still_errors_after_fix', { error: e2 instanceof Error ? e2.message : String(e2) }));
                    }
                }
                else {
                    console.log(t('plan.no_auto_fix_available'));
                }
            }
        }
    });
    cmd.command('upload')
        .requiredOption('--project <id>', 'AE projectId', (v) => parseInt(v, 10))
        .requiredOption('--xlsx <path>', 'xlsx file')
        .requiredOption('--draft <path>', 'draft JSON (for auto-fix)')
        .option('--host <url>', 'AE host', DEFAULT_HOST)
        .option('--lang <locale>', 'xlsx language: zh / en / ja / ko')
        .option('--replace', 'delete existing plan first', false)
        .option('--auto-fix', 'auto fix upload errors and retry', true)
        .option('--switch-lang', 'auto switch AE project language to match xlsx', false)
        .action(async (opts) => {
        // 使用自动刷新获取 token
        let token = await getToken(opts.host, true);
        let client = new TEClient({ host: opts.host, token });
        // 预读 draft.json 获取 xlsx 语言，检测 AE 语言是否一致（通过缓存的 umi_locale）
        const draftPre = JSON.parse(await readFile(opts.draft, 'utf8'));
        const xlsxLocale = opts.lang ?? draftPre.meta.lang ?? detectCliLocale();
        const umiRaw = readUmilocal(opts.host);
        const aeLocale = umiRaw ? aeLocaleToCli(umiRaw) : null;
        if (aeLocale && aeLocale !== xlsxLocale) {
            console.log('\n' + t('plan.ae_lang_mismatch', { aeLang: aeLocale, xlsxLang: xlsxLocale }));
            if (opts.switchLang) {
                const aeLangCode = cliLocaleToAE(xlsxLocale);
                await client.setAELang(aeLangCode);
                saveToken(token, opts.host, xlsxLocale);
                const lsValue = aeLangCode.replace('_', '-');
                if (writeUmilocale(opts.host, lsValue)) {
                    console.log(t('plan.ae_lang_switched', { aeLang: aeLangCode }));
                }
                else {
                    console.log(t('plan.ae_lang_switched', { aeLang: aeLangCode }));
                    console.log(t('plan.ae_lang_manual_refresh'));
                }
                await new Promise(r => setTimeout(r, 2000));
            }
            else {
                console.log(t('plan.ae_lang_switch_hint'));
                process.exit(13);
            }
        }
        // 追加模式：先检测冲突
        if (!opts.replace) {
            console.log(t('plan.checking_conflicts'));
            try {
                const existingDraft = await fetchPlan({ projectId: opts.project, host: opts.host, token });
                // 有既有方案，进行冲突检测
                const conflictResult = detectConflicts(draftPre, existingDraft);
                if (conflictResult.hasErrors) {
                    // 严重冲突：打印报告并退出
                    const existingSummary = {
                        events: existingDraft.events.length,
                        eventProps: existingDraft.event_properties.length,
                        commonProps: existingDraft.common_event_properties.length,
                        userProps: existingDraft.user_properties.length,
                    };
                    console.log('\n' + formatConflictReport(conflictResult, existingSummary));
                    console.log('\n' + t('plan.cannot_append_mode'));
                    return;
                }
                if (conflictResult.hasWarnings) {
                    // 仅提醒：打印并继续
                    const existingSummary = {
                        events: existingDraft.events.length,
                        eventProps: existingDraft.event_properties.length,
                        commonProps: existingDraft.common_event_properties.length,
                        userProps: existingDraft.user_properties.length,
                    };
                    console.log('\n' + formatConflictReport(conflictResult, existingSummary));
                    console.log('\n' + t('plan.append_continue_note') + '\n');
                }
            }
            catch (e) {
                // 区分 token 过期和项目无方案
                const errorMsg = e instanceof Error ? e.message : String(e);
                if (errorMsg.includes('return_code -1001') || errorMsg.includes('token expired')) {
                    // Token 过期：自动刷新后重试一次
                    console.log(t('plan.token_expired_refreshing'));
                    token = await refreshToken(opts.host);
                    client = new TEClient({ host: opts.host, token });
                    // 不需要再检测冲突，直接进入上传流程（后续上传失败会再次检测）
                }
                else {
                    // 其他错误（如 404、无数据）：项目无既有方案，直接上传
                    console.log(t('plan.no_existing_plan'));
                }
            }
        }
        if (opts.replace) {
            const del = await client.deleteProgram(opts.project);
            console.log(t('plan.deleted_existing_plan', { message: del.return_message }));
            await new Promise((resolve) => setTimeout(resolve, 5000));
        }
        // 上传并检查错误
        let retryCount = 0;
        let draft = JSON.parse(await readFile(opts.draft, 'utf8'));
        const locale = opts.lang ?? draft.meta.lang ?? 'zh';
        let tokenRefreshed = false;
        let lastRes = null;
        while (retryCount < MAX_FIX_RETRIES) {
            const file = await readFile(opts.xlsx);
            const res = await client.uploadProgramExcel({
                projectId: opts.project,
                file,
                filename: path.basename(opts.xlsx),
            });
            lastRes = res;
            console.log(JSON.stringify(res, null, 2));
            // Token 过期：自动刷新后重试
            if (res.return_code === -1001 && !tokenRefreshed) {
                console.log('[auth] token expired during upload, auto-refreshing...');
                token = await refreshToken(opts.host);
                client = new TEClient({ host: opts.host, token });
                tokenRefreshed = true;
                continue; // 重试上传
            }
            // 检查是否有错误
            const errors = extractErrors(res);
            if (errors.length === 0 || res.return_code !== 0) {
                // 无错误或上传失败（非校验错误）
                break;
            }
            if (!opts.autoFix) {
                console.log('\n' + t('plan.upload_has_validation_errors'));
                break;
            }
            // 自动修复
            console.log('\n' + t('plan.auto_fixing_retry', { current: retryCount + 1, max: MAX_FIX_RETRIES }) + '...');
            const fixed = fixDraft(draft, res);
            if (fixed.length === 0) {
                console.log(t('plan.no_auto_fix_upload'));
                break;
            }
            console.log(t('plan.fixed_items', { items: fixed.join(', ') }));
            // 更新 draft.json 并重新生成 xlsx
            await writeFile(opts.draft, JSON.stringify(draft, null, 2), 'utf8');
            injectAutotrackEvents(draft, locale);
            await writeDraftXlsx(draft, opts.xlsx, locale);
            console.log(t('plan.regenerated_xlsx', { path: opts.xlsx }));
            retryCount++;
        }
        if (retryCount >= MAX_FIX_RETRIES) {
            console.log('\n' + t('plan.max_fix_retries'));
        }
        // 语言不匹配提示：AE 返回"无法识别表格内容"类错误时，提示检查语言设置
        const msg = lastRes?.return_message ?? '';
        const isLangMismatch = msg.includes('无法识别表格内容') ||
            msg.includes('The content of the form cannot be recognized') ||
            msg.includes('テーブルの内容を認識できません') ||
            msg.includes('트래킹 방안을 실별 할 수 없습니다');
        if (lastRes && lastRes.return_code !== 0 && isLangMismatch) {
            const xlsxLocale = opts.lang ?? draft.meta.lang ?? detectCliLocale();
            console.log('\n' + t('plan.language_mismatch_hint', { locale: xlsxLocale }));
        }
    });
    cmd.command('fetch')
        .requiredOption('--project <id>', 'AE projectId', (v) => parseInt(v, 10))
        .option('--host <url>', 'AE host', DEFAULT_HOST)
        .option('--out <path>', 'write to file instead of stdout')
        .action(async (opts) => {
        const token = await getToken(opts.host);
        const draft = await fetchPlan({ projectId: opts.project, host: opts.host, token });
        const s = JSON.stringify(draft, null, 2) + '\n';
        if (opts.out)
            await writeFile(opts.out, s, 'utf8');
        else
            process.stdout.write(s);
    });
    cmd.command('archive')
        .requiredOption('--draft <path>', 'draft.json path')
        .requiredOption('--xlsx <path>', 'draft.xlsx path')
        .requiredOption('--name <name>', 'plan name for filename')
        .option('--plans-dir <dir>', 'plans output directory', resolve(process.cwd(), 'plans'))
        .action(async (opts) => {
        const stamp = new Date().toISOString().slice(0, 10);
        const dest = resolve(opts.plansDir, `${stamp}-${opts.name}.xlsx`);
        // Copy with guard against overwrite
        if (existsSync(dest)) {
            console.error(t('plan.archive_exists', { path: dest }));
            console.error(t('plan.remove_or_rename'));
            return;
        }
        try {
            await cp(opts.xlsx, dest);
        }
        catch (e) {
            // Only EEXIST is possible here since we already checked above, but be defensive
            if (e.code === 'EEXIST') {
                console.error(t('plan.archive_exists', { path: dest }));
                console.error(t('plan.remove_or_rename'));
                return;
            }
            throw e;
        }
        // Mark as archived in draft.json
        const draft = JSON.parse(await readFile(opts.draft, 'utf8'));
        draft.meta.archived_at = stamp;
        draft.meta.archived_path = dest;
        await writeFile(opts.draft, JSON.stringify(draft, null, 2), 'utf8');
        console.log(t('plan.archived_to', { path: dest }));
        console.log(t('plan.draft_updated_archived', { stamp }));
    });
    cmd.command('delete')
        .requiredOption('--project <id>', 'AE projectId', (v) => parseInt(v, 10))
        .option('--host <url>', 'AE host', DEFAULT_HOST)
        .action(async (opts) => {
        const token = await getToken(opts.host);
        const client = new TEClient({ host: opts.host, token });
        const res = await client.deleteProgram(opts.project);
        console.log(JSON.stringify(res, null, 2));
    });
    cmd.command('list-templates')
        .description('list available tracking plan templates')
        .action(async () => {
        const found = [];
        for (const dir of TEMPLATE_DIRS) {
            if (!existsSync(dir))
                continue;
            let entries = [];
            try {
                entries = await readdir(dir);
            }
            catch {
                continue;
            }
            for (const f of entries) {
                if (!f.endsWith('.xlsx'))
                    continue;
                const base = f.replace(/\.xlsx$/i, '');
                const mdPath = resolve(dir, base + '.md');
                const hasMd = existsSync(mdPath);
                found.push({
                    path: hasMd ? mdPath : resolve(dir, f),
                    name: base,
                    hasMd,
                });
            }
        }
        if (found.length === 0) {
            console.log(t('plan.no_templates_found', { dirs: TEMPLATE_DIRS.join(', ') }));
            return;
        }
        console.log(t('plan.templates_found', { count: found.length }) + '\n');
        for (const tmpl of found) {
            const mdNote = tmpl.hasMd ? t('plan.template_md_label') : t('plan.template_xlsx_label');
            console.log(`  ${tmpl.name}${mdNote}`);
            console.log(`    ${tmpl.path}`);
        }
    });
}
