/**
 * Per-chat conversation history for the Telegram bot.
 *
 * Why this exists: the previous bot was *stateless* — every inbound
 * message hit `AIService.chat({system, user})` in isolation, with no
 * memory of what the user said two minutes ago or what the secretary
 * just reported. Users hit this immediately: "I just told you about
 * project X, why don't you remember?"
 *
 * This module solves it with a thin append-only log:
 *
 *   - Each `(chatId, role, text)` triple is appended to a JSONL file at
 *     `<workspace>/.astra/company/_agents/secretary/telegram_history.jsonl`.
 *     One file per workspace, scoped by `chatId` at read time, so
 *     multi-chat setups don't bleed into each other.
 *   - A small in-memory cache (last 200 entries) sits in front so the
 *     hot path is `O(1)` lookup, not "scan the whole file".
 *   - `getRecentMessages` returns the most recent N entries for a chat,
 *     including the secretary's company-turn mirror entries that the
 *     dispatcher appends — so when the user asks a follow-up question
 *     ("저 폴더가 없다고"), the AI sees its own report from 30 seconds ago.
 *
 * The file is human-readable on purpose — users can grep it / delete it
 * to clear history without any CLI.
 */
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { logError } from '../../utils';

export interface TelegramHistoryEntry {
    chatId: number;
    role: 'user' | 'assistant';
    text: string;
    /** ISO timestamp at append time. */
    ts: string;
    /** Optional tag: `'company-mirror'` for secretary's auto-reports, `'reply'` for normal replies. */
    kind?: 'company-mirror' | 'reply' | 'user';
}

/** Max entries kept across all chats in the in-memory cache. */
const MEMORY_CAP = 200;
/** Max entries returned by `getRecentMessages`. */
const DEFAULT_RECENT = 12;

let _cache: TelegramHistoryEntry[] = [];
let _hydratedForWorkspace: string | null = null;

function _historyPath(): string {
    const ws = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
    if (!ws) return '';
    return path.join(ws, '.astra', 'company', '_agents', 'secretary', 'telegram_history.jsonl');
}

/**
 * Lazy-load the on-disk JSONL into the cache the first time we need it
 * for the current workspace. Switching workspaces re-hydrates from the
 * new file — that way each project keeps its own chat memory.
 */
function _hydrateIfNeeded(): void {
    const ws = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? '';
    if (_hydratedForWorkspace === ws) return;
    _hydratedForWorkspace = ws;
    _cache = [];
    const p = _historyPath();
    if (!p || !fs.existsSync(p)) return;
    try {
        const lines = fs.readFileSync(p, 'utf8').split('\n').filter((l) => l.trim());
        // Trim to the cap from the *end* so we keep the freshest entries.
        const tail = lines.length > MEMORY_CAP ? lines.slice(-MEMORY_CAP) : lines;
        for (const line of tail) {
            try {
                const entry = JSON.parse(line) as Partial<TelegramHistoryEntry>;
                if (typeof entry.chatId === 'number'
                    && (entry.role === 'user' || entry.role === 'assistant')
                    && typeof entry.text === 'string'
                    && typeof entry.ts === 'string') {
                    _cache.push(entry as TelegramHistoryEntry);
                }
            } catch {
                // Skip malformed line — keep going so a single bad write doesn't poison everything.
            }
        }
    } catch (e: any) {
        logError('telegram.history: read failed.', { error: e?.message ?? String(e) });
    }
}

/**
 * Append one entry. Best-effort: cache always updates so the *current*
 * session sees its own writes immediately even if the disk write fails.
 */
export function appendTelegramMessage(entry: Omit<TelegramHistoryEntry, 'ts'> & { ts?: string }): void {
    _hydrateIfNeeded();
    const fullEntry: TelegramHistoryEntry = {
        chatId: entry.chatId,
        role: entry.role,
        text: entry.text,
        kind: entry.kind,
        ts: entry.ts ?? new Date().toISOString(),
    };
    _cache.push(fullEntry);
    if (_cache.length > MEMORY_CAP) _cache.splice(0, _cache.length - MEMORY_CAP);
    const p = _historyPath();
    if (!p) return;
    try {
        fs.mkdirSync(path.dirname(p), { recursive: true });
        fs.appendFileSync(p, JSON.stringify(fullEntry) + '\n', 'utf8');
    } catch (e: any) {
        logError('telegram.history: append failed.', { chatId: entry.chatId, error: e?.message ?? String(e) });
    }
}

/**
 * Return the most recent N messages for one chat, oldest-first so the
 * caller can feed them to an LLM in turn order. `maxEntries` defaults to
 * 12 — enough for a couple of follow-up turns without exploding the
 * context window of a small local model.
 */
export function getRecentMessages(
    chatId: number,
    maxEntries: number = DEFAULT_RECENT,
): TelegramHistoryEntry[] {
    _hydrateIfNeeded();
    const forChat = _cache.filter((e) => e.chatId === chatId);
    return forChat.slice(-Math.max(1, maxEntries));
}

/**
 * Format recent history as a readable block we can inline into the user
 * message. We don't use the AI service's multi-message API because
 * `IAIService.chat({system, user})` only takes one user turn — embedding
 * the back-and-forth in the user message keeps the API surface unchanged
 * while still giving the model the context it needs.
 */
export function formatHistoryForPrompt(history: TelegramHistoryEntry[]): string {
    if (!history.length) return '';
    const lines: string[] = ['[Previous conversation in this Telegram chat]'];
    for (const e of history) {
        const speaker = e.role === 'user' ? 'User' : 'Bot';
        // Keep each message bounded so a single huge company-mirror report
        // doesn't dominate the prompt budget.
        const trimmed = e.text.length > 800 ? e.text.slice(0, 800) + '…' : e.text;
        lines.push(`${speaker}: ${trimmed}`);
    }
    lines.push('[End of previous conversation]');
    return lines.join('\n');
}

/** For tests + the "clear history" command. */
export function _resetCacheForTests(): void {
    _cache = [];
    _hydratedForWorkspace = null;
}
