import * as http from 'http';
import * as fs from 'fs';
import {
    _getBrainDir,
    findBrainFiles,
    logError,
    logInfo,
    summarizeText
} from './utils';
import { getConfig } from './config';
import { IAIService, IBrainService, AIService, BrainService } from './core/services';
import {
    ISkillInjectionService,
    FileSystemSkillInjectionService,
    SkillInjectionError,
} from './skills/skillInjectionService';
import { resolveAgentSkillsDir } from './lib/paths';

export interface BridgeInterface {
    injectSystemMessage(msg: string): void;
    getHistoryText(): string;
    sendPromptFromExtension(prompt: string): void;
    brainEnabled: boolean;
    findBrainFiles(dir: string): string[];
}

/**
 * BridgeServer: 
 * 외부 툴(EZER, A.U 등)과 Astra 확장을 연결하는 통신 브릿지.
 * 서비스 레이어(AIService, BrainService)를 통해 비즈니스 로직을 분리하여 유지보수성을 극대화했습니다.
 */
export class BridgeServer {
    private server: http.Server | null = null;
    private aiService: IAIService;
    private brainService: IBrainService;
    private skillService: ISkillInjectionService;

    constructor(
        private provider: BridgeInterface,
        aiService?: IAIService,
        brainService?: IBrainService,
        skillService?: ISkillInjectionService
    ) {
        // 의존성 주입 (DIP): 기본값 제공 및 외부 주입 허용
        this.aiService = aiService || new AIService();
        this.brainService = brainService || new BrainService();
        this.skillService = skillService || new FileSystemSkillInjectionService({
            resolveSkillsDir: resolveAgentSkillsDir,
            onInjected: (result, req) => {
                this.provider.injectSystemMessage(
                    `**[Skill]** Injected agent skill: ${req.displayName || result.safeName}`
                );
            },
        });
    }

    public start(port: number = 4825) {
        const server = http.createServer((req, res) => {
            res.setHeader('Access-Control-Allow-Origin', '*');
            res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
            res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

            if (req.method === 'OPTIONS') {
                res.writeHead(200);
                res.end();
                return;
            }

            const url = req.url || '';
            const method = req.method;

            // 라우팅 로직 (SRP에 따라 비즈니스 로직은 서비스로 위임)
            if (method === 'GET' && url === '/ping') {
                this.handlePing(res);
            } else if (method === 'POST' && url === '/api/exam') {
                this.handlePost(req, res, this.processExam.bind(this));
            } else if (method === 'POST' && url === '/api/evaluate') {
                this.handlePost(req, res, this.processEvaluate.bind(this));
            } else if (method === 'GET' && url === '/api/evaluate-history') {
                this.processEvaluateHistory(res);
            } else if (method === 'POST' && url === '/api/brain-inject') {
                this.handlePost(req, res, this.processBrainInject.bind(this));
            } else if (method === 'POST' && url === '/api/skill-inject') {
                this.handlePost(req, res, this.processSkillInject.bind(this));
            } else {
                res.writeHead(404);
                res.end();
            }
        });

        // once() 사용: 중복 에러 이벤트 방지
        server.once('error', (err: any) => {
            if (err.code === 'EADDRINUSE') {
                // INFO 레벨: ERR 콘솔 오염 방지 (Extension Host가 console.error를 ERR로 표시)
                logInfo(`Bridge Port ${port} already in use. Trying port ${port + 1}... (Current PID: ${process.pid})`);
                server.close();
                if (this.server === server) {
                    this.server = null;
                }
                this.start(port + 1);
            } else {
                // EADDRINUSE 외 진짜 에러만 logError
                logInfo(`Bridge server non-fatal error on port ${port}: ${err.code || err.message} (PID: ${process.pid})`);
            }
        });

        // 성공 시 서버 참조 저장
        server.listen(port, '127.0.0.1', () => {
            this.server = server;
            logInfo(`Bridge server active on 127.0.0.1:${port} (PID: ${process.pid}).`);
        });
    }

    /**
     * Cached `/ping` payload. `/ping` is a liveness probe that external tools may
     * hit frequently; the previous implementation did a full brain-corpus walk
     * plus a config serialize on *every* hit. We now compute that body at most
     * once per `PING_CACHE_TTL_MS` and reuse it — the response shape (status /
     * config / brain) is unchanged, ping just stops walking the brain per hit.
     */
    private _pingCache: { body: string; expiresAt: number } | null = null;
    private static readonly PING_CACHE_TTL_MS = 5000;

