// ============================================
// ADVANCED / UTILITY TOOLS
// ============================================

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

// ============================================
// HELPERS
// ============================================

/** Mutation keywords that should never appear in a read-only GAQL query */
const MUTATION_KEYWORDS = /\b(INSERT|UPDATE|DELETE|CREATE|REMOVE)\b/i;

/** Flatten a nested API response row into dot-notation key-value pairs */
function flattenRow(
  obj: Record<string, any>,
  prefix = ""
): Record<string, string> {
  const flat: Record<string, string> = {};
  for (const [key, value] of Object.entries(obj)) {
    const path = prefix ? `${prefix}.${key}` : key;
    if (value && typeof value === "object" && !Array.isArray(value)) {
      Object.assign(flat, flattenRow(value, path));
    } else if (Array.isArray(value)) {
      flat[path] = value.map((v) => (typeof v === "object" ? JSON.stringify(v) : String(v))).join("; ");
    } else {
      flat[path] = value == null ? "" : String(value);
    }
  }
  return flat;
}

/** Convert a value that looks like micros to a formatted dollar string */
function autoFormatValue(key: string, value: string): string {
  const lk = key.toLowerCase();
  if (lk.includes("micros") && /^\d+$/.test(value)) {
    return "$" + microsToMoney(value);
  }
  if ((lk.includes("ctr") || lk.includes("rate")) && !isNaN(Number(value))) {
    return formatPercent(Number(value));
  }
  return value;
}

// ============================================
// ENTITY DEFINITIONS FOR export_report
// ============================================

interface EntityConfig {
  resource: string;
  fields: string[];
  metricsFields: string[];
  filterField?: string;
}

const ENTITY_CONFIGS: Record<string, EntityConfig> = {
  campaigns: {
    resource: "campaign",
    fields: [
      "campaign.id",
      "campaign.name",
      "campaign.status",
      "campaign.advertising_channel_type",
      "campaign.bidding_strategy_type",
    ],
    metricsFields: [
      "metrics.impressions",
      "metrics.clicks",
      "metrics.cost_micros",
      "metrics.conversions",
      "metrics.conversions_value",
      "metrics.ctr",
      "metrics.average_cpc",
    ],
    filterField: "campaign.status",
  },
  ad_groups: {
    resource: "ad_group",
    fields: [
      "ad_group.id",
      "ad_group.name",
      "ad_group.status",
      "ad_group.type",
      "campaign.id",
      "campaign.name",
    ],
    metricsFields: [
      "metrics.impressions",
      "metrics.clicks",
      "metrics.cost_micros",
      "metrics.conversions",
      "metrics.ctr",
      "metrics.average_cpc",
    ],
    filterField: "ad_group.status",
  },
  keywords: {
    resource: "keyword_view",
    fields: [
      "ad_group_criterion.criterion_id",
      "ad_group_criterion.keyword.text",
      "ad_group_criterion.keyword.match_type",
      "ad_group_criterion.status",
      "ad_group_criterion.quality_info.quality_score",
      "ad_group.name",
      "campaign.name",
    ],
    metricsFields: [
      "metrics.impressions",
      "metrics.clicks",
      "metrics.cost_micros",
      "metrics.conversions",
      "metrics.ctr",
      "metrics.average_cpc",
    ],
    filterField: "ad_group_criterion.status",
  },
  ads: {
    resource: "ad_group_ad",
    fields: [
      "ad_group_ad.ad.id",
      "ad_group_ad.ad.type",
      "ad_group_ad.ad.final_urls",
      "ad_group_ad.status",
      "ad_group.name",
      "campaign.name",
    ],
    metricsFields: [
      "metrics.impressions",
      "metrics.clicks",
      "metrics.cost_micros",
      "metrics.conversions",
      "metrics.ctr",
    ],
    filterField: "ad_group_ad.status",
  },
};

// ============================================
// TOOL HANDLERS
// ============================================

// ---- run_gaql_query ----

