/**
 * Telegram mirror for the secretary agent (영숙).
 *
 * After every company turn finishes, this helper takes the CEO synthesis +
 * task list and pushes it to the user's Telegram chat — same behaviour as
 * Connect_origin's `_handleCorporatePrompt` (lines ~20620): "Secretary
 * automatically reports the round to the user." Connect AI had a Telegram
 * integration but it was wired only for *inbound* messages — the company
 * dispatcher we shipped earlier never called it.
 *
 * Design notes:
 *   - **Outbound-only**: no polling, no inbound side effects. Sends a single
 *     `sendMessage` per turn.
 *   - **Honours user opt-in**: requires `g1nation.telegram.enabled` to be
 *     true AND a stored bot token AND at least one allowed chat id. If any
 *     piece is missing, `buildTelegramReporter` returns `null` and the
 *     dispatcher silently skips the mirror — no log spam, no fake error.
 *   - **Per-call isolation**: each reporter fetches the token freshly via
 *     `context.secrets.get`, so rotating the secret in the settings UI
 *     takes effect on the *next* turn without a restart.
 *   - **Markdown-aware**: re-uses `TelegramHttpClient`, which already
 *     truncates to Telegram's 4096-char limit and falls back to plain text
 *     when the Markdown parser refuses a string.
 */
import * as vscode from 'vscode';
import { logError, logInfo } from '../../utils';
import { TelegramHttpClient } from '../../integrations/telegram/telegramClient';
import { appendTelegramMessage } from '../../integrations/telegram/conversationHistory';
import { resolveAgent } from './companyConfig';
import { AgentTurnOutput, CompanyState, CompanyTaskPlan } from './types';

/** Same key the rest of the extension uses. Defined locally so this module is dependency-free. */
const TELEGRAM_TOKEN_SECRET_KEY = 'g1nation.telegram.botToken';

/** Maximum characters of the CEO report to embed verbatim in the Telegram message. */
const REPORT_BUDGET = 2000;

/** Reporter contract — call once per turn, returns whether delivery succeeded. */
export type TelegramReporter = (text: string) => Promise<boolean>;

/**
 * Build a Telegram reporter bound to the current user's bot config. Returns
 * `null` when the user hasn't opted in (no token, no chat id, or
 * `telegram.enabled` is false) — the dispatcher then treats it as "no
 * mirror, nothing to do".
 *
 * The returned function targets the *first* allowed chat id. Users with
 * multiple chats today still get one canonical destination; broader fan-out
 * would be a feature add, not a port of origin's behaviour.
 */