    private handlePing(res: http.ServerResponse) {
        const now = Date.now();
        if (!this._pingCache || this._pingCache.expiresAt <= now) {
            const brainDir = _getBrainDir();
            const brainCount = fs.existsSync(brainDir) ? findBrainFiles(brainDir).length : 0;
            const body = JSON.stringify({
                status: 'ok',
                config: getConfig(),
                brain: { fileCount: brainCount, enabled: this.provider.brainEnabled }
            });
            this._pingCache = { body, expiresAt: now + BridgeServer.PING_CACHE_TTL_MS };
        }
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(this._pingCache.body);
    }

    private handlePost(req: http.IncomingMessage, res: http.ServerResponse, processor: (data: any, res: http.ServerResponse) => Promise<void>) {
        let body = '';
        req.on('data', chunk => body += chunk.toString());
        req.on('end', async () => {
            try {
                const parsed = JSON.parse(body);
                await processor(parsed, res);
            } catch (e: any) {
                logError('Bridge request processor failed.', { url: req.url, error: e.message });
                res.writeHead(500, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ error: e.message }));
            }
        });
    }

    private async processExam(data: any, res: http.ServerResponse) {
        const prompt = data.prompt || 'Automatic Prompt';
        this.provider.sendPromptFromExtension(`[Bridge] ${prompt}`);
        const result = await this.aiService.call(prompt);
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: true, rawOutput: result }));
    }

    private async processEvaluate(data: any, res: http.ServerResponse) {
        const prompt = data.prompt || '';
        this.provider.injectSystemMessage(`**[A.U Evaluation]** Analyzing input...`);
        const result = await this.aiService.call(`[EVALUATE] ${prompt}`);
        this.provider.injectSystemMessage(`**[Result]** ${summarizeText(result, 200)}`);
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ rawOutput: result }));
    }

    private async processEvaluateHistory(res: http.ServerResponse) {
        const historyText = this.provider.getHistoryText();
        if (!historyText || historyText.length < 50) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: "Insufficient history" }));
            return;
        }

        const result = await this.aiService.call(`Analyze chat history for metrics (JSON):\n${historyText.slice(-6000)}`);
        
        // Try to extract valid JSON from the AI response
        let parsed: any = null;
        try {
            parsed = JSON.parse(result);
        } catch {
            // AI may wrap JSON in markdown code fences — try to extract
            const fenceMatch = result.match(/```(?:json)?\s*([\s\S]*?)```/);
            if (fenceMatch) {
                try { parsed = JSON.parse(fenceMatch[1].trim()); } catch { /* fall through */ }
            }
        }

        if (parsed) {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(parsed));
        } else {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: "Failed to parse AI response as JSON", raw: summarizeText(result, 500) }));
        }
    }

    private async processBrainInject(data: any, res: http.ServerResponse) {
        const { title, markdown, prompt } = data;
        await this.brainService.inject(title, markdown);
        this.provider.injectSystemMessage(`**[Brain]** Knowledge captured: ${title}`);
        const result = await this.aiService.call(prompt || `Analyze: ${title}`);
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: true, rawOutput: result }));
    }

    /**
     * Inject an agent skill (markdown-only — see SkillInjectionService doc) from
     * an external tool. Routing-only: validation, file IO, and telemetry live in
     * the service.
     */
    private async processSkillInject(data: any, res: http.ServerResponse) {
        try {
            const result = await this.skillService.inject({
                name: data?.name,
                content: data?.content ?? data?.markdown,
                displayName: data?.displayName,
                description: data?.description,
                source: data?.source,
            });
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({
                success: true,
                safeName: result.safeName,
                filePath: result.filePath,
            }));
        } catch (e: any) {
            const status = e instanceof SkillInjectionError && (e.code === 'INVALID_NAME' || e.code === 'EMPTY_CONTENT')
                ? 400
                : 500;
            const code = e instanceof SkillInjectionError ? e.code : 'UNKNOWN';
            logError('Skill inject endpoint failed.', { code, error: e?.message ?? String(e) });
            res.writeHead(status, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: e?.message ?? String(e), code }));
        }
    }
}