async function runGaqlQuery(
  args: Record<string, any>,
  client: GoogleAdsClient
): Promise<ToolResult> {
  const customerId = args.customer_id || client.getCustomerId();
  if (!customerId) {
    return {
      content: [{ type: "text", text: "Error: customer_id is required." }],
      isError: true,
    };
  }

  const query: string = (args.query || "").trim();
  if (!query) {
    return {
      content: [{ type: "text", text: "Error: query is required." }],
      isError: true,
    };
  }

  // Safety: block mutation keywords
  if (MUTATION_KEYWORDS.test(query)) {
    return {
      content: [
        {
          type: "text",
          text: "Error: This tool only supports read-only GAQL queries. Mutation keywords (INSERT, UPDATE, DELETE, CREATE, REMOVE) are not allowed. Use the appropriate management tools for mutations.",
        },
      ],
      isError: true,
    };
  }

  // Validate it starts with SELECT
  if (!/^\s*SELECT\b/i.test(query)) {
    return {
      content: [
        {
          type: "text",
          text: "Error: Query must start with SELECT. This tool only supports GAQL SELECT queries.",
        },
      ],
      isError: true,
    };
  }

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

  if (!rows.length) {
    return {
      content: [{ type: "text", text: `Query returned 0 results.\n\n\`\`\`sql\n${query}\n\`\`\`` }],
    };
  }

  // Flatten rows and auto-format values
  const flatRows = rows.map((row) => flattenRow(row));
  const allKeys = [...new Set(flatRows.flatMap((r) => Object.keys(r)))];

  // Format as a readable table (markdown)
  const formatted = flatRows.map((row, i) => {
    const entries = allKeys
      .filter((k) => row[k] !== undefined && row[k] !== "")
      .map((k) => `  ${k}: ${autoFormatValue(k, row[k])}`)
      .join("\n");
    return `**Row ${i + 1}:**\n${entries}`;
  });

  // Truncate if too many rows
  const MAX_DISPLAY = 100;
  const truncated = rows.length > MAX_DISPLAY;
  const displayRows = formatted.slice(0, MAX_DISPLAY);

  return {
    content: [
      {
        type: "text",
        text: [
          `## GAQL Query Results`,
          `**Rows:** ${rows.length}${truncated ? ` (showing first ${MAX_DISPLAY})` : ""}`,
          `**Fields:** ${allKeys.join(", ")}`,
          ``,
          `\`\`\`sql\n${query}\n\`\`\``,
          ``,
          displayRows.join("\n\n"),
          truncated ? `\n\n> ⚠️ Results truncated. ${rows.length - MAX_DISPLAY} additional rows not shown.` : "",
        ]
          .filter(Boolean)
          .join("\n"),
      },
    ],
  };
}

// ---- export_report ----

async function exportReport(
  args: Record<string, any>,
  client: GoogleAdsClient
): Promise<ToolResult> {
  const customerId = args.customer_id || client.getCustomerId();
  if (!customerId) {
    return {
      content: [{ type: "text", text: "Error: customer_id is required." }],
      isError: true,
    };
  }

  const entity = args.entity as string;
  const format = (args.format as string) || "csv";
  const dateRange = args.date_range || "LAST_30_DAYS";

  const config = ENTITY_CONFIGS[entity];
  if (!config) {
    return {
      content: [
        {
          type: "text",
          text: `Error: Unknown entity "${entity}". Valid options: ${Object.keys(ENTITY_CONFIGS).join(", ")}`,
        },
      ],
      isError: true,
    };
  }

  // Build GAQL dynamically
  const allFields = [...config.fields, ...config.metricsFields];
  const conditions: string[] = [`segments.date DURING ${dateRange}`];

  // Apply optional status filter
  if (args.status && config.filterField) {
    conditions.push(`${config.filterField} = '${args.status}'`);
  }

  // Apply minimum impressions filter for cleaner reports
  if (args.min_impressions) {
    conditions.push(`metrics.impressions >= ${args.min_impressions}`);
  }

  // Apply campaign filter for ad_groups, keywords, ads
  if (args.campaign_id && entity !== "campaigns") {
    conditions.push(`campaign.id = ${args.campaign_id}`);
  }

  const query = `
    SELECT ${allFields.join(", ")}
    FROM ${config.resource}
    WHERE ${conditions.join(" AND ")}
    ORDER BY metrics.cost_micros DESC
    LIMIT ${args.limit || 1000}
  `;

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

  if (!rows.length) {
    return {
      content: [
        {
          type: "text",
          text: `No data found for ${entity} in ${dateRange}.`,
        },
      ],
    };
  }

  // Flatten all rows
  const flatRows = rows.map((row) => flattenRow(row));
  const headers = [...new Set(flatRows.flatMap((r) => Object.keys(r)))];

  if (format === "csv") {
    // Escape CSV values
    const escapeCSV = (val: string): string => {
      if (val.includes(",") || val.includes('"') || val.includes("\n")) {
        return `"${val.replace(/"/g, '""')}"`;
      }
      return val;
    };

    const csvLines = [
      headers.map(escapeCSV).join(","),
      ...flatRows.map((row) =>
        headers.map((h) => escapeCSV(autoFormatValue(h, row[h] || ""))).join(",")
      ),
    ];

    return {
      content: [
        {
          type: "text",
          text: [
            `## ${entity.replace("_", " ").toUpperCase()} Report (CSV)`,
            `📅 ${dateRange} | ${rows.length} rows`,
            ``,
            "```csv",
            csvLines.join("\n"),
            "```",
          ].join("\n"),
        },
      ],
    };
  } else {
    // JSON format
    const jsonRows = flatRows.map((row) => {
      const obj: Record<string, string> = {};
      for (const h of headers) {
        obj[h] = autoFormatValue(h, row[h] || "");
      }
      return obj;
    });

    return {
      content: [
        {
          type: "text",
          text: [
            `## ${entity.replace("_", " ").toUpperCase()} Report (JSON)`,
            `📅 ${dateRange} | ${rows.length} rows`,
            ``,
            "```json",
            JSON.stringify(jsonRows, null, 2),
            "```",
          ].join("\n"),
        },
      ],
    };
  }
}

