import * as codearts from '@codearts/plugin';
import * as fs from 'fs';
import * as path from 'path';
import * as https from 'https';
import * as http from 'http';
import { exposable, expose } from '@cloudide/messaging';
import { AbstractBackend, PluginLike } from './abstract-backend';
import { AIService } from './ai-service';
import { ChatMessage, AIConfig, AIProvider, DEFAULT_CONFIG, ToolCall, AIResponse, ToolDefinition } from '../common/types';
import { getToolDefinitions, executeTool, ToolContext } from './tools';

const MAX_TOOL_ITERATIONS = 15;

@exposable
export class Backend extends AbstractBackend {
    private aiService: AIService;
    private conversationHistory: ChatMessage[] = [];
    private viewId = 'view_type_vibedev_chat';

    constructor(plugin: PluginLike, context: codearts.ExtensionContext) {
        super(plugin, context);
        this.aiService = new AIService();
    }

    async init(): Promise<void> { }

    public async run(): Promise<void> {
        this.plugin.log('INFO', 'VibeDev AI backend started');
    }

    public stop(): void {
        this.conversationHistory = [];
    }

    private getConfig(): AIConfig {
        const cfg = codearts.workspace.getConfiguration('vibedev');
        return {
            provider: cfg.get<AIProvider>('provider', DEFAULT_CONFIG.provider),
            apiKey: cfg.get<string>('apiKey', DEFAULT_CONFIG.apiKey),
            baseUrl: cfg.get<string>('baseUrl', DEFAULT_CONFIG.baseUrl),
            model: cfg.get<string>('model', DEFAULT_CONFIG.model),
            maxTokens: cfg.get<number>('maxTokens', DEFAULT_CONFIG.maxTokens),
            temperature: cfg.get<number>('temperature', DEFAULT_CONFIG.temperature)
        };
    }

    private getWorkspaceRoot(): string {
        const folders = codearts.workspace.workspaceFolders;
        if (folders && folders.length > 0) {
            return folders[0].uri.fsPath;
        }
        return process.cwd();
    }

    private async notifyToolStatus(toolName: string, description: string): Promise<void> {
        await this.plugin.call(`${this.viewId}::page.toolStatus`, toolName, description);
    }

