// src/tools/chat/export.ts
// Copyright (C) 2025 DTP Technologies, LLC
// All Rights Reserved

import fs from "node:fs/promises";
import path from "node:path";

import type { ToolDefinition } from "../../lib/ai-client.js";
import type { IUser } from "../../models/user.js";

import {
  DtpTool,
  type ToolArguments,
  type ToolContext,
} from "../../lib/tool.js";
import { ChatHistory } from "../../models/chat-history.js";
import { ChatSession, ChatSessionMode } from "../../models/chat-session.js";

type ExportFormat = "json" | "markdown" | "text";

interface IChatToolCallParameter {
  name: string;
  value: string;
}

interface IChatFileOperation {
  type: "read" | "write" | "edit" | "shell";
  path?: string;
  diff?: string;
  linesAdded?: number;
  linesRemoved?: number;
  isBinary?: boolean;
}

interface IChatToolCall {
  tool: {
    name: string;
    callId: string;
    parameters: IChatToolCallParameter[];
  };
  response: string;
  fileOperation?: IChatFileOperation;
  subagentStats?: {
    inputTokens: number;
    outputTokens: number;
    toolCallCount: number;
  };
}

interface IChatHistoryExport {
  createdAt: Date;
  prompt: string;
  mode: string;
  toolCalls: IChatToolCall[];
  fileOperations: IChatFileOperation[];
  response?: {
    thinking?: string;
    message?: string;
  };
  qdrantId?: string;
  status: "success" | "failed";
  isSubagent?: boolean;
  error?: {
    message: string;
    stack?: string;
    timestamp: Date;
  };
  subagentHistory?: IChatHistoryExport[];
}

interface RawChatHistoryItem {
  createdAt: Date | string;
  prompt: string | undefined;
  mode: string | undefined;
  toolCalls: unknown;
  fileOperations: unknown;
  response: { thinking?: string; message?: string } | undefined;
  qdrantId: string | undefined;
  status: "success" | "failed" | undefined;
  isSubagent: boolean | undefined;
  error?: { message: string; stack?: string; timestamp: Date };
  subagentHistory?: RawChatHistoryItem[];
}

export class ChatExportTool extends DtpTool {
  get name(): string {
    return "ChatExportTool";
  }
  get slug(): string {
    return "chat-export";
  }
  get metadata() {
    return {
      name: this.definition.function.name || "chat_export",
      category: "chat",
      tags: ["export", "session", "download", "backup"],
      modes: [
        ChatSessionMode.Plan,
        ChatSessionMode.Build,
        ChatSessionMode.Test,
        ChatSessionMode.Ship,
        ChatSessionMode.Develop,
      ],
    };
  }

  public definition: ToolDefinition = {
    type: "function",
    function: {
      name: "chat_export",
      description:
        "Export the conversation from the current or specified session to JSON, Markdown, or plain text format. When filename is provided, writes the export to a file in the current working directory. Returns the formatted content and file information.",
      parameters: {
        type: "object",
        properties: {
          session_id: {
            type: "string",
            description:
              "Optional: Session ID to export. If omitted, exports the current session.",
          },
          format: {
            type: "string",
            enum: ["json", "markdown", "text"],
            description:
              "Export format: 'json' for structured data, 'markdown' for readable docs, 'text' for plain text.",
          },
          filename: {
            type: "string",
            description:
              "Optional: Filename to write the export to. When provided, the export will be written to a file in the current working directory. The file will be created or overwritten if it exists.",
          },
        },
        required: ["format"],
      },
    },
  };