// ---- get_change_history ----

async function getChangeHistory(
  args: Record<string, any>,
  client: GoogleAdsClient
): Promise<ToolResult> {
  const customerId = args.customer_id || client.getCustomerId();
  if (!customerId) {
    return {
      content: [{ type: "text", text: "Error: customer_id is required." }],
      isError: true,
    };
  }

  const days = args.days || 14;
  const resourceTypeFilter = args.resource_type
    ? `AND change_event.change_resource_type = '${args.resource_type}'`
    : "";

  const query = `
    SELECT
      change_event.change_date_time,
      change_event.change_resource_type,
      change_event.change_resource_name,
      change_event.client_type,
      change_event.user_email,
      change_event.old_resource,
      change_event.new_resource
    FROM change_event
    WHERE change_event.change_date_time DURING LAST_${days}_DAYS
      ${resourceTypeFilter}
    ORDER BY change_event.change_date_time DESC
    LIMIT ${args.limit || 50}
  `;

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

  if (!rows.length) {
    return {
      content: [
        {
          type: "text",
          text: `No changes found in the last ${days} days.`,
        },
      ],
    };
  }

  const lines = rows.map((row) => {
    const ce = row.changeEvent;
    const timestamp = ce.changeDateTime || "Unknown time";
    const resourceType = ce.changeResourceType || "UNKNOWN";
    const resourceName = ce.changeResourceName || "";
    const clientType = ce.clientType || "";
    const email = ce.userEmail || "system";

    // Build a concise diff summary
    let diffSummary = "";
    if (ce.oldResource && ce.newResource) {
      const oldFlat = flattenRow(ce.oldResource);
      const newFlat = flattenRow(ce.newResource);
      const changes: string[] = [];
      const allDiffKeys = new Set([
        ...Object.keys(oldFlat),
        ...Object.keys(newFlat),
      ]);
      for (const key of allDiffKeys) {
        if (oldFlat[key] !== newFlat[key]) {
          changes.push(
            `      ${key}: ${oldFlat[key] || "(empty)"} → ${newFlat[key] || "(empty)"}`
          );
        }
      }
      if (changes.length > 0) {
        diffSummary = `\n    Changes:\n${changes.slice(0, 5).join("\n")}`;
        if (changes.length > 5) {
          diffSummary += `\n      ... and ${changes.length - 5} more fields`;
        }
      }
    }

    return [
      `  • **${timestamp}** — ${resourceType}`,
      `    Resource: \`${resourceName}\``,
      `    By: ${email} (${clientType})${diffSummary}`,
    ].join("\n");
  });

  return {
    content: [
      {
        type: "text",
        text: `## Account Change History (Last ${days} Days)\n**Changes found:** ${rows.length}\n\n${lines.join("\n\n")}`,
      },
    ],
  };
}

// ---- get_labels ----

async function getLabels(
  args: Record<string, any>,
  client: GoogleAdsClient
): Promise<ToolResult> {
  const customerId = args.customer_id || client.getCustomerId();
  if (!customerId) {
    return {
      content: [{ type: "text", text: "Error: customer_id is required." }],
      isError: true,
    };
  }

  const query = `
    SELECT
      label.id,
      label.name,
      label.status
    FROM label
    ORDER BY label.name
  `;

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

  if (!rows.length) {
    return {
      content: [
        {
          type: "text",
          text: "No labels found in this account. Labels help organize campaigns, ad groups, ads, and keywords into custom groups.",
        },
      ],
    };
  }

  const lines = rows.map((row) => {
    const l = row.label;
    const statusIcon = l.status === "ENABLED" ? "🟢" : "⚪";
    return `  ${statusIcon} **${l.name}** (ID: ${l.id}) — ${l.status}`;
  });

  return {
    content: [
      {
        type: "text",
        text: `## Labels (${rows.length})\n\n${lines.join("\n")}`,
      },
    ],
  };
}

// ============================================
// TOOL DEFINITIONS
// ============================================