export async function buildTelegramReporter(
    context: vscode.ExtensionContext,
): Promise<TelegramReporter | null> {
    const cfg = vscode.workspace.getConfiguration('g1nation');
    if (!cfg.get<boolean>('telegram.enabled', false)) return null;

    const token = ((await context.secrets.get(TELEGRAM_TOKEN_SECRET_KEY)) || '').trim();
    if (!token) {
        logInfo('telegramReport: opt-in is on but no bot token stored; skipping mirror.');
        return null;
    }

    const allowed = cfg.get<number[]>('telegram.allowedChatIds', []) || [];
    const chatId = allowed.find((id) => typeof id === 'number' && Number.isFinite(id) && id !== 0);
    if (!chatId) {
        logInfo('telegramReport: telegram enabled but allowedChatIds is empty; skipping mirror.');
        return null;
    }

    // Build a thin client per turn — token capture happens once, so this
    // doesn't keep a long-lived reference to the secret.
    const client = new TelegramHttpClient({ getToken: () => token });

    return async (text: string): Promise<boolean> => {
        if (!text || !text.trim()) return false;
        // Append to the per-chat history *before* the send. The bot's
        // next inbound turn reads this history, so even if delivery
        // fails the user's follow-up question still has context for
        // "what did you just say to me?". Persisting on attempt also
        // means timing matches the user's perception ("the bot reported
        // X, then I replied").
        appendTelegramMessage({ chatId, role: 'assistant', text, kind: 'company-mirror' });
        try {
            await client.sendMessage({ chatId, text, parseMode: 'Markdown' });
            return true;
        } catch (e: any) {
            // Fallback to plain text — Telegram rejects malformed Markdown
            // (unbalanced asterisks, etc.). The origin does the same thing
            // in `sendTelegramLong`, so behaviour stays familiar.
            try {
                const plain = text.replace(/[*_`[\]]/g, '');
                await client.sendMessage({ chatId, text: plain, parseMode: null });
                return true;
            } catch (e2: any) {
                logError('telegramReport: mirror failed in both modes.', {
                    primary: e?.message ?? String(e),
                    fallback: e2?.message ?? String(e2),
                });
                return false;
            }
        }
    };
}

/**
 * Compose the per-turn Telegram message. Format mirrors Connect_origin's
 * Secretary report so users moving between the two products see consistent
 * notifications: header + brief + agent list + CEO synthesis + session id +
 * **concrete file paths** so follow-up questions like "어디에 만들었어?"
 * have a clean answer (the bot's previous answer "I don't have file system
 * access" was the user-visible bug we're fixing here).
 *
 * The function is pure (no I/O) and exported separately so it can be unit-
 * tested without touching VS Code APIs.
 */
export function formatCompanyTelegramReport(opts: {
    state: CompanyState;
    userPrompt: string;
    plan: CompanyTaskPlan;
    outputs: AgentTurnOutput[];
    report: string;
    sessionTimestamp: string;
    /** Absolute path of the session directory — surfaced in the report so
     *  the user can locate generated files without asking the bot again. */
    sessionDir: string;
}): string {
    const company = opts.state.companyName || '1인 기업';
    const header = `*📱 ${company} — 작업 라운드 보고*`;
    const cmdLine = `*명령:* ${opts.userPrompt.slice(0, 200)}`;
    const brief = opts.plan.brief ? `\n\n*브리프:* ${opts.plan.brief}` : '';
    const agentsLine = opts.plan.tasks.length > 0
        ? '\n\n*완료한 에이전트:*\n' + opts.plan.tasks.map((t) => {
            const def = resolveAgent(opts.state, t.agent);
            const ranOk = opts.outputs.find((o) => o.agentId === t.agent && !o.error);
            const mark = ranOk ? '✅' : '⚠️';
            return `• ${mark} ${def?.emoji ?? ''} ${def?.name ?? t.agent}`;
        }).join('\n')
        : '';
    // Concrete artefacts: extract the file paths the action-tag executor
    // actually wrote, so the user can copy-paste them into Finder or `cd` to
    // the right place. Empty when no agent emitted file/command tags.
    const artefactsLine = _formatArtefactsBlock(opts.outputs);
    const reportBody = opts.report.trim()
        ? `\n\n${opts.report.trim().slice(0, REPORT_BUDGET)}`
        : '';
    const sessionLine = `\n\n*세션 폴더:* \`${opts.sessionDir}\``;
    return `${header}\n\n${cmdLine}${brief}${agentsLine}${artefactsLine}${reportBody}${sessionLine}`;
}

/**
 * Pull a `*결과물:*` block out of the per-agent action reports. The
 * executor emits lines like `"✅ Created: testtimer/timer.py"` —
 * we re-format them with the workspace-relative path intact so the user
 * can `cd <workspace>/testtimer` and find the file.
 */
function _formatArtefactsBlock(outputs: AgentTurnOutput[]): string {
    const lines: string[] = [];
    for (const out of outputs) {
        if (!out.actionReport || out.actionReport.length === 0) continue;
        for (const r of out.actionReport) {
            // Keep success lines verbatim; trim long shell-output blocks.
            const trimmed = r.length > 200 ? r.slice(0, 200) + '…' : r;
            lines.push(`• ${trimmed}`);
        }
    }
    if (lines.length === 0) return '';
    return '\n\n*결과물:*\n' + lines.join('\n');
}
