import { Injectable, Logger } from '@nestjs/common';
import type { AiChatDto } from './dto/ai-chat.dto';
import { AdminConversationService } from '../admin-conversation/admin-conversation.service';
import { AppConfigService } from '../config/config.service';
import type { Response } from 'express';

// HERMES_API_URL now read from config, fallback to hardcoded
const HERMES_API_KEY = 'zhixi-hermes-key-2026';

interface HermesRun {
  run_id: string;
  status: string;
}
interface HermesSSEEvent {
  event: string;
  run_id: string;
  delta?: string;
  text?: string;
  output?: string;
  usage?: { input_tokens: number; output_tokens: number; total_tokens: number };
  error?: { message: string };
}

@Injectable()
export class AdminAiChatService {
  private readonly logger = new Logger(AdminAiChatService.name);
  private activeRuns = new Map<string, AbortController>();

  constructor(private readonly conversationService: AdminConversationService, private readonly config: AppConfigService) {}

  async chat(dto: AiChatDto, adminUserId: string) {
    const sessionId = dto.conversationId
      ? await this.conversationService.getSessionId(dto.conversationId, adminUserId)
      : null;
    const conversationId = dto.conversationId
      ?? (await this.conversationService.create(adminUserId)).id;

    const userMsg = dto.messages[dto.messages.length - 1];
    if (userMsg?.role === 'user') {
      await this.conversationService.saveMessage(conversationId, 'user', userMsg.content);
    }

    const result = await this.callHermes(dto.messages, sessionId);
    await this.conversationService.saveMessage(conversationId, 'assistant', result.content);
    return { ...result, conversationId };
  }

  private async callHermes(
    messages: Array<{ role: string; content: string }>,
    sessionId: string | null,
  ) {
    const start = Date.now();
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      Authorization: 'Bearer ' + HERMES_API_KEY,
    };
    if (sessionId) headers['X-Hermes-Session-Id'] = sessionId;

    const hermesUrl = await this.config.get('hermes.api_url', 'http://10.2.0.7:8642');
    const resp = await fetch(`${hermesUrl}/v1/chat/completions`, {
      method: 'POST', headers,
      body: JSON.stringify({ model: 'hermes-agent', messages, temperature: parseFloat((await this.config.get('ai.temperature')) || '0.7'), max_tokens: parseInt((await this.config.get('ai.max_tokens')) || '4096') }),
      signal: AbortSignal.timeout(120_000),
    });
    if (!resp.ok) throw new Error(`Hermes API error ${resp.status}`);
    const data = await resp.json();
    const content = data.choices?.[0]?.message?.content || '';
    const usage = data.usage || {};
    this.logger.log(`Chat: ${Date.now() - start}ms, tokens: ${usage.prompt_tokens || 0}/${usage.completion_tokens || 0}`);
    return { content, usage: { model: 'hermes-agent', inputTokens: usage.prompt_tokens, outputTokens: usage.completion_tokens } };
  }

  // ── Runs + SSE ────────────────────────────────────────────

  async streamChat(dto: AiChatDto, adminUserId: string, res: Response) {
    const sessionId = dto.conversationId
      ? await this.conversationService.getSessionId(dto.conversationId, adminUserId)
      : null;
    const conversationId = dto.conversationId
      ?? (await this.conversationService.create(adminUserId)).id;

    // Save user message
    const userMsg = dto.messages[dto.messages.length - 1];
    if (userMsg?.role === 'user') {
      await this.conversationService.saveMessage(conversationId, 'user', userMsg.content);
    }

    // Set up SSE response
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
      'X-Accel-Buffering': 'no',
    });
    res.write(`data: ${JSON.stringify({ event: 'meta', conversationId })}\n\n`);

    try {
      // 1. Create run
      const headers: Record<string, string> = {
        'Content-Type': 'application/json',
        Authorization: 'Bearer ' + HERMES_API_KEY,
      };
      if (sessionId) headers['X-Hermes-Session-Id'] = sessionId;

      const hermesUrl2 = await this.config.get('hermes.api_url', 'http://10.2.0.7:8642');
      const runResp = await fetch(`${hermesUrl2}/v1/runs`, {
        method: 'POST', headers,
        body: JSON.stringify({
          input: dto.messages.map(m => ({ role: m.role, content: m.content })),
        }),
      });
      if (!runResp.ok) {
        const err = await runResp.text().catch(() => '');
        throw new Error(`Run creation failed ${runResp.status}: ${err}`);
      }
      const run = await runResp.json() as HermesRun;
      const runId = run.run_id;

      // 2. Stream events
      const abortController = new AbortController();
      this.activeRuns.set(runId, abortController);

      const eventResp = await fetch(`${hermesUrl2}/v1/runs/${runId}/events`, {
        headers: { Authorization: 'Bearer ' + HERMES_API_KEY },
        signal: abortController.signal,
      });

      const reader = eventResp.body?.getReader();
      if (!reader) throw new Error('No response body');

      const decoder = new TextDecoder();
      let buffer = '';
      let fullResponse = '';

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() || '';
        for (const line of lines) {
          if (line.startsWith('data: ')) {
            try {
              const event: HermesSSEEvent = JSON.parse(line.slice(6));
              // Forward to client
              res.write(`data: ${JSON.stringify({ ...event, runId })}\n\n`);

              // Forward tool events
              if (event.event === 'approval.request') {
                res.write(`data: ${JSON.stringify({ event: 'approval.request', command: (event as any).command, description: (event as any).description, choices: (event as any).choices, runId: runId })}

`);
              }
              if (event.event === 'tool.started' || event.event === 'tool.completed') {
                res.write(`data: ${JSON.stringify({ event: event.event, tool: (event as any).tool, preview: (event as any).preview, duration: (event as any).duration, error: (event as any).error })}

`);
              }
              if (event.event === 'message.delta' && event.delta) {
                fullResponse += event.delta;
              } else if (event.event === 'run.completed') {
                fullResponse = event.output || fullResponse;
              } else if (event.event === 'run.error' || event.event === 'error') {
                throw new Error(event.error?.message || 'Run error');
              }
            } catch (e: any) {
              if (e.message?.includes('Run error') || e.message?.includes('error')) throw e;
              // Ignore parse errors on individual lines
            }
          }
        }
      }

      // Save assistant message
      if (fullResponse) {
        await this.conversationService.saveMessage(conversationId, 'assistant', fullResponse);
      }

      res.write(`data: ${JSON.stringify({ event: 'done', conversationId })}\n\n`);
    } catch (err: any) {
      if (err.name === 'AbortError') {
        res.write(`data: ${JSON.stringify({ event: 'stopped' })}\n\n`);
      } else {
        this.logger.error('Stream error: ' + err.message);
        res.write(`data: ${JSON.stringify({ event: 'error', error: err.message })}\n\n`);
      }
    } finally {
      res.end();
    }
  }

  async stopRun(runId: string) {
    const controller = this.activeRuns.get(runId);
    if (controller) {
      controller.abort();
      this.activeRuns.delete(runId);
    }
    // Also tell Hermes
    try {
      await fetch(`http://10.2.0.7:8642/v1/runs/${runId}/stop`, {
        method: 'POST',
        headers: { Authorization: 'Bearer ' + HERMES_API_KEY },
      });
    } catch { /* best effort */ }
    return { success: true };
  }

  getDashboardConfig() {
    return {
      url: 'http://10.2.0.7:9119',
      apiUrl: 'http://10.2.0.7:8642/v1',
      description: 'Hermes Agent Dashboard — 4核4G 上的 AI Agent',
    };
  }
}