  public async execute(
    context: ToolContext,
    args: ToolArguments,
  ): Promise<string> {
    const { session_id, format, filename } = args;
    const user = context.session.user as IUser;
    const userId = user._id.toHexString();
    const currentSessionId = context.session._id.toHexString();

    const exportFormat = (format as ExportFormat) || "text";
    if (!["json", "markdown", "text"].includes(exportFormat)) {
      return this.error(
        "INVALID_PARAMETER",
        `Invalid format: ${exportFormat}. Must be 'json', 'markdown', or 'text'.`,
        { parameter: "format" },
      );
    }

    const targetSessionId =
      (session_id as string | undefined) || currentSessionId;
    if (!targetSessionId) {
      return this.error(
        "MISSING_SESSION",
        "No session ID provided and no current session context.",
      );
    }

    try {
      const session = await ChatSession.findOne({
        _id: targetSessionId,
        user: userId,
      });

      if (!session) {
        return this.error(
          "NOT_FOUND",
          `Session not found: ${targetSessionId}`,
          {
            recoveryHint:
              "Verify the session ID exists and belongs to the current user.",
          },
        );
      }

      const history = await ChatHistory.find({
        session: targetSessionId,
        status: "success",
      })
        .sort({ createdAt: 1 })
        .lean();

      if (history.length === 0) {
        const result = {
          content: "",
          turnCount: 0,
          format: exportFormat,
          filename: undefined as string | undefined,
        };

        if (filename && typeof filename === "string") {
          result.filename = filename;
        }

        return this.success(
          result,
          "No conversation history found in this session.",
        );
      }

      const enhancedHistory = await this.enhanceHistoryWithSubagents(
        history as RawChatHistoryItem[],
      );
      const exported = this.formatExport(
        session,
        enhancedHistory,
        exportFormat,
      );

      let result: {
        content: string;
        turnCount: number;
        format: ExportFormat;
        filename?: string;
        filePath?: string;
        byteCount?: number;
      } = {
        content: exported,
        turnCount: enhancedHistory.length,
        format: exportFormat,
      };

      if (filename && typeof filename === "string") {
        const filePath = path.resolve(filename);
        await fs.writeFile(filePath, exported, "utf-8");
        const byteCount = Buffer.byteLength(exported, "utf-8");
        result.filename = filename;
        result.filePath = filePath;
        result.byteCount = byteCount;
      }

      return this.success(
        result,
        filename && typeof filename === "string"
          ? `Export written to ${filename} (${result.byteCount} bytes)`
          : `Exported ${result.turnCount} turns in ${exportFormat} format`,
      );
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : String(error);

      this.log.error("Failed to export session", {
        sessionId: targetSessionId,
        error: errorMessage,
      });

      return this.error(
        "OPERATION_FAILED",
        `Failed to export session: ${errorMessage}`,
        {
          recoveryHint: "Try a different format or check the session exists.",
        },
      );
    }
  }

  private async enhanceHistoryWithSubagents(
    history: RawChatHistoryItem[],
  ): Promise<IChatHistoryExport[]> {
    const enhanced: IChatHistoryExport[] = [];

    for (const item of history) {
      const entry: IChatHistoryExport = {
        createdAt: new Date(item.createdAt),
        prompt: (item.prompt as string) || "",
        mode: (item.mode as string) || "",
        toolCalls: (item.toolCalls as IChatToolCall[]) || [],
        fileOperations: (item.fileOperations as IChatFileOperation[]) || [],
        response: item.response,
        qdrantId: (item.qdrantId as string) || undefined,
        status: (item.status as "success" | "failed") || "success",
        isSubagent: (item.isSubagent as boolean) || false,
        error: item.error,
      };

      // Handle subagent history recursively
      if (item.subagentHistory && Array.isArray(item.subagentHistory)) {
        entry.subagentHistory = await this.enhanceHistoryWithSubagents(
          item.subagentHistory as RawChatHistoryItem[],
        );
      }

      enhanced.push(entry);
    }

    return enhanced;
  }

  private formatExport(
    session: { _id: unknown; name: string; createdAt: Date; type: string },
    history: IChatHistoryExport[],
    format: ExportFormat,
  ): string {
    switch (format) {
      case "json":
        return this.formatAsJson(session, history);
      case "markdown":
        return this.formatAsMarkdown(session, history);
      case "text":
      default:
        return this.formatAsText(session, history);
    }
  }

