// src/tools/search/google-search.ts
// Copyright (C) 2025 DTP Technologies, LLC
// All Rights Reserved

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

import SearchService, { SearchServiceError } from "../../services/search.js";

import type {
  ToolArguments,
  ToolContext,
  ToolMetadata,
} from "../../lib/tool.js";
import { DtpTool } from "../../lib/tool.js";
import { ChatSessionMode } from "@/models/chat-session.js";
import HostMonitorService from "../../services/host-monitor.js";

class GoogleSearchTool extends DtpTool {
  get name(): string {
    return "GoogleSearchTool";
  }
  get slug(): string {
    return "google-search";
  }
  get metadata(): ToolMetadata {
    return {
      name: this.definition.function.name || "search_google",
      category: "search",
      tags: ["web", "external", "google"],
      modes: [
        ChatSessionMode.Plan,
        ChatSessionMode.Build,
        ChatSessionMode.Test,
        ChatSessionMode.Ship,
        ChatSessionMode.Develop,
      ],
    };
  }

  public definition: ToolDefinition = {
    type: "function",
    function: {
      name: "search_google",
      description:
        "Perform a Google search for relevant information on the web.",
      parameters: {
        type: "object",
        properties: {
          query: {
            type: "string",
            description: "The search query string.",
          },
          num_results: {
            type: "number",
            description:
              "Number of search results to return (default: 10, max: 10).",
          },
          siteSearch: {
            type: "string",
            description:
              "Optional site to restrict the search to (e.g. github.com).",
          },
          dateRestrict: {
            type: "string",
            description:
              "Restricts results to documents based on a date range. Examples: d1 (last day), d7 (last week), d30 (last month), d365 (last year).",
          },
          fileType: {
            type: "string",
            description:
              "Restricts results to files of a specified extension. Examples: pdf, doc, xls, ppt.",
          },
          sort: {
            type: "string",
            description:
              "Sort order for results. Values: 'relevance' (default) or 'date'.",
            enum: ["relevance", "date"],
          },
          start: {
            type: "number",
            description:
              "The index of the first result to return (for pagination). Default: 1.",
          },
        },
        required: ["query"],
      },
    },
  };

  public requiresUserConfig(): boolean {
    return true;
  }

  public async checkUserConfig(userId: string): Promise<boolean> {
    return await SearchService.userHasCseConfigured(userId);
  }

  public async execute(
    context: ToolContext,
    args: ToolArguments,
  ): Promise<string> {
    const { query } = args;

    if (!query || typeof query !== "string" || query.trim().length === 0) {
      return this.error(
        "MISSING_PARAMETER",
        "The 'query' parameter is required.",
        {
          parameter: "query",
          expected: "A non-empty string containing the search query.",
          example: 'search_google(query: "latest AI news")',
          recoveryHint:
            "Provide a 'query' parameter with your search terms and try again.",
        },
      );
    }

    // Get user ID from context
    const userId = context.session.user._id.toString();

    this.log.debug("performing Google search for user", { userId, args });

    try {
      const {
        num_results = 10,
        siteSearch,
        dateRestrict,
        fileType,
        sort,
        start,
      } = args;

      const results = await SearchService.searchForUser(userId, query, {
        num: Math.min(num_results as number, 10),
        siteSearch: siteSearch as string | undefined,
        dateRestrict: dateRestrict as string | undefined,
        fileType: fileType as string | undefined,
        safe: "active",
        sort: sort as "relevance" | "date" | undefined,
        start: start as number | undefined,
      });

      this.log.debug("Google search results", { results });

      let content = "";
      if (results && results.length) {
        content += `Here are some relevant search results I found:\n\n`;
        for (const result of results) {
          const title = JSON.stringify(result.title || "").slice(1, -1);
          const link = JSON.stringify(result.link || "").slice(1, -1);
          const snippet = JSON.stringify(result.snippet || "").slice(1, -1);
          const displayLink = result.displayLink
            ? JSON.stringify(result.displayLink).slice(1, -1)
            : "";

          content += `Title: ${title}\n`;
          content += `Link: ${link}\n`;
          if (displayLink) {
            content += `Source: ${displayLink}\n`;
          }
          content += `Snippet: ${snippet}\n\n`;
        }
      } else {
        content += "No relevant search results found.";
      }

      const byteCount = Buffer.byteLength(content, "utf-8");
      HostMonitorService.toolCall(byteCount);
      return this.success(
        { query, resultCount: results?.length ?? 0 },
        content,
      );
    } catch (error: any) {
      // Handle SearchServiceError with detailed error information
      if (error instanceof SearchServiceError) {
        const errorDetails = error.details
          ? `\nDetails: ${JSON.stringify(error.details, null, 2)}`
          : "";

        // Map SearchServiceError codes to ToolErrorCode
        let toolErrorCode: import("../../lib/tool-error.js").ToolErrorCode =
          "OPERATION_FAILED";
        let recoveryHint: string | undefined;

        switch (error.code) {
          case "CSE_NOT_CONFIGURED":
            toolErrorCode = "PERMISSION_DENIED";
            recoveryHint =
              "Press Ctrl+, to open Account Settings and configure your Google CSE credentials.";
            break;
          case "UNAUTHORIZED":
          case "FORBIDDEN":
            toolErrorCode = "PERMISSION_DENIED";
            recoveryHint =
              "Your Google CSE credentials may be invalid. Press Ctrl+, to update them in Account Settings.";
            break;
          case "RATE_LIMIT_EXCEEDED":
            toolErrorCode = "RATE_LIMITED";
            break;
          case "NETWORK_ERROR":
            toolErrorCode = "OPERATION_FAILED";
            break;
          default:
            toolErrorCode = "OPERATION_FAILED";
        }

        return this.error(toolErrorCode, `${error.message}${errorDetails}`, {
          recoveryHint,
        });
      }

      // Generic error handling
      return this.error(
        "OPERATION_FAILED",
        `Failed to perform search: ${error.message}`,
        {
          recoveryHint: "Please try again or check your search query.",
        },
      );
    }
  }
}

export default new GoogleSearchTool();
