// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0

import { IAiLogger } from "../../api.ts";
import { formatError } from "../tool-error.ts";
import { AiTool, IToolArguments, IToolDefinition } from "../tool.ts";

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

export interface ISearchResult {
  title: string;
  link: string;
  snippet: string;
  image?: string;
  position?: number;
  displayLink?: string;
}

export interface ISearchOptions {
  num?: number;
  siteSearch?: string;
  dateRestrict?: string;
  fileType?: string;
  safe?: "active" | "off";
  sort?: "relevance" | "date";
  start?: number;
}

export class GoogleSearchTool extends AiTool {
  get name(): string {
    return "search_google";
  }

  get category(): string {
    return "search";
  }

  public definition: IToolDefinition = {
    type: "function",
    function: {
      name: this.name,
      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 async execute(
    args: IToolArguments,
    logger: IAiLogger,
  ): Promise<string> {
    const { query } = args;

    if (!query || typeof query !== "string" || query.trim().length === 0) {
      return formatError({
        code: "MISSING_PARAMETER",
        message: "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.",
      });
    }

    logger.debug("performing Google search for user", { args });

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

      const results = await this.search(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,
      });

      logger.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.";
      }

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

  async search(
    query: string,
    options: ISearchOptions,
  ): Promise<ISearchResult[]> {
    const apiKey = this.toolbox.env.services?.google?.cse?.apiKey;
    const engineId = this.toolbox.env.services?.google?.cse?.engineId;

    if (!apiKey || !engineId) {
      throw new Error(
        "Google CSE credentials not configured in environment",
      );
    }

    const customSearch = google.customsearch({
      version: "v1",
      auth: apiKey,
    });

    const params: any = {
      q: query,
      cx: engineId,
      num: options.num || 10,
    };

    if (options.siteSearch) {
      params.siteSearch = options.siteSearch;
    }
    if (options.dateRestrict) {
      params.dateRestrict = options.dateRestrict;
    }
    if (options.fileType) {
      params.fileType = options.fileType;
    }
    if (options.safe) {
      params.safe = options.safe;
    }
    if (options.sort) {
      params.sort = options.sort;
    }
    if (options.start) {
      params.start = options.start;
    }

    const response = await customSearch.cse.list(params);
    const results: ISearchResult[] = [];

    if (response.data.items) {
      response.data.items.forEach((item: any, index: number) => {
        const result: ISearchResult = {
          title: item.title || "",
          link: item.link || "",
          snippet: item.snippet || "",
          position: item.position || index + 1,
          displayLink: item.displayLink || "",
        };

        // Extract thumbnail if available
        if (item.pagemap?.cse_thumbnail?.[0]?.src) {
          result.image = item.pagemap.cse_thumbnail[0].src;
        }

        results.push(result);
      });
    }

    return results;
  }

  /**
   * Parse Google CSE API errors and provide meaningful messages
   */
  private parseCseError(error: any): string {
    // Handle Google API error structure
    if (error.response?.data?.error) {
      const apiError = error.response.data.error;
      const statusCode = error.response.status;

        switch (statusCode) {
        case 401:
          return formatError({
            code: "UNAUTHORIZED",
            message: `401 Unauthorized - Invalid API key. ${apiError.message || ""}`,
          });
        case 403:
          return formatError({
            code: "FORBIDDEN",
            message: `403 Forbidden - ${apiError.message || "Access denied"}. Check your Engine ID and API key permissions.`,
          });
        case 429:
          const retryAfter = error.response.headers?.["retry-after"];
          return formatError({
            code: "RATE_LIMIT_EXCEEDED",
            message: `429 Too Many Requests - Rate limit exceeded. ${retryAfter ? `Retry after: ${retryAfter} seconds.` : ""} ${apiError.message || ""}`,
          });
        default:
          break;
      }

      return formatError({
        code: "TOOL_EXECUTION_FAILED",
        message: `HTTP ${statusCode} - ${apiError.message || "Unknown error"}`,
      });
    }

    // Handle network errors
    if (error.code === "ENOTFOUND") {
      return formatError({
        code: "NETWORK_ERROR",
        message: `Network error (code: ${error.code}) - Unable to reach Google API`,
      });
    }

    // Generic error
    return formatError({
      code: error.code || "UNKNOWN_ERROR",
      message: error.message || "An unknown error occurred",
    });
  }
}
