// ============================================
// BIDDING & BUDGET 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 };
}

// ---- Tools ----

const tools: ToolDefinition[] = [
  // ============================
  // list_bidding_strategies
  // ============================
  {
    name: "list_bidding_strategies",
    description:
      "List portfolio bidding strategies in the account, including type, target metrics, campaign count, and performance status.",
    category: "bidding",
    annotations: {
      title: "List Bidding Strategies",
      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).",
        },
        limit: {
          type: "number",
          description: "Maximum strategies to return (default: 50).",
        },
      },
    },
    _meta: {
      labels: {
        category: "bidding",
        access: "read",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);
      const limit = Math.min(args.limit || 50, 200);

      const query = `
        SELECT
          bidding_strategy.id,
          bidding_strategy.name,
          bidding_strategy.type,
          bidding_strategy.status,
          bidding_strategy.campaign_count,
          bidding_strategy.non_removed_campaign_count,
          bidding_strategy.resource_name,
          bidding_strategy.target_cpa.target_cpa_micros,
          bidding_strategy.target_roas.target_roas,
          bidding_strategy.maximize_conversions.target_cpa_micros,
          bidding_strategy.maximize_conversion_value.target_roas,
          bidding_strategy.target_spend.cpc_bid_ceiling_micros,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM bidding_strategy
        WHERE segments.date DURING LAST_30_DAYS
        ORDER BY metrics.cost_micros DESC
        LIMIT ${limit}`;

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

      if (!rows.length) {
        return textResult("No portfolio bidding strategies found in this account.");
      }

      const lines = rows.map((r: any) => {
        const bs = r.biddingStrategy || {};
        const m = r.metrics || {};

        // Extract target value based on strategy type
        let targetInfo = "";
        if (bs.targetCpa?.targetCpaMicros) {
          targetInfo = `Target CPA: $${microsToMoney(bs.targetCpa.targetCpaMicros)}`;
        } else if (bs.targetRoas?.targetRoas) {
          targetInfo = `Target ROAS: ${(bs.targetRoas.targetRoas * 100).toFixed(0)}%`;
        } else if (bs.maximizeConversions?.targetCpaMicros) {
          targetInfo = `Max Conv Target CPA: $${microsToMoney(bs.maximizeConversions.targetCpaMicros)}`;
        } else if (bs.maximizeConversionValue?.targetRoas) {
          targetInfo = `Max Value Target ROAS: ${(bs.maximizeConversionValue.targetRoas * 100).toFixed(0)}%`;
        } else if (bs.targetSpend?.cpcBidCeilingMicros) {
          targetInfo = `CPC Ceiling: $${microsToMoney(bs.targetSpend.cpcBidCeilingMicros)}`;
        }

        return [
          `**${bs.name || "Unnamed"}** (ID: ${bs.id})`,
          `  Type: ${bs.type || "—"} | Status: ${bs.status || "—"} | Campaigns: ${bs.nonRemovedCampaignCount || bs.campaignCount || 0}`,
          targetInfo ? `  ${targetInfo}` : null,
          `  Cost: $${microsToMoney(m.costMicros || 0)} | Clicks: ${formatNumber(m.clicks || 0)} | Conv: ${formatNumber(m.conversions || 0)} | Conv Value: $${(m.conversionsValue || 0).toFixed(2)}`,
        ]
          .filter(Boolean)
          .join("\n");
      });

      return textResult(
        `## Portfolio Bidding Strategies (Last 30 Days)\n\n${lines.join("\n\n")}\n\n_${rows.length} strateg${rows.length === 1 ? "y" : "ies"} returned._`
      );
    },
  },

  // ============================
  // get_bid_recommendations
  // ============================
  {
    name: "get_bid_recommendations",
    description:
      "Get Google's automated bid recommendations for keywords, including suggested bids and estimated impact on clicks and impressions.",
    category: "bidding",
    annotations: {
      title: "Get Bid Recommendations",
      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: "Limit recommendations to a specific campaign.",
        },
        limit: {
          type: "number",
          description: "Maximum recommendations to return (default: 25).",
        },
      },
    },
    _meta: {
      labels: {
        category: "bidding",
        access: "read",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);
      const limit = Math.min(args.limit || 25, 100);

      // Fetch bid-related recommendations
      let query = `
        SELECT
          recommendation.type,
          recommendation.resource_name,
          recommendation.campaign,
          recommendation.ad_group,
          recommendation.impact.base_metrics.impressions,
          recommendation.impact.base_metrics.clicks,
          recommendation.impact.base_metrics.cost_micros,
          recommendation.impact.base_metrics.conversions,
          recommendation.impact.potential_metrics.impressions,
          recommendation.impact.potential_metrics.clicks,
          recommendation.impact.potential_metrics.cost_micros,
          recommendation.impact.potential_metrics.conversions,
          recommendation.keyword_recommendation,
          recommendation.target_cpa_opt_in_recommendation,
          recommendation.maximize_conversions_opt_in_recommendation
        FROM recommendation
        WHERE recommendation.type IN (
          'KEYWORD_MATCH_TYPE',
          'TARGET_CPA_OPT_IN',
          'MAXIMIZE_CONVERSIONS_OPT_IN',
          'ENHANCED_CPC_OPT_IN',
          'TARGET_ROAS_OPT_IN',
          'MAXIMIZE_CLICKS_OPT_IN'
        )`;

      if (args.campaign_id) {
        query += `\n          AND recommendation.campaign = 'customers/${cid}/campaigns/${args.campaign_id}'`;
      }

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

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

      if (!rows.length) {
        return textResult("No bid recommendations available. Your account may already be well-optimized, or there's insufficient data.");
      }

      const lines = rows.map((r: any, i: number) => {
        const rec = r.recommendation || {};
        const impact = rec.impact || {};
        const base = impact.baseMetrics || {};
        const potential = impact.potentialMetrics || {};

        const impactLines: string[] = [];

        if (potential.impressions && base.impressions) {
          const delta = parseInt(potential.impressions) - parseInt(base.impressions);
          if (delta > 0) impactLines.push(`+${formatNumber(delta)} impressions`);
        }
        if (potential.clicks && base.clicks) {
          const delta = parseInt(potential.clicks) - parseInt(base.clicks);
          if (delta > 0) impactLines.push(`+${formatNumber(delta)} clicks`);
        }
        if (potential.conversions && base.conversions) {
          const delta = (potential.conversions || 0) - (base.conversions || 0);
          if (delta > 0) impactLines.push(`+${delta.toFixed(1)} conversions`);
        }
        if (potential.costMicros && base.costMicros) {
          const deltaCost = parseInt(potential.costMicros) - parseInt(base.costMicros);
          impactLines.push(`${deltaCost >= 0 ? "+" : ""}$${microsToMoney(deltaCost)} cost`);
        }

        const impactStr = impactLines.length ? impactLines.join(", ") : "—";

        return `${i + 1}. **${rec.type || "UNKNOWN"}**\n   Estimated impact: ${impactStr}`;
      });

      return textResult(
        `## Bid Recommendations\n\n${lines.join("\n\n")}\n\n_${rows.length} recommendation(s) found._`
      );
    },
  },

  // ============================
  // update_keyword_bids
  // ============================
  {
    name: "update_keyword_bids",
    description:
      "Change the CPC bid for one or more keywords. " +
      "⚠️ This modifies live keyword bids and may immediately affect ad serving and costs.",
    category: "bidding",
    annotations: {
      title: "Update Keyword Bids",
      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 keyword belongs to.",
        },
        keyword_criterion_id: {
          type: "string",
          description: "Keyword criterion ID to update bid for. Use this for a single keyword.",
        },
        bid_updates: {
          type: "array",
          items: {
            type: "object",
            properties: {
              criterion_id: { type: "string", description: "Keyword criterion ID." },
              bid_amount: { type: "number", description: "New CPC bid in dollars." },
            },
            required: ["criterion_id", "bid_amount"],
          },
          description: "Batch bid updates. Alternative to single keyword_criterion_id + new_bid_amount.",
        },
        new_bid_amount: {
          type: "number",
          description: "New max CPC bid in dollars (e.g. 2.50 for $2.50). Used with keyword_criterion_id.",
        },
      },
      required: ["ad_group_id"],
    },
    _meta: {
      labels: {
        category: "bidding",
        access: "delete",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);

      // Build list of bid updates from either single or batch params
      interface BidUpdate {
        criterionId: string;
        bidAmount: number;
      }

      const updates: BidUpdate[] = [];

      if (args.bid_updates?.length) {
        for (const u of args.bid_updates) {
          if (!u.criterion_id || u.bid_amount == null) {
            return errorResult("Each bid_update must have criterion_id and bid_amount.");
          }
          if (u.bid_amount <= 0) {
            return errorResult(`Bid amount must be positive. Got $${u.bid_amount} for criterion ${u.criterion_id}.`);
          }
          updates.push({ criterionId: u.criterion_id, bidAmount: u.bid_amount });
        }
      } else if (args.keyword_criterion_id && args.new_bid_amount != null) {
        if (args.new_bid_amount <= 0) {
          return errorResult("Bid amount must be positive.");
        }
        updates.push({ criterionId: args.keyword_criterion_id, bidAmount: args.new_bid_amount });
      } else {
        return errorResult(
          "Provide either (keyword_criterion_id + new_bid_amount) for a single update, or bid_updates array for batch."
        );
      }

      const operations = updates.map((u) => ({
        entity: "adGroupCriterion",
        operation: "update" as const,
        resource: {
          resourceName: `customers/${cid}/adGroupCriteria/${args.ad_group_id}~${u.criterionId}`,
          cpcBidMicros: String(Math.round(u.bidAmount * 1_000_000)),
        },
        updateMask: "cpc_bid_micros",
      }));

      await client.mutate(cid, operations);

      const summary = updates
        .map((u) => `  - Criterion ${u.criterionId}: $${u.bidAmount.toFixed(2)}`)
        .join("\n");

      return textResult(
        `✅ Updated ${updates.length} keyword bid(s) in ad group ${args.ad_group_id}:\n\n${summary}`
      );
    },
  },

  // ============================
  // list_recommendations
  // ============================
  {
    name: "list_recommendations",
    description:
      "Get Google's optimization score and all recommendations for the account, including estimated impact on performance. " +
      "Covers bid strategies, keywords, ads, budgets, and more.",
    category: "bidding",
    annotations: {
      title: "List Recommendations",
      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).",
        },
        types: {
          type: "array",
          items: { type: "string" },
          description:
            "Filter by recommendation types (e.g. KEYWORD, CAMPAIGN_BUDGET, TARGET_CPA_OPT_IN, RESPONSIVE_SEARCH_AD, SITELINK_EXTENSION). Omit for all types.",
        },
        campaign_id: {
          type: "string",
          description: "Limit recommendations to a specific campaign.",
        },
        limit: {
          type: "number",
          description: "Maximum recommendations to return (default: 50).",
        },
      },
    },
    _meta: {
      labels: {
        category: "bidding",
        access: "read",
        complexity: "simple"
      }
    },
    handler: async (args, client) => {
      const cid = resolveCustomerId(args, client);
      const limit = Math.min(args.limit || 50, 200);

      // First, get optimization score
      let scoreText = "";
      try {
        const scoreQuery = `
          SELECT
            customer.optimization_score,
            customer.optimization_score_weight
          FROM customer
          LIMIT 1`;
        const scoreRows = await client.query(cid, scoreQuery);
        if (scoreRows.length) {
          const score = scoreRows[0].customer?.optimizationScore;
          if (score != null) {
            const pct = (score * 100).toFixed(1);
            const bar = "█".repeat(Math.round(score * 10)) + "░".repeat(10 - Math.round(score * 10));
            scoreText = `**Optimization Score:** ${pct}% ${bar}\n\n`;
          }
        }
      } catch {
        // Optimization score not available for all accounts
      }

      // Fetch recommendations
      let query = `
        SELECT
          recommendation.type,
          recommendation.resource_name,
          recommendation.campaign,
          recommendation.ad_group,
          recommendation.impact.base_metrics.impressions,
          recommendation.impact.base_metrics.clicks,
          recommendation.impact.base_metrics.cost_micros,
          recommendation.impact.base_metrics.conversions,
          recommendation.impact.potential_metrics.impressions,
          recommendation.impact.potential_metrics.clicks,
          recommendation.impact.potential_metrics.cost_micros,
          recommendation.impact.potential_metrics.conversions,
          recommendation.campaign_budget_recommendation,
          recommendation.keyword_recommendation,
          recommendation.text_ad_recommendation,
          recommendation.responsive_search_ad_recommendation
        FROM recommendation`;

      const conditions: string[] = [];

      if (args.types?.length) {
        conditions.push(
          `recommendation.type IN (${args.types.map((t: string) => `'${t}'`).join(", ")})`
        );
      }

      if (args.campaign_id) {
        conditions.push(`recommendation.campaign = 'customers/${cid}/campaigns/${args.campaign_id}'`);
      }

      if (conditions.length) {
        query += `\n        WHERE ${conditions.join(" AND ")}`;
      }

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

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

      if (!rows.length && !scoreText) {
        return textResult("No recommendations available for this account. Your campaigns may already be well-optimized.");
      }

      // Group by type
      const byType = new Map<string, any[]>();
      for (const r of rows) {
        const type = r.recommendation?.type || "UNKNOWN";
        if (!byType.has(type)) byType.set(type, []);
        byType.get(type)!.push(r);
      }

      const sections: string[] = ["## Google Ads Recommendations\n"];
      if (scoreText) sections.push(scoreText);

      for (const [type, recs] of byType) {
        sections.push(`### ${type.replace(/_/g, " ")} (${recs.length})\n`);

        recs.forEach((r: any, i: number) => {
          const rec = r.recommendation || {};
          const impact = rec.impact || {};
          const base = impact.baseMetrics || {};
          const potential = impact.potentialMetrics || {};

          const impactParts: string[] = [];
          if (potential.clicks && base.clicks) {
            const delta = parseInt(potential.clicks) - parseInt(base.clicks);
            if (delta !== 0) impactParts.push(`${delta > 0 ? "+" : ""}${formatNumber(delta)} clicks`);
          }
          if (potential.conversions && base.conversions) {
            const delta = (potential.conversions || 0) - (base.conversions || 0);
            if (delta !== 0) impactParts.push(`${delta > 0 ? "+" : ""}${delta.toFixed(1)} conv`);
          }
          if (potential.costMicros && base.costMicros) {
            const delta = parseInt(potential.costMicros) - parseInt(base.costMicros);
            if (delta !== 0) impactParts.push(`${delta > 0 ? "+" : ""}$${microsToMoney(delta)} cost`);
          }

          // Extract details from specific recommendation types
          let detail = "";
          if (rec.keywordRecommendation?.keyword) {
            const kw = rec.keywordRecommendation.keyword;
            detail = ` — Keyword: "${kw.text}" (${kw.matchType})`;
          } else if (rec.campaignBudgetRecommendation?.recommendedBudgetAmountMicros) {
            detail = ` — Suggested budget: $${microsToMoney(rec.campaignBudgetRecommendation.recommendedBudgetAmountMicros)}`;
          }

          const impactStr = impactParts.length ? `Est. impact: ${impactParts.join(", ")}` : "";

          sections.push(`${i + 1}. ${impactStr}${detail}`);
        });

        sections.push("");
      }

      sections.push(`_${rows.length} total recommendation(s)._`);

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

export default tools;
