import { ToolDef } from '../../llm/openai-compat.js';
import type { ToolContext, ToolResult } from './core.js';
import { logger } from '../../logger.js';
import type { StructuredBlock, AmazonProductItem } from './structured-blocks.js';

const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
const SEARCH_TIMEOUT = 15_000;

const SEARCH_AMAZON_DEF: ToolDef = {
  type: 'function',
  function: {
    name: 'SearchAmazon',
    description: 'Amazon.co.jp で商品検索し、商品画像・価格・Keepa グラフ付き Markdown を返す（画像要素は省略せずそのまま最終回答に埋め込むこと）。詳細は ReadToolDoc({ name: "SearchAmazon" })。',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string', description: '検索キーワード' },
        max_results: { type: 'number', description: '取得件数（デフォルト: 5, 最大: 10）' },
      },
      required: ['query'],
    },
  },
};

export const TOOL_DEFS: Record<string, ToolDef> = {
  SearchAmazon: SEARCH_AMAZON_DEF,
};

interface AmazonProduct {
  asin: string;
  title: string;
  price?: string;
  rating?: string;
  reviewCount?: string;
  imageUrl?: string;
}

async function fetchAmazonSearch(query: string): Promise<string> {
  const url = `https://www.amazon.co.jp/s?k=${encodeURIComponent(query)}&language=ja_JP`;
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), SEARCH_TIMEOUT);
  try {
    const res = await fetch(url, {
      headers: {
        'User-Agent': USER_AGENT,
        'Accept-Language': 'ja-JP,ja;q=0.9,en;q=0.8',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Encoding': 'gzip, deflate',
      },
      signal: controller.signal,
    });
    if (!res.ok) throw new Error(`Amazon returned ${res.status}`);
    return await res.text();
  } finally {
    clearTimeout(timer);
  }
}

function parseProducts(html: string, maxResults: number): AmazonProduct[] {
  const products: AmazonProduct[] = [];

  // Match product containers: data-asin and data-component-type can appear in either order
  const blockRegex = /<div[^>]+data-asin="(B[A-Z0-9]{9})"[^>]+data-component-type="s-search-result"[^>]*>([\s\S]*?)(?=<div[^>]+data-asin="B[A-Z0-9]{9}"[^>]+data-component-type="s-search-result"|$)/gi;

  let match: RegExpExecArray | null;

  // Try the block approach first
  while ((match = blockRegex.exec(html)) !== null && products.length < maxResults) {
    const asin = match[1];
    const block = match[2];
    if (!asin || asin === 'undefined') continue;

    const product: AmazonProduct = { asin, title: '' };

    // Extract title: usually in <h2> <a> <span>
    const titleMatch = block.match(/<h2[^>]*>[\s\S]*?<span[^>]*>([\s\S]*?)<\/span>/i);
    if (titleMatch) {
      product.title = titleMatch[1].replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
    }

    // Extract price: <span class="a-price">...<span class="a-offscreen">¥12,980</span>
    const priceMatch = block.match(/<span class="a-price"[^>]*>[\s\S]*?<span class="a-offscreen">([\s\S]*?)<\/span>/i);
    if (priceMatch) {
      product.price = priceMatch[1].replace(/<[^>]+>/g, '').trim();
    }

    // Extract rating: <span class="a-icon-alt">5つ星のうち4.5</span>
    const ratingMatch = block.match(/<span class="a-icon-alt">([\d.]+つ星のうち[\d.]+)<\/span>/i)
                      || block.match(/(\d+(?:\.\d+)?)\s*つ星のうち/i);
    if (ratingMatch) {
      const rVal = ratingMatch[1].match(/(\d+(?:\.\d+)?)/);
      if (rVal) product.rating = rVal[1];
    }

    // Extract review count
    const reviewMatch = block.match(/aria-label="([\d,]+)件の評価"/i)
                      || block.match(/<span[^>]*>([\d,]+)<\/span>\s*件の評価/i);
    if (reviewMatch) {
      product.reviewCount = reviewMatch[1];
    }

    // Extract image URL
    const imgMatch = block.match(/<img[^>]+class="s-image"[^>]+src="([^"]+)"/i);
    if (imgMatch) {
      product.imageUrl = imgMatch[1];
    }

    if (product.title) {
      products.push(product);
    }
  }

  // Fallback: simpler extraction if block approach found nothing
  if (products.length === 0) {
    const asinRegex = /data-asin="(B[A-Z0-9]{9})"/g;
    const asins = new Set<string>();
    let asinMatch: RegExpExecArray | null;
    while ((asinMatch = asinRegex.exec(html)) !== null) {
      asins.add(asinMatch[1]);
    }

    for (const asin of asins) {
      if (products.length >= maxResults) break;

      // Find the section around this ASIN
      const asinIdx = html.indexOf(`data-asin="${asin}"`);
      if (asinIdx === -1) continue;
      const section = html.slice(asinIdx, asinIdx + 5000);

      const product: AmazonProduct = { asin, title: '' };

      const titleMatch = section.match(/<h2[^>]*>[\s\S]*?<span[^>]*>([\s\S]*?)<\/span>/i);
      if (titleMatch) {
        product.title = titleMatch[1].replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
      }

      const priceMatch = section.match(/<span class="a-offscreen">([¥￥][\d,]+)<\/span>/i);
      if (priceMatch) {
        product.price = priceMatch[1].trim();
      }

      const imgMatch = section.match(/<img[^>]+class="s-image"[^>]+src="([^"]+)"/i);
      if (imgMatch) {
        product.imageUrl = imgMatch[1];
      }

      if (product.title) {
        products.push(product);
      }
    }
  }

  return products;
}