  private formatAsJson(
    session: { _id: unknown; name: string; createdAt: Date; type: string },
    history: IChatHistoryExport[],
  ): string {
    const data = {
      session: {
        id: (session._id as any).toString(),
        name: session.name,
        type: session.type,
        createdAt: session.createdAt.toISOString(),
      },
      turns: history.map((turn, index) => ({
        turnNumber: index + 1,
        timestamp: turn.createdAt.toISOString(),
        mode: turn.mode,
        user: turn.prompt,
        assistant: turn.response?.message || "",
        thinking: turn.response?.thinking || undefined,
        toolCalls: turn.toolCalls.map((tc) => ({
          tool: {
            name: tc.tool?.name || "",
            callId: tc.tool?.callId || "",
            parameters: tc.tool?.parameters || [],
          },
          response: tc.response || "",
          fileOperation: tc.fileOperation,
          subagentStats: tc.subagentStats,
        })),
        fileOperations: turn.fileOperations,
        status: turn.status,
        isSubagent: turn.isSubagent || false,
        error: turn.error,
        subagentHistory: turn.subagentHistory?.map((sh) => ({
          turnNumber: 0,
          timestamp: sh.createdAt.toISOString(),
          mode: sh.mode,
          user: sh.prompt,
          assistant: sh.response?.message || "",
          thinking: sh.response?.thinking || undefined,
          toolCalls: sh.toolCalls.map((tc) => ({
            tool: {
              name: tc.tool?.name || "",
              callId: tc.tool?.callId || "",
              parameters: tc.tool?.parameters || [],
            },
            response: tc.response || "",
            fileOperation: tc.fileOperation,
            subagentStats: tc.subagentStats,
          })),
          fileOperations: sh.fileOperations,
          status: sh.status,
          isSubagent: sh.isSubagent || false,
          error: sh.error,
        })),
      })),
    };

    return JSON.stringify(data, null, 2);
  }

  private formatAsMarkdown(
    session: { _id: unknown; name: string; createdAt: Date; type: string },
    history: IChatHistoryExport[],
  ): string {
    let md = `# ${session.name}\n\n`;
    md += `**Session ID:** ${(session._id as any).toString()}\n`;
    md += `**Type:** ${session.type}\n`;
    md += `**Created:** ${session.createdAt.toISOString()}\n`;
    md += `**Turns:** ${history.length}\n\n`;
    md += `---\n\n`;

    for (const [i, turn] of history.entries()) {
      md += `## Turn ${i + 1}\n\n`;
      md += `**Timestamp:** ${turn.createdAt.toISOString()}\n`;
      md += `**Mode:** ${turn.mode}\n`;
      md += `**Status:** ${turn.status}\n`;
      md += `**Subagent:** ${turn.isSubagent ? "Yes" : "No"}\n\n`;
      md += `### User\n\n${turn.prompt}\n\n`;

      if (turn.response?.thinking) {
        md += `### Thinking\n\n${turn.response.thinking}\n\n`;
      }

      if (turn.response?.message) {
        md += `### Assistant\n\n${turn.response.message}\n\n`;
      }

      if (turn.toolCalls && turn.toolCalls.length > 0) {
        md += `### Tool Calls\n\n`;
        for (const tc of turn.toolCalls) {
          md += `#### Tool Call: ${tc.tool?.name || "unknown"}\n\n`;
          md += `**Call ID:** ${tc.tool?.callId || ""}\n\n`;
          if (tc.tool?.parameters && tc.tool.parameters.length > 0) {
            md += `**Parameters:**\n\n`;
            for (const p of tc.tool.parameters) {
              md += `- ${p.name}: ${p.value}\n`;
            }
            md += "\n";
          }
          if (tc.response) {
            md += `**Response:** ${tc.response}\n\n`;
          }
          if (tc.fileOperation) {
            const fo = tc.fileOperation;
            md += `**File Operation:** ${fo.type}\n`;
            if (fo.path) md += `**Path:** ${fo.path}\n`;
            if (fo.linesAdded !== undefined)
              md += `**Lines Added:** ${fo.linesAdded}\n`;
            if (fo.linesRemoved !== undefined)
              md += `**Lines Removed:** ${fo.linesRemoved}\n`;
            md += "\n";
          }
        }
        md += "---\n\n";
      }

      if (turn.fileOperations && turn.fileOperations.length > 0) {
        md += `### File Operations\n\n`;
        for (const fo of turn.fileOperations) {
          md += `#### File Operation: ${fo.type}\n\n`;
          if (fo.path) md += `**Path:** ${fo.path}\n`;
          if (fo.linesAdded !== undefined)
            md += `**Lines Added:** ${fo.linesAdded}\n`;
          if (fo.linesRemoved !== undefined)
            md += `**Lines Removed:** ${fo.linesRemoved}\n`;
          md += "\n---\n\n";
        }
      }

      if (turn.subagentHistory && turn.subagentHistory.length > 0) {
        md += `### Subagent History\n\n`;
        for (const sh of turn.subagentHistory) {
          md += `#### Subagent Turn\n\n`;
          md += `**Timestamp:** ${sh.createdAt.toISOString()}\n`;
          md += `**Mode:** ${sh.mode}\n`;
          md += `**User:** ${sh.prompt}\n\n`;
          if (sh.response?.message) {
            md += `**Assistant:** ${sh.response.message}\n\n`;
          }
          if (sh.response?.thinking) {
            md += `**Thinking:** ${sh.response.thinking}\n\n`;
          }
          md += "---\n\n";
        }
      }

      if (turn.error) {
        md += `### Error\n\n`;
        md += `**Message:** ${turn.error.message}\n\n`;
        if (turn.error.stack) {
          md += `**Stack:**\n\`\`\`\n${turn.error.stack}\n\`\`\`\n\n`;
        }
        md += "---\n\n";
      }

      md += `---\n\n`;
    }

    return md;
  }