    // Inline HTTP streaming — bypasses cached openai-compat module
    private inlineSendMessage(
        messages: ChatMessage[],
        config: AIConfig,
        tools?: ToolDefinition[],
        onChunk?: (content: string) => void
    ): Promise<AIResponse> {
        // Normalize baseUrl: strip trailing slashes and /v1 suffix to avoid doubling
        let base = config.baseUrl.replace(/\/+$/, '');
        if (base.endsWith('/v1')) base = base.slice(0, -3);
        const url = new URL(`${base}/v1/chat/completions`);
        const isHttps = url.protocol === 'https:';
        const mod = isHttps ? https : http;
        this.plugin.log('INFO', `[VibeDev-inline] Request URL: ${url.href}`);

        const bodyObj: Record<string, unknown> = {
            model: config.model,
            messages: messages.map(m => {
                const msg: Record<string, unknown> = { role: m.role, content: m.content };
                if (m.toolCalls && m.toolCalls.length > 0) {
                    msg.tool_calls = m.toolCalls.map(tc => ({
                        id: tc.id, type: 'function',
                        function: { name: tc.name, arguments: tc.arguments }
                    }));
                }
                if (m.toolCallId) msg.tool_call_id = m.toolCallId;
                if (m.role === 'assistant' && m.reasoningContent) {
                    msg.reasoning_content = m.reasoningContent;
                }
                return msg;
            }),
            max_tokens: config.maxTokens,
            temperature: config.temperature,
            stream: true
        };
        if (tools && tools.length > 0) {
            bodyObj.tools = tools.map(t => ({
                type: 'function',
                function: { name: t.name, description: t.description, parameters: t.parameters }
            }));
        }

        const body = JSON.stringify(bodyObj);
        this.plugin.log('INFO', `[VibeDev-inline] Request body preview: ${body.slice(0, 500)}`);

        return new Promise<AIResponse>((resolve, reject) => {
            const req = mod.request(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${config.apiKey}`,
                    'Accept': 'text/event-stream'
                }
            }, (res) => {
                if (res.statusCode !== 200) {
                    let errBody = '';
                    res.on('data', (chunk: Buffer) => { errBody += chunk.toString(); });
                    res.on('end', () => reject(new Error(`API error ${res.statusCode}: ${errBody}`)));
                    return;
                }

                let fullContent = '';
                let fullReasoningContent = '';
                let buffer = '';
                let chunkCount = 0;
                const toolCallMap = new Map<number, { id: string; name: string; arguments: string }>();

                res.on('data', (chunk: Buffer) => {
                    buffer += chunk.toString();
                    const lines = buffer.split('\n');
                    buffer = lines.pop() || '';

                    for (const line of lines) {
                        const trimmed = line.trim();
                        if (!trimmed || !trimmed.startsWith('data: ')) continue;
                        const data = trimmed.slice(6);
                        if (data === '[DONE]') continue;
                        try {
                            const parsed = JSON.parse(data);
                            chunkCount++;
                            const delta = parsed.choices?.[0]?.delta;
                            if (!delta) continue;

                            // Log first few chunks to see actual field names
                            if (chunkCount <= 3) {
                                this.plugin.log('INFO', `[VibeDev-inline] chunk#${chunkCount} delta keys: [${Object.keys(delta).join(',')}], choice keys: [${Object.keys(parsed.choices[0]).join(',')}]`);
                            }

                            if (delta.content) {
                                fullContent += delta.content;
                                if (onChunk) onChunk(delta.content);
                            }

                            // Capture reasoning_content from all known field names
                            const rc = delta.reasoning_content || delta.reasoning ||
                                delta.thought || delta.thinking ||
                                parsed.choices?.[0]?.reasoning_content || parsed.choices?.[0]?.reasoning;
                            if (rc) fullReasoningContent += rc;

                            if (delta.tool_calls) {
                                for (const tc of delta.tool_calls) {
                                    const idx = tc.index;
                                    if (!toolCallMap.has(idx)) {
                                        toolCallMap.set(idx, { id: tc.id || '', name: '', arguments: '' });
                                    }
                                    const entry = toolCallMap.get(idx)!;
                                    if (tc.id) entry.id = tc.id;
                                    if (tc.function?.name) entry.name = tc.function.name;
                                    if (tc.function?.arguments) entry.arguments += tc.function.arguments;
                                }
                            }
                        } catch { /* skip malformed */ }
                    }
                });

                res.on('end', () => {
                    if (buffer.trim()) {
                        const trimmed = buffer.trim();
                        if (trimmed.startsWith('data: ') && trimmed.slice(6) !== '[DONE]') {
                            try {
                                const parsed = JSON.parse(trimmed.slice(6));
                                const delta = parsed.choices?.[0]?.delta;
                                if (delta?.content) {
                                    fullContent += delta.content;
                                    if (onChunk) onChunk(delta.content);
                                }
                                const rc2 = delta?.reasoning_content || delta?.reasoning ||
                                    delta?.thought || delta?.thinking ||
                                    parsed.choices?.[0]?.reasoning_content || parsed.choices?.[0]?.reasoning;
                                if (rc2) fullReasoningContent += rc2;
                                if (delta?.tool_calls) {
                                    for (const tc of delta.tool_calls) {
                                        const idx = tc.index;
                                        if (!toolCallMap.has(idx)) {
                                            toolCallMap.set(idx, { id: tc.id || '', name: '', arguments: '' });
                                        }
                                        const entry = toolCallMap.get(idx)!;
                                        if (tc.id) entry.id = tc.id;
                                        if (tc.function?.name) entry.name = tc.function.name;
                                        if (tc.function?.arguments) entry.arguments += tc.function.arguments;
                                    }
                                }
                            } catch { /* skip */ }
                        }
                    }

                    const toolCalls: ToolCall[] = [];
                    for (const entry of toolCallMap.values()) {
                        toolCalls.push({ id: entry.id, name: entry.name, arguments: entry.arguments });
                    }

                    this.plugin.log('INFO', `[VibeDev-inline] Done: contentLen=${fullContent.length}, toolCalls=${toolCalls.length}, reasoningLen=${fullReasoningContent.length}, chunks=${chunkCount}`);

                    resolve({
                        content: fullContent,
                        toolCalls,
                        reasoningContent: fullReasoningContent || undefined
                    });
                });

                res.on('error', reject);
            });

            req.on('error', reject);
            req.write(body);
            req.end();
        });
    }


    @expose('ai.getSettings')
    public getSettings(): AIConfig {
        return this.getConfig();
    }

    @expose('ai.saveSettings')
    public async saveSettings(settings: Partial<AIConfig>): Promise<void> {
        const cfg = codearts.workspace.getConfiguration('vibedev');
        if (settings.provider !== undefined) await cfg.update('provider', settings.provider);
        if (settings.apiKey !== undefined) await cfg.update('apiKey', settings.apiKey);
        if (settings.baseUrl !== undefined) await cfg.update('baseUrl', settings.baseUrl);
        if (settings.model !== undefined) await cfg.update('model', settings.model);
        if (settings.maxTokens !== undefined) await cfg.update('maxTokens', settings.maxTokens);
        if (settings.temperature !== undefined) await cfg.update('temperature', settings.temperature);
        this.plugin.log('INFO', 'VibeDev settings saved');
    }

    @expose('ai.chat')
    public async chat(userMessage: string): Promise<void> {
        const config = this.getConfig();

        if (!config.apiKey) {
            await this.plugin.call(`${this.viewId}::page.addMessage`, {
                role: 'assistant',
                content: 'Please configure your API key in the settings first.'
            });
            await this.plugin.call(`${this.viewId}::page.chatDone`);
            return;
        }

        this.conversationHistory.push({
            role: 'user',
            content: userMessage,
            timestamp: Date.now()
        });

        // Editor context
        let contextPrefix = '';
        try {
            const editor = codearts.window.activeTextEditor;
            if (editor) {
                const doc = editor.document;
                const selection = editor.selection;
                const selectedText = doc.getText(selection);
                const languageId = doc.languageId;
                if (selectedText) {
                    contextPrefix = `[Selected ${languageId} code]:\n\`\`\`${languageId}\n${selectedText}\n\`\`\`\n\n`;
                } else {
                    const fullText = doc.getText();
                    if (fullText.length < 8000) {
                        contextPrefix = `[Current file: ${doc.fileName} (${languageId})]:\n\`\`\`${languageId}\n${fullText}\n\`\`\`\n\n`;
                    }
                }
            }
        } catch { /* ignore */ }

        const systemMessage: ChatMessage = {
            role: 'system',
            content: 'You are a helpful AI coding assistant. You have access to tools that let you read, write, and search files in the workspace. Use them when the user asks you to work with files. Provide clear, concise answers.'
        };

        const messages: ChatMessage[] = [systemMessage];
        const historySlice = this.conversationHistory.slice(-20);
        for (const msg of historySlice) {
            if (msg.role === 'user' && messages.length === 1 && contextPrefix) {
                messages.push({ ...msg, content: contextPrefix + msg.content });
            } else {
                messages.push(msg);
            }
        }

        const tools = getToolDefinitions();
        const toolCtx: ToolContext = { workspaceRoot: this.getWorkspaceRoot() };

        try {
            // Tool call loop
            for (let iteration = 0; iteration < MAX_TOOL_ITERATIONS; iteration++) {
                let streamedContent = '';

                // Debug: log message structure before sending
                const msgSummary = messages.map(m => ({
                    role: m.role,
                    hasReasoning: !!m.reasoningContent,
                    reasoningLen: m.reasoningContent?.length || 0,
                    hasToolCalls: !!(m.toolCalls && m.toolCalls.length > 0),
                    hasToolCallId: !!m.toolCallId,
                    contentLen: m.content?.length || 0
                }));
                console.log(`[VibeDev] Sending ${messages.length} messages (iteration ${iteration}):`, JSON.stringify(msgSummary));

                const response: AIResponse = await this.inlineSendMessage(
                    messages, config, tools,
                    (chunk) => {
                        streamedContent += chunk;
                        this.plugin.call(`${this.viewId}::page.addChunk`, chunk);
                    }
                );

                console.log(`[VibeDev] Response: contentLen=${response.content.length}, toolCalls=${response.toolCalls.length}, hasReasoning=${!!response.reasoningContent}, reasoningLen=${response.reasoningContent?.length || 0}`);

                // If no tool calls, we're done
                if (response.toolCalls.length === 0) {
                    this.conversationHistory.push({
                        role: 'assistant',
                        content: response.content,
                        timestamp: Date.now(),
                        reasoningContent: response.reasoningContent
                    });
                    break;
                }

                // Has tool calls — add assistant message with tool calls
                messages.push({
                    role: 'assistant',
                    content: response.content,
                    toolCalls: response.toolCalls,
                    reasoningContent: response.reasoningContent
                });
                this.conversationHistory.push({
                    role: 'assistant',
                    content: response.content,
                    toolCalls: response.toolCalls,
                    timestamp: Date.now(),
                    reasoningContent: response.reasoningContent
                });

                // Execute each tool call
                for (const tc of response.toolCalls) {
                    await this.notifyToolStatus(tc.name, tc.arguments);

                    const result = await executeTool(tc, toolCtx);

                    const toolMsg: ChatMessage = {
                        role: 'tool',
                        content: result.content,
                        toolCallId: result.toolCallId
                    };
                    messages.push(toolMsg);
                    this.conversationHistory.push(toolMsg);
                }
            }
        } catch (err: unknown) {
            const errMsg = err instanceof Error ? err.message : String(err);
            this.plugin.log('ERROR', `AI chat error: ${errMsg}`);
            await this.plugin.call(`${this.viewId}::page.addMessage`, {
                role: 'assistant',
                content: `Error: ${errMsg}`
            });
        }

        await this.plugin.call(`${this.viewId}::page.chatDone`);
    }

    @expose('ai.clearHistory')
    public clearHistory(): void {
        this.conversationHistory = [];
        this.plugin.log('INFO', 'Conversation history cleared');
    }

    // File operations (still exposed for direct frontend use)

    @expose('ai.readFile')
    public async readFile(filePath: string): Promise<{ success: boolean; content?: string; error?: string }> {
        try {
            const absPath = path.isAbsolute(filePath) ? filePath : path.join(this.getWorkspaceRoot(), filePath);
            const content = fs.readFileSync(absPath, 'utf-8');
            return { success: true, content };
        } catch (err: unknown) {
            const errMsg = err instanceof Error ? err.message : String(err);
            return { success: false, error: errMsg };
        }
    }

    @expose('ai.writeFile')
    public async writeFile(filePath: string, content: string): Promise<{ success: boolean; error?: string }> {
        try {
            const absPath = path.isAbsolute(filePath) ? filePath : path.join(this.getWorkspaceRoot(), filePath);
            const dir = path.dirname(absPath);
            if (!fs.existsSync(dir)) {
                fs.mkdirSync(dir, { recursive: true });
            }
            fs.writeFileSync(absPath, content, 'utf-8');
            this.plugin.log('INFO', `File written: ${absPath}`);
            try {
                const doc = await codearts.workspace.openTextDocument(absPath);
                await codearts.window.showTextDocument(doc);
            } catch { /* ignore */ }
            return { success: true };
        } catch (err: unknown) {
            const errMsg = err instanceof Error ? err.message : String(err);
            return { success: false, error: errMsg };
        }
    }

    @expose('ai.applyCode')
    public async applyCode(filePath: string, code: string): Promise<{ success: boolean; error?: string }> {
        return this.writeFile(filePath, code);
    }
}