function formatResults(
  products: AmazonProduct[],
  query: string,
  affiliateTag?: string,
): string {
  if (products.length === 0) {
    return `Amazon.co.jp で「${query}」の検索結果が取得できませんでした。Amazon がアクセスをブロックしている可能性があります。BrowseWeb で https://www.amazon.co.jp/s?k=${encodeURIComponent(query)} にアクセスして手動で検索してください。`;
  }

  const lines: string[] = [
    `## Amazon.co.jp 検索結果: 「${query}」`,
    '',
    '> **注意**: 以下の出力には商品画像と Keepa 価格推移グラフが含まれています。画像要素（`![...](...)`）を省略せず、そのまま最終回答に含めてください。',
    '',
  ];

  for (let i = 0; i < products.length; i++) {
    const p = products[i];
    const productUrl = affiliateTag
      ? `https://www.amazon.co.jp/dp/${p.asin}?tag=${affiliateTag}`
      : `https://www.amazon.co.jp/dp/${p.asin}`;
    const keepaUrl = `https://keepa.com/#!product/5-${p.asin}`;
    const keepaGraph = `https://graph.keepa.com/pricehistory.png?asin=${p.asin}&domain=co.jp`;

    lines.push(`### ${i + 1}. ${p.title}`);
    lines.push('');
    if (p.imageUrl) lines.push(`![商品画像](${p.imageUrl})`);
    lines.push('');
    if (p.price) lines.push(`- **価格**: ${p.price}`);
    if (p.rating) lines.push(`- **評価**: ${p.rating}${p.reviewCount ? ` (${p.reviewCount}件)` : ''}`);
    lines.push(`- **ASIN**: ${p.asin}`);
    lines.push(`- **商品リンク**: ${productUrl}`);
    lines.push(`- **Keepa 価格推移**: [グラフを見る](${keepaUrl})`);
    lines.push('');
    lines.push(`![価格推移](${keepaGraph})`);
    lines.push('');
  }

  return lines.join('\n');
}

async function executeSearchAmazon(
  input: Record<string, unknown>,
  ctx: ToolContext,
): Promise<ToolResult> {
  const query = input['query'] as string;
  if (!query) {
    return { output: 'query is required', isError: true };
  }

  const maxResults = Math.min(10, Math.max(1, typeof input['max_results'] === 'number' ? Math.floor(input['max_results']) : 5));
  const affiliateTag = ctx.toolsConfig?.amazonAffiliateTag;

  try {
    logger.info(`[SearchAmazon] searching: ${query}`);
    const html = await fetchAmazonSearch(query);
    const products = parseProducts(html, maxResults);
    const output = formatResults(products, query, affiliateTag);

    // 構造化データを生成
    const refId = `amazon-${Date.now()}`;
    const structuredBlocks: StructuredBlock[] = [{
      refId,
      type: 'amazon_products',
      title: `Amazon 検索結果: 「${query}」`,
      data: {
        query,
        products: products.map((p): AmazonProductItem => ({
          asin: p.asin,
          title: p.title,
          price: p.price,
          rating: p.rating ? parseFloat(p.rating) : undefined,
          reviewCount: p.reviewCount ? parseInt(p.reviewCount.replace(/,/g, ''), 10) : undefined,
          imageUrl: p.imageUrl,
          productUrl: affiliateTag
            ? `https://www.amazon.co.jp/dp/${p.asin}?tag=${affiliateTag}`
            : `https://www.amazon.co.jp/dp/${p.asin}`,
          keepaGraphUrl: `https://graph.keepa.com/pricehistory.png?asin=${p.asin}&domain=co.jp`,
          keepaDetailUrl: `https://keepa.com/#!product/5-${p.asin}`,
        })),
      },
    }];

    return { output: `${output}\n\n[[embed:${refId}]]`, isError: false, structuredBlocks };
  } catch (e) {
    const msg = (e as Error).name === 'AbortError'
      ? `Amazon 検索がタイムアウトしました (${SEARCH_TIMEOUT / 1000}秒)`
      : `Amazon 検索に失敗しました: ${(e as Error).message}`;
    return { output: `${msg}\n\nBrowseWeb で https://www.amazon.co.jp/s?k=${encodeURIComponent(query)} にアクセスして手動で検索してください。`, isError: true };
  }
}

/**
 * テキスト中の Amazon ASIN に対して Keepa 価格推移グラフが欠落していれば末尾に補完する。
 * LLM がツール出力から Keepa グラフを省略した場合のセーフティネット。
 */
export function ensureKeepaGraphs(text: string): string {
  const asinRegex = /amazon\.co\.jp\/dp\/(B[A-Z0-9]{9})/g;
  const asins: string[] = [];
  let m: RegExpExecArray | null;
  while ((m = asinRegex.exec(text)) !== null) {
    if (!asins.includes(m[1])) asins.push(m[1]);
  }
  if (asins.length === 0) return text;

  const missing = asins.filter(
    (asin) => !text.includes(`graph.keepa.com/pricehistory.png?asin=${asin}`),
  );
  if (missing.length === 0) return text;

  const section = [
    '',
    '---',
    '',
    '### 価格推移 (Keepa)',
    '',
    ...missing.flatMap((asin) => [
      `![価格推移](https://graph.keepa.com/pricehistory.png?asin=${asin}&domain=co.jp)`,
      `[Keepa で詳細を見る](https://keepa.com/#!product/5-${asin})`,
      '',
    ]),
  ];
  return text + section.join('\n');
}

export async function executeTool(
  name: string,
  input: Record<string, unknown>,
  ctx: ToolContext,
): Promise<ToolResult | null> {
  switch (name) {
    case 'SearchAmazon':
      return executeSearchAmazon(input, ctx);
    default:
      return null;
  }
}