  private formatAsText(
    session: { _id: unknown; name: string; createdAt: Date; type: string },
    history: IChatHistoryExport[],
  ): string {
    let txt = `${session.name}\n`;
    txt += `${"=".repeat(session.name.length)}\n\n`;
    txt += `Session ID: ${(session._id as any).toString()}\n`;
    txt += `Type: ${session.type}\n`;
    txt += `Created: ${session.createdAt.toISOString()}\n`;
    txt += `Turns: ${history.length}\n\n`;
    txt += `${"-".repeat(40)}\n\n`;

    for (const [i, turn] of history.entries()) {
      txt += `[Turn ${i + 1}] ${turn.createdAt.toISOString()}\n`;
      txt += `Mode: ${turn.mode}\n`;
      txt += `Status: ${turn.status}\n`;
      txt += `Subagent: ${turn.isSubagent ? "Yes" : "No"}\n\n`;
      txt += `USER:\n${turn.prompt}\n\n`;

      if (turn.response?.thinking) {
        txt += `THINKING:\n${turn.response.thinking}\n\n`;
      }

      if (turn.response?.message) {
        txt += `ASSISTANT:\n${turn.response.message}\n\n`;
      }

      if (turn.toolCalls && turn.toolCalls.length > 0) {
        txt += `TOOL CALLS:\n`;
        for (const tc of turn.toolCalls) {
          txt += `- Tool: ${tc.tool?.name || "unknown"} (Call ID: ${tc.tool?.callId || ""})\n`;
          if (tc.tool?.parameters) {
            for (const p of tc.tool.parameters) {
              txt += `  - ${p.name}: ${p.value}\n`;
            }
          }
          if (tc.response) {
            txt += `  Response: ${tc.response}\n`;
          }
          if (tc.fileOperation) {
            txt += `  File Op: ${tc.fileOperation.type}`;
            if (tc.fileOperation.path) txt += ` (${tc.fileOperation.path})`;
            txt += "\n";
          }
        }
        txt += "\n";
      }

      if (turn.fileOperations && turn.fileOperations.length > 0) {
        txt += `FILE OPERATIONS:\n`;
        for (const fo of turn.fileOperations) {
          txt += `- Op: ${fo.type}`;
          if (fo.path) txt += ` (${fo.path})`;
          txt += "\n";
        }
        txt += "\n";
      }

      if (turn.subagentHistory && turn.subagentHistory.length > 0) {
        txt += `SUBAGENT HISTORY:\n`;
        for (const sh of turn.subagentHistory) {
          txt += `- Turn [${sh.createdAt.toISOString()}]: ${sh.prompt.substring(0, 100)}...\n`;
        }
        txt += "\n";
      }

      if (turn.error) {
        txt += `ERROR: ${turn.error.message}\n`;
        if (turn.error.stack) {
          txt += `Stack:\n${turn.error.stack}\n`;
        }
        txt += "\n";
      }

      txt += `${"-".repeat(40)}\n\n`;
    }

    return txt;
  }
}

export default new ChatExportTool();
