// ============================================
// KEYWORD & TARGETING TOOLS
// ============================================

import type { ToolDefinition, GoogleAdsClient, ToolResult } from "../types.js";
import { microsToMoney, formatPercent, formatNumber } from "../types.js";
import type { GoogleAdsRestClient } from "../client.js";

// ---- Helpers ----

function resolveCustomerId(args: Record<string, any>, client: GoogleAdsClient): string {
  const cid = args.customer_id || client.getCustomerId();
  if (!cid) throw new Error("customer_id is required — pass it explicitly or set GOOGLE_ADS_CUSTOMER_ID.");
  return cid.replace(/-/g, "");
}

function textResult(text: string): ToolResult {
  return { content: [{ type: "text", text }] };
}

function errorResult(message: string): ToolResult {
  return { content: [{ type: "text", text: `❌ ${message}` }], isError: true };
}

function qualityScoreLabel(score: number | undefined): string {
  if (score == null) return "—";
  if (score >= 8) return `${score}/10 🟢`;
  if (score >= 5) return `${score}/10 🟡`;
  return `${score}/10 🔴`;
}

// ---- Tools ----

const tools: ToolDefinition[] = [
  // ============================
  // list_keywords
  // ============================
  {
    name: "list_keywords",
    description:
      "List keywords for an ad group with match type, quality score, status, bids, and performance metrics.",
    category: "keywords",
    annotations: {
      title: "List Keywords",
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description: "Google Ads customer ID (defaults to configured account).",
        },
        ad_group_id: {
          type: "string",
          description: "Ad group ID to list keywords for.",
        },
        status_filter: {
          type: "string",
          description: "Filter by keyword status.",
          enum: ["ENABLED", "PAUSED", "REMOVED"],
        },
        limit: {
          type: "number",
          description: "Maximum keywords to return (default: 100).",
        },
      },
      required: ["ad_group_id"],
    },
    _meta: {
      labels: {
        category: "keywords",
        access: "read",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);
      const limit = Math.min(args.limit || 100, 1000);

      let query = `
        SELECT
          ad_group_criterion.criterion_id,
          ad_group_criterion.keyword.text,
          ad_group_criterion.keyword.match_type,
          ad_group_criterion.status,
          ad_group_criterion.effective_cpc_bid_micros,
          ad_group_criterion.quality_info.quality_score,
          ad_group_criterion.quality_info.creative_quality_score,
          ad_group_criterion.quality_info.search_predicted_ctr,
          ad_group_criterion.quality_info.post_click_quality_score,
          ad_group_criterion.resource_name,
          ad_group.id,
          ad_group.name,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.ctr,
          metrics.average_cpc
        FROM ad_group_criterion
        WHERE ad_group.id = ${args.ad_group_id}
          AND ad_group_criterion.type = 'KEYWORD'
          AND segments.date DURING LAST_30_DAYS`;

      if (args.status_filter) {
        query += `\n          AND ad_group_criterion.status = '${args.status_filter}'`;
      }

      query += `\n        ORDER BY metrics.impressions DESC\n        LIMIT ${limit}`;

      const rows = await client.query(cid, query);

      if (!rows.length) {
        return textResult("No keywords found in this ad group.");
      }

      const lines = rows.map((r: any) => {
        const kw = r.adGroupCriterion || {};
        const keyword = kw.keyword || {};
        const qi = kw.qualityInfo || {};
        const m = r.metrics || {};

        const matchSymbol: Record<string, string> = {
          BROAD: "🅱️",
          PHRASE: "🅿️",
          EXACT: "🎯",
        };

        return [
          `${matchSymbol[keyword.matchType] || "❓"} **${keyword.text || "—"}** (${keyword.matchType || "—"})`,
          `  ID: ${kw.criterionId} | Status: ${kw.status || "—"} | Quality: ${qualityScoreLabel(qi.qualityScore)}`,
          `  CPC Bid: ${kw.effectiveCpcBidMicros ? "$" + microsToMoney(kw.effectiveCpcBidMicros) : "auto"}`,
          `  Predicted CTR: ${qi.searchPredictedCtr || "—"} | Ad Relevance: ${qi.creativeQualityScore || "—"} | Landing: ${qi.postClickQualityScore || "—"}`,
          `  Impressions: ${formatNumber(m.impressions || 0)} | Clicks: ${formatNumber(m.clicks || 0)} | CTR: ${formatPercent(m.ctr || 0)} | Cost: $${microsToMoney(m.costMicros || 0)} | Conv: ${formatNumber(m.conversions || 0)}`,
        ].join("\n");
      });

      return textResult(
        `## Keywords in Ad Group ${args.ad_group_id}\n\n${lines.join("\n\n")}\n\n_${rows.length} keyword(s) returned._`
      );
    },
  },

  // ============================
  // add_keywords
  // ============================
  {
    name: "add_keywords",
    description:
      "Add keywords to an ad group with specified match types. " +
      "⚠️ This adds live keywords that may trigger ad serving immediately. " +
      "Supports BROAD, PHRASE, and EXACT match types.",
    category: "keywords",
    annotations: {
      title: "Add Keywords",
      readOnlyHint: false,
      destructiveHint: true,
      idempotentHint: false,
      openWorldHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description: "Google Ads customer ID (defaults to configured account).",
        },
        ad_group_id: {
          type: "string",
          description: "Ad group ID to add keywords to.",
        },
        keywords: {
          type: "array",
          items: {
            type: "object",
            properties: {
              text: { type: "string", description: "Keyword text." },
              match_type: {
                type: "string",
                description: "Match type: BROAD, PHRASE, or EXACT.",
                enum: ["BROAD", "PHRASE", "EXACT"],
              },
            },
            required: ["text", "match_type"],
          },
          description: "Array of keywords to add, each with text and match_type.",
        },
        cpc_bid_amount: {
          type: "number",
          description: "Optional CPC bid override in dollars for all keywords. Uses ad group default if omitted.",
        },
        status: {
          type: "string",
          description: "Initial keyword status. Default: ENABLED.",
          enum: ["ENABLED", "PAUSED"],
        },
      },
      required: ["ad_group_id", "keywords"],
    },
    _meta: {
      labels: {
        category: "keywords",
        access: "delete",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);

      if (!args.keywords?.length) {
        return errorResult("At least one keyword is required.");
      }

      const validMatchTypes = new Set(["BROAD", "PHRASE", "EXACT"]);
      for (const kw of args.keywords) {
        if (!kw.text?.trim()) {
          return errorResult("Each keyword must have non-empty text.");
        }
        if (!validMatchTypes.has(kw.match_type)) {
          return errorResult(`Invalid match type "${kw.match_type}" for keyword "${kw.text}". Use BROAD, PHRASE, or EXACT.`);
        }
      }

      const operations = args.keywords.map((kw: { text: string; match_type: string }) => {
        const resource: Record<string, any> = {
          adGroup: `customers/${cid}/adGroups/${args.ad_group_id}`,
          status: args.status || "ENABLED",
          keyword: {
            text: kw.text.trim(),
            matchType: kw.match_type,
          },
        };

        if (args.cpc_bid_amount != null) {
          resource.cpcBidMicros = String(Math.round(args.cpc_bid_amount * 1_000_000));
        }

        return {
          entity: "adGroupCriterion",
          operation: "create" as const,
          resource,
        };
      });

      const result = await client.mutate(cid, operations);

      const kwSummary = args.keywords.map((kw: any) => `  - "${kw.text}" (${kw.match_type})`).join("\n");

      return textResult(
        `✅ Added ${args.keywords.length} keyword(s) to ad group ${args.ad_group_id}:\n\n${kwSummary}\n\n` +
          `- CPC Bid: ${args.cpc_bid_amount != null ? "$" + args.cpc_bid_amount.toFixed(2) : "ad group default"}\n` +
          `- Status: ${args.status || "ENABLED"}`
      );
    },
  },

  // ============================
  // remove_keywords
  // ============================
  {
    name: "remove_keywords",
    description:
      "Remove keywords from an ad group by their criterion IDs. " +
      "⚠️ This permanently removes keywords — they cannot be recovered. Consider pausing instead if unsure.",
    category: "keywords",
    annotations: {
      title: "Remove Keywords",
      readOnlyHint: false,
      destructiveHint: true,
      idempotentHint: true,
      openWorldHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description: "Google Ads customer ID (defaults to configured account).",
        },
        ad_group_id: {
          type: "string",
          description: "Ad group ID the keywords belong to.",
        },
        criterion_ids: {
          type: "array",
          items: { type: "string" },
          description: "Array of keyword criterion IDs to remove.",
        },
      },
      required: ["ad_group_id", "criterion_ids"],
    },
    _meta: {
      labels: {
        category: "keywords",
        access: "delete",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);

      if (!args.criterion_ids?.length) {
        return errorResult("At least one criterion_id is required.");
      }

      const operations = args.criterion_ids.map((criterionId: string) => ({
        entity: "adGroupCriterion",
        operation: "remove" as const,
        resourceName: `customers/${cid}/adGroupCriteria/${args.ad_group_id}~${criterionId}`,
      }));

      await client.mutate(cid, operations);

      return textResult(
        `🗑️ Removed ${args.criterion_ids.length} keyword(s) from ad group ${args.ad_group_id}.\n\n` +
          `Criterion IDs: ${args.criterion_ids.join(", ")}`
      );
    },
  },

  // ============================
  // get_keyword_ideas
  // ============================
  {
    name: "get_keyword_ideas",
    description:
      "Generate keyword suggestions from seed keywords or a URL. Returns search volume, competition, and CPC estimates. " +
      "Useful for keyword research and discovering new opportunities.",
    category: "keywords",
    annotations: {
      title: "Get Keyword Ideas",
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description: "Google Ads customer ID (defaults to configured account).",
        },
        seed_keywords: {
          type: "array",
          items: { type: "string" },
          description: "Seed keywords to base suggestions on.",
        },
        seed_url: {
          type: "string",
          description: "URL to scan for keyword ideas.",
        },
        language: {
          type: "string",
          description: "Language constant (e.g. 'languageConstants/1000' for English). Default: English.",
        },
        geo_targets: {
          type: "array",
          items: { type: "string" },
          description: "Geo target constants (e.g. ['geoTargetConstants/2840'] for US). Default: US.",
        },
        limit: {
          type: "number",
          description: "Maximum number of ideas to return (default: 20).",
        },
      },
    },
    _meta: {
      labels: {
        category: "keywords",
        access: "read",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);

      if (!args.seed_keywords?.length && !args.seed_url) {
        return errorResult("Provide at least seed_keywords or seed_url for keyword ideas.");
      }

      // Cast to GoogleAdsRestClient to access generateKeywordIdeas
      const restClient = client as GoogleAdsRestClient;
      const results = await restClient.generateKeywordIdeas(cid, {
        seedKeywords: args.seed_keywords,
        seedUrl: args.seed_url,
        language: args.language,
        geoTargets: args.geo_targets,
      });

      const limit = Math.min(args.limit || 20, 100);
      const ideas = results.slice(0, limit);

      if (!ideas.length) {
        return textResult("No keyword ideas found for the given seeds. Try different keywords or a more specific URL.");
      }

      const lines = ideas.map((idea: any, i: number) => {
        const kw = idea.text || idea.keywordIdeaMetrics?.text || "—";
        const m = idea.keywordIdeaMetrics || {};
        const avgSearch = m.avgMonthlySearches != null ? formatNumber(m.avgMonthlySearches) : "—";
        const competition = m.competition || "—";
        const lowCpc = m.lowTopOfPageBidMicros ? "$" + microsToMoney(m.lowTopOfPageBidMicros) : "—";
        const highCpc = m.highTopOfPageBidMicros ? "$" + microsToMoney(m.highTopOfPageBidMicros) : "—";

        return `${i + 1}. **${kw}** — ${avgSearch} avg searches/mo | Competition: ${competition} | CPC: ${lowCpc} – ${highCpc}`;
      });

      const seeds = args.seed_keywords?.length
        ? `Seeds: ${args.seed_keywords.join(", ")}`
        : `URL: ${args.seed_url}`;

      return textResult(
        `## Keyword Ideas\n${seeds}\n\n${lines.join("\n")}\n\n_${ideas.length} idea(s) shown._`
      );
    },
  },

  // ============================
  // get_keyword_metrics
  // ============================
  {
    name: "get_keyword_metrics",
    description:
      "Get search volume, competition level, and CPC estimates for specific keywords. " +
      "Useful for evaluating keywords before adding them to campaigns.",
    category: "keywords",
    annotations: {
      title: "Get Keyword Metrics",
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description: "Google Ads customer ID (defaults to configured account).",
        },
        keywords: {
          type: "array",
          items: { type: "string" },
          description: "Keywords to get metrics for.",
        },
        language: {
          type: "string",
          description: "Language constant. Default: English (languageConstants/1000).",
        },
        geo_targets: {
          type: "array",
          items: { type: "string" },
          description: "Geo target constants. Default: US (geoTargetConstants/2840).",
        },
      },
      required: ["keywords"],
    },
    _meta: {
      labels: {
        category: "keywords",
        access: "read",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);

      if (!args.keywords?.length) {
        return errorResult("At least one keyword is required.");
      }

      // Use generateKeywordIdeas with the exact keywords as seeds
      const restClient = client as GoogleAdsRestClient;
      const results = await restClient.generateKeywordIdeas(cid, {
        seedKeywords: args.keywords,
        language: args.language,
        geoTargets: args.geo_targets,
      });

      // Filter results to only the requested keywords (case-insensitive match)
      const requested = new Set(args.keywords.map((k: string) => k.toLowerCase()));
      const matched = results.filter((idea: any) => {
        const text = (idea.text || "").toLowerCase();
        return requested.has(text);
      });

      // If exact matches aren't found, show top results as approximations
      const display = matched.length ? matched : results.slice(0, args.keywords.length);

      if (!display.length) {
        return textResult("No metrics available for the given keywords.");
      }

      const header = `| Keyword | Avg Searches/Mo | Competition | Low CPC | High CPC |`;
      const divider = `|---------|-----------------|-------------|---------|----------|`;

      const rows = display.map((idea: any) => {
        const kw = idea.text || "—";
        const m = idea.keywordIdeaMetrics || {};
        const avgSearch = m.avgMonthlySearches != null ? formatNumber(m.avgMonthlySearches) : "—";
        const competition = m.competition || "—";
        const lowCpc = m.lowTopOfPageBidMicros ? "$" + microsToMoney(m.lowTopOfPageBidMicros) : "—";
        const highCpc = m.highTopOfPageBidMicros ? "$" + microsToMoney(m.highTopOfPageBidMicros) : "—";
        return `| ${kw} | ${avgSearch} | ${competition} | ${lowCpc} | ${highCpc} |`;
      });

      return textResult(
        `## Keyword Metrics\n\n${header}\n${divider}\n${rows.join("\n")}\n\n_${display.length} keyword(s)._`
      );
    },
  },

  // ============================
  // list_negative_keywords
  // ============================
  {
    name: "list_negative_keywords",
    description:
      "List negative keywords at both campaign and ad group level. Shows match type and scope.",
    category: "keywords",
    annotations: {
      title: "List Negative Keywords",
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description: "Google Ads customer ID (defaults to configured account).",
        },
        campaign_id: {
          type: "string",
          description: "Campaign ID to list negatives for. If omitted, lists all.",
        },
        ad_group_id: {
          type: "string",
          description: "Ad group ID to list negatives for (ad group level only).",
        },
        limit: {
          type: "number",
          description: "Maximum negatives to return (default: 200).",
        },
      },
    },
    _meta: {
      labels: {
        category: "keywords",
        access: "read",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);
      const limit = Math.min(args.limit || 200, 1000);
      const sections: string[] = ["## Negative Keywords\n"];

      // Campaign-level negatives
      if (!args.ad_group_id) {
        let campaignQuery = `
          SELECT
            campaign_criterion.criterion_id,
            campaign_criterion.keyword.text,
            campaign_criterion.keyword.match_type,
            campaign_criterion.negative,
            campaign.id,
            campaign.name
          FROM campaign_criterion
          WHERE campaign_criterion.type = 'KEYWORD'
            AND campaign_criterion.negative = TRUE`;

        if (args.campaign_id) {
          campaignQuery += `\n            AND campaign.id = ${args.campaign_id}`;
        }

        campaignQuery += `\n          LIMIT ${limit}`;

        const campaignRows = await client.query(cid, campaignQuery);

        if (campaignRows.length) {
          sections.push(`### Campaign Level (${campaignRows.length})\n`);
          campaignRows.forEach((r: any) => {
            const cc = r.campaignCriterion || {};
            const kw = cc.keyword || {};
            const camp = r.campaign || {};
            sections.push(`- **${kw.text || "—"}** (${kw.matchType || "—"}) — Campaign: ${camp.name || camp.id || "—"} [ID: ${cc.criterionId}]`);
          });
          sections.push("");
        } else {
          sections.push("_No campaign-level negative keywords found._\n");
        }
      }

      // Ad group-level negatives
      {
        let agQuery = `
          SELECT
            ad_group_criterion.criterion_id,
            ad_group_criterion.keyword.text,
            ad_group_criterion.keyword.match_type,
            ad_group_criterion.negative,
            ad_group.id,
            ad_group.name,
            campaign.id,
            campaign.name
          FROM ad_group_criterion
          WHERE ad_group_criterion.type = 'KEYWORD'
            AND ad_group_criterion.negative = TRUE`;

        if (args.ad_group_id) {
          agQuery += `\n            AND ad_group.id = ${args.ad_group_id}`;
        } else if (args.campaign_id) {
          agQuery += `\n            AND campaign.id = ${args.campaign_id}`;
        }

        agQuery += `\n          LIMIT ${limit}`;

        const agRows = await client.query(cid, agQuery);

        if (agRows.length) {
          sections.push(`### Ad Group Level (${agRows.length})\n`);
          agRows.forEach((r: any) => {
            const agc = r.adGroupCriterion || {};
            const kw = agc.keyword || {};
            const ag = r.adGroup || {};
            sections.push(`- **${kw.text || "—"}** (${kw.matchType || "—"}) — Ad Group: ${ag.name || ag.id || "—"} [ID: ${agc.criterionId}]`);
          });
        } else {
          sections.push("_No ad group-level negative keywords found._");
        }
      }

      return textResult(sections.join("\n"));
    },
  },
];

export default tools;