const tools: ToolDefinition[] = [
  {
    name: "run_gaql_query",
    description:
      "Execute a raw GAQL (Google Ads Query Language) query. Power-user escape hatch for any data " +
      "accessible via the Google Ads API. Only SELECT queries are allowed (no mutations). " +
      "Refer to https://developers.google.com/google-ads/api/fields/v21/overview for available fields.",
    category: "advanced",
    annotations: {
      title: "Run GAQL Query",
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description:
            "Google Ads customer ID (without dashes). Defaults to configured account.",
        },
        query: {
          type: "string",
          description:
            'Full GAQL SELECT query to execute. Example: "SELECT campaign.name, metrics.clicks FROM campaign WHERE segments.date DURING LAST_7_DAYS"',
        },
      },
      required: ["query"],
    },
    _meta: {
      labels: {
        category: "advanced",
        access: "read",
        complexity: "complex"
      }
    },
    handler: runGaqlQuery,
  },
  {
    name: "export_report",
    description:
      "Export campaign, ad group, keyword, or ad performance data in CSV or JSON format. " +
      "Builds the appropriate GAQL query automatically based on entity type. " +
      "Useful for data analysis, spreadsheet import, or sharing with stakeholders.",
    category: "advanced",
    annotations: {
      title: "Export Report",
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description:
            "Google Ads customer ID (without dashes). Defaults to configured account.",
        },
        entity: {
          type: "string",
          description: "Type of entity to export.",
          enum: ["campaigns", "ad_groups", "keywords", "ads"],
        },
        format: {
          type: "string",
          description: "Output format.",
          enum: ["csv", "json"],
          default: "csv",
        },
        date_range: {
          type: "string",
          description: "GAQL date range predicate.",
          enum: [
            "TODAY",
            "YESTERDAY",
            "LAST_7_DAYS",
            "LAST_14_DAYS",
            "LAST_30_DAYS",
            "LAST_90_DAYS",
            "THIS_MONTH",
            "LAST_MONTH",
            "THIS_QUARTER",
            "LAST_QUARTER",
          ],
          default: "LAST_30_DAYS",
        },
        status: {
          type: "string",
          description:
            "Filter by status (ENABLED, PAUSED, REMOVED). Omit for all statuses.",
          enum: ["ENABLED", "PAUSED", "REMOVED"],
        },
        campaign_id: {
          type: "string",
          description:
            "Filter to a specific campaign (for ad_groups, keywords, ads entity types).",
        },
        min_impressions: {
          type: "number",
          description:
            "Minimum impressions threshold to filter out low-volume entries.",
        },
        limit: {
          type: "number",
          description: "Maximum rows to return. Default: 1000.",
          default: 1000,
        },
      },
      required: ["entity"],
    },
    _meta: {
      labels: {
        category: "advanced",
        access: "read",
        complexity: "complex"
      }
    },
    handler: exportReport,
  },
  {
    name: "get_change_history",
    description:
      "Get recent account changes including who made them, what was changed, and before/after values. " +
      "Useful for auditing, debugging unexpected changes, and tracking team activity.",
    category: "advanced",
    annotations: {
      title: "Get Change History",
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description:
            "Google Ads customer ID (without dashes). Defaults to configured account.",
        },
        days: {
          type: "number",
          description:
            "Number of days of history to retrieve (1-30). Default: 14.",
          default: 14,
          minimum: 1,
          maximum: 30,
        },
        resource_type: {
          type: "string",
          description: "Filter to a specific resource type.",
          enum: [
            "AD",
            "AD_GROUP",
            "AD_GROUP_AD",
            "AD_GROUP_CRITERION",
            "AD_GROUP_BID_MODIFIER",
            "CAMPAIGN",
            "CAMPAIGN_BUDGET",
            "CAMPAIGN_CRITERION",
          ],
        },
        limit: {
          type: "number",
          description: "Maximum number of changes to return. Default: 50.",
          default: 50,
        },
      },
    },
    _meta: {
      labels: {
        category: "advanced",
        access: "read",
        complexity: "simple"
      }
    },
    handler: getChangeHistory,
  },
  {
    name: "get_labels",
    description:
      "List all labels in the Google Ads account. Labels are used to organize and tag campaigns, " +
      "ad groups, ads, and keywords into custom categories for filtering and reporting.",
    category: "advanced",
    annotations: {
      title: "Get Labels",
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        customer_id: {
          type: "string",
          description:
            "Google Ads customer ID (without dashes). Defaults to configured account.",
        },
      },
    },
    _meta: {
      labels: {
        category: "advanced",
        access: "read",
        complexity: "simple"
      }
    },
    handler: getLabels,
  },
];

export default tools;
