// src/tools/file/edit.test.ts
// Copyright (C) 2025 DTP Technologies, LLC
// All Rights Reserved

import { describe, it, expect, beforeEach, afterEach } from "vitest";
import fs from "node:fs/promises";
import path from "node:path";
import { FileEditTool } from "./edit.js";
import { PROJECT_ROOT } from "../../config/env.js";

describe("FileEditTool", () => {
  let tool: FileEditTool;
  let mockSession: any;
  const testFilePath = "test-edit-file.txt";
  const testFileContent = "Hello World\nThis is a test file\nGoodbye World\n";

  beforeEach(async () => {
    tool = new FileEditTool();
    mockSession = {
      _id: "test-session-id",
      user: "test-user-id",
    };
    // Create test file
    await fs.writeFile(
      path.join(PROJECT_ROOT, testFilePath),
      testFileContent,
      "utf-8",
    );
  });

  afterEach(async () => {
    // Clean up test file
    try {
      await fs.unlink(path.join(PROJECT_ROOT, testFilePath));
    } catch {
      // Ignore if file doesn't exist
    }
  });

  describe("definition", () => {
    it("should have correct tool name", () => {
      expect(tool.definition.function.name).toBe("file_edit");
    });

    it("should have correct definition structure for AI clients", () => {
      const def = tool.definition;
      expect(def.type).toBe("function");
      expect(def.function).toBeDefined();
      expect(def.function.name).toBe("file_edit");
      expect(def.function.description).toBeDefined();
      expect(def.function.parameters).toBeDefined();
      const params = def.function.parameters as any;
      expect(params.type).toBe("object");
      expect(params.properties).toBeDefined();
      expect(params.properties.path).toBeDefined();
      expect(params.properties.search).toBeDefined();
      expect(params.properties.replace).toBeDefined();
      expect(params.required).toContain("path");
      expect(params.required).toContain("search");
      expect(params.required).toContain("replace");
    });
  });

  describe("execute", () => {
    it("should return error for missing path", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: "", search: "test", replace: "test" },
      );

      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("File path must not be empty");
    });

    it("should return error for missing search string", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "", replace: "test" },
      );

      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("Search string must not be empty");
    });

    it("should return error for undefined replace string", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "test", replace: undefined as any },
      );

      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("Replace string must not be undefined");
    });

    it("should return error for non-existent file", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: "non/existent/file.txt", search: "test", replace: "test" },
      );

      expect(result).toContain("NOT_FOUND");
      expect(result).toContain("File not found");
    });

    it("should return error when search string not found", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "NotFound", replace: "test" },
      );

      expect(result).toContain("NOT_FOUND");
      expect(result).toContain("Search string not found");
    });

    it("should show file content context when search not found", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "NotFound", replace: "test" },
      );

      // Should show file content context
      expect(result).toContain("File content");
      expect(result).toContain("line");
    });

    it("should successfully edit a file and return plain text response", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "World", replace: "Universe" },
      );

      // Verify plain text format with header
      expect(result).toContain("PATH:");
      expect(result).toContain("FILE OPERATION: edit");
      expect(result).toContain("SEARCH FOUND: true");
      expect(result).toContain("---");

      // Verify the file was actually edited
      const fileContent = await fs.readFile(
        path.join(PROJECT_ROOT, testFilePath),
        "utf-8",
      );
      expect(fileContent).toContain("Hello Universe");
      expect(fileContent).not.toContain("Hello World");
    });

    it("should include diff context in the response", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "World", replace: "Universe" },
      );

      // Response should contain diff context
      expect(result).toContain("Changed line");
      expect(result).toContain("Removed");
      expect(result).toContain("Added");
    });

    it("should show context before and after the change", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "test", replace: "sample" },
      );

      // Response should contain context
      expect(result).toContain("Context");
    });

    it("should handle multi-line search and replace", async () => {
      const multiLineContent = "Line 1\nLine 2\nLine 3\nLine 4\n";
      await fs.writeFile(
        path.join(PROJECT_ROOT, testFilePath),
        multiLineContent,
        "utf-8",
      );

      const result = await tool.execute(
        { session: mockSession },
        {
          path: testFilePath,
          search: "Line 2\nLine 3",
          replace: "Replacement",
        },
      );

      expect(result).toContain("Changed lines");
      expect(result).toContain("Search spanned 2 lines");

      // Verify the file was edited correctly
      const fileContent = await fs.readFile(
        path.join(PROJECT_ROOT, testFilePath),
        "utf-8",
      );
      expect(fileContent).toBe("Line 1\nReplacement\nLine 4\n");
    });

    it("should show all affected lines for multi-line changes", async () => {
      const multiLineContent = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n";
      await fs.writeFile(
        path.join(PROJECT_ROOT, testFilePath),
        multiLineContent,
        "utf-8",
      );

      const result = await tool.execute(
        { session: mockSession },
        {
          path: testFilePath,
          search: "Line 2\nLine 3\nLine 4",
          replace: "New Line 2\nNew Line 3",
        },
      );

      // Should show all changed lines
      expect(result).toContain("Changed lines 2-4");
      expect(result).toContain("Search spanned 3 lines");
      expect(result).toContain("Line 2");
      expect(result).toContain("Line 3");
      expect(result).toContain("Line 4");
    });

    it("should handle multi-line replacement with different line count", async () => {
      const content = "Start\nMiddle\nEnd\n";
      await fs.writeFile(
        path.join(PROJECT_ROOT, testFilePath),
        content,
        "utf-8",
      );

      const result = await tool.execute(
        { session: mockSession },
        {
          path: testFilePath,
          search: "Middle",
          replace: "New Middle 1\nNew Middle 2\nNew Middle 3",
        },
      );

      expect(result).toContain("Changed line");

      // Verify the file was edited correctly
      const fileContent = await fs.readFile(
        path.join(PROJECT_ROOT, testFilePath),
        "utf-8",
      );
      expect(fileContent).toBe(
        "Start\nNew Middle 1\nNew Middle 2\nNew Middle 3\nEnd\n",
      );
    });
  });

  describe("response format for AI clients", () => {
    it("should return a string that can be used in message content", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "World", replace: "Universe" },
      );

      expect(typeof result).toBe("string");
      expect(result.length).toBeGreaterThan(0);
    });

    it("should return plain text with header metadata", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "World", replace: "Universe" },
      );

      // Verify plain text format with header
      expect(result).toMatch(/^PATH:/m);
      expect(result).toContain("FILE OPERATION: edit");
      expect(result).toContain("SEARCH FOUND: true");
      expect(result).toContain("---");
    });

    it("should not return JSON format", async () => {
      const result = await tool.execute(
        { session: mockSession },
        { path: testFilePath, search: "World", replace: "Universe" },
      );

      // Should not contain JSON structure
      expect(result).not.toMatch(/^\{.*"success".*\}$/s);
      expect(result).not.toMatch(/^\{.*"data".*\}$/s);
    });
  });
});
