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

// --- ツール定義 ---

const GET_YOUTUBE_TRANSCRIPT_DEF: ToolDef = {
  type: 'function',
  function: {
    name: 'GetYouTubeTranscript',
    description: 'YouTube 動画の字幕をタイムスタンプ付きで取得する（動画内容を扱う際は必須）。詳細は ReadToolDoc({ name: "GetYouTubeTranscript" })。',
    parameters: {
      type: 'object',
      properties: {
        url: { type: 'string', description: 'YouTube 動画の URL または動画 ID（例: https://www.youtube.com/watch?v=xxx または xxx）' },
        lang: { type: 'string', description: '字幕の言語コード（例: "ja", "en"）。省略時は利用可能な最初の字幕を返す' },
      },
      required: ['url'],
    },
  },
};

const SEARCH_YOUTUBE_DEF: ToolDef = {
  type: 'function',
  function: {
    name: 'SearchYouTube',
    description: 'YouTube 動画を検索しタイトル・URL・再生回数等を返す。詳細は ReadToolDoc({ name: "SearchYouTube" })。',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string', description: '検索キーワード' },
        limit: { type: 'number', description: '取得件数（デフォルト: 5, 最大: 20）' },
      },
      required: ['query'],
    },
  },
};

export const TOOL_DEFS: Record<string, ToolDef> = {
  GetYouTubeTranscript: GET_YOUTUBE_TRANSCRIPT_DEF,
  SearchYouTube: SEARCH_YOUTUBE_DEF,
};

// --- ヘルパー ---

const VIDEO_ID_REGEX = /(?:youtube\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?/\s]{11})/i;
const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
const ANDROID_UA = 'com.google.android.youtube/20.10.38 (Linux; U; Android 14)';
const INNERTUBE_URL = 'https://www.youtube.com/youtubei/v1/player?prettyPrint=false';
const INNERTUBE_SEARCH_URL = 'https://www.youtube.com/youtubei/v1/search?prettyPrint=false';

function extractVideoId(input: string): string {
  if (input.length === 11 && /^[A-Za-z0-9_-]+$/.test(input)) return input;
  const match = input.match(VIDEO_ID_REGEX);
  if (match?.[1]) return match[1];
  throw new Error(`YouTube 動画 ID を抽出できません: ${input}`);
}

function decodeEntities(text: string): string {
  return text
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/&apos;/g, "'")
    .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16)))
    .replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(parseInt(dec, 10)));
}

function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  if (hours > 0) {
    return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  }
  return `${minutes}:${String(seconds).padStart(2, '0')}`;
}

interface TranscriptSegment {
  text: string;
  offset: number;
  duration: number;
}

function parseTranscriptXml(xml: string): TranscriptSegment[] {
  const segments: TranscriptSegment[] = [];

  // Format 1: <p t="offset" d="duration"><s>text</s></p>
  const pRegex = /<p\s+t="(\d+)"\s+d="(\d+)"[^>]*>([\s\S]*?)<\/p>/g;
  let match;
  while ((match = pRegex.exec(xml)) !== null) {
    const offset = parseInt(match[1], 10);
    const duration = parseInt(match[2], 10);
    const inner = match[3];
    // Extract text from <s> tags or use raw inner
    let text = '';
    const sRegex = /<s[^>]*>([^<]*)<\/s>/g;
    let sMatch;
    while ((sMatch = sRegex.exec(inner)) !== null) {
      text += sMatch[1];
    }
    if (!text) text = inner.replace(/<[^>]+>/g, '');
    text = decodeEntities(text).trim();
    if (text) segments.push({ text, offset, duration });
  }

  if (segments.length > 0) return segments;

  // Format 2: <text start="offset" dur="duration">text</text>
  const textRegex = /<text start="([^"]*)" dur="([^"]*)">([^<]*)<\/text>/g;
  while ((match = textRegex.exec(xml)) !== null) {
    const offset = Math.round(parseFloat(match[1]) * 1000);
    const duration = Math.round(parseFloat(match[2]) * 1000);
    const text = decodeEntities(match[3]).trim();
    if (text) segments.push({ text, offset, duration });
  }

  return segments;
}

// --- GetYouTubeTranscript ---

async function executeGetYouTubeTranscript(
  input: Record<string, unknown>,
): Promise<ToolResult> {
  const urlOrId = input['url'] as string;
  const lang = input['lang'] as string | undefined;

  let videoId: string;
  try {
    videoId = extractVideoId(urlOrId);
  } catch (e: unknown) {
    return { output: (e as Error).message, isError: true };
  }

  try {
    // InnerTube API で字幕トラック情報を取得
    const response = await fetch(INNERTUBE_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': ANDROID_UA,
      },
      body: JSON.stringify({
        context: {
          client: {
            clientName: 'ANDROID',
            clientVersion: '20.10.38',
          },
        },
        videoId,
      }),
    });

    if (!response.ok) {
      throw new Error(`YouTube API エラー: ${response.status} ${response.statusText}`);
    }

    const data = await response.json() as Record<string, unknown>;

    // 動画タイトルを取得
    const videoDetails = data['videoDetails'] as Record<string, unknown> | undefined;
    const title = videoDetails?.['title'] as string || '(不明)';
    const lengthSeconds = videoDetails?.['lengthSeconds'] as string | undefined;

    const captions = data['captions'] as Record<string, unknown> | undefined;
    const tracklistRenderer = captions?.['playerCaptionsTracklistRenderer'] as Record<string, unknown> | undefined;
    const tracks = tracklistRenderer?.['captionTracks'] as Array<Record<string, unknown>> | undefined;

    if (!Array.isArray(tracks) || tracks.length === 0) {
      // Web ページ経由でフォールバック
      return await fetchTranscriptViaWebPage(videoId, lang, title);
    }

    // 利用可能な言語一覧
    const availableLangs = tracks.map(t => `${t['languageCode']}(${t['name'] && (t['name'] as Record<string, unknown>)['simpleText'] || t['languageCode']})`);

    // 指定言語のトラックを探す
    let selectedTrack = lang
      ? tracks.find(t => t['languageCode'] === lang)
      : tracks[0];

    if (!selectedTrack && lang) {
      return {
        output: `言語 "${lang}" の字幕は利用できません。利用可能: ${availableLangs.join(', ')}`,
        isError: true,
      };
    }

    if (!selectedTrack) selectedTrack = tracks[0];

    const baseUrl = selectedTrack['baseUrl'] as string;
    if (!baseUrl) {
      throw new Error('字幕の URL を取得できませんでした');
    }

    // 字幕 XML を取得
    const transcriptRes = await fetch(baseUrl, {
      headers: { 'User-Agent': USER_AGENT },
    });

    if (!transcriptRes.ok) {
      throw new Error(`字幕取得エラー: ${transcriptRes.status}`);
    }

    const xml = await transcriptRes.text();
    const segments = parseTranscriptXml(xml);

    if (segments.length === 0) {
      return { output: 'この動画の字幕を解析できませんでした。', isError: true };
    }

    // 出力フォーマット
    const header = [
      `# ${title}`,
      `URL: https://www.youtube.com/watch?v=${videoId}`,
      lengthSeconds ? `動画時間: ${formatTime(parseInt(lengthSeconds, 10) * 1000)}` : '',
      `言語: ${selectedTrack['languageCode']}`,
      `利用可能な言語: ${availableLangs.join(', ')}`,
      `字幕セグメント数: ${segments.length}`,
      '',
      '---',
      '',
    ].filter(Boolean).join('\n');

    const body = segments
      .map(s => `[${formatTime(s.offset)}] ${s.text}`)
      .join('\n');

    return { output: header + body, isError: false };
  } catch (e: unknown) {
    logger.warn(`[youtube] GetYouTubeTranscript error: ${e}`);
    return { output: `字幕の取得に失敗しました: ${(e as Error).message}`, isError: true };
  }
}

async function fetchTranscriptViaWebPage(
  videoId: string,
  lang: string | undefined,
  title: string,
): Promise<ToolResult> {
  const pageRes = await fetch(`https://www.youtube.com/watch?v=${videoId}`, {
    headers: {
      'User-Agent': USER_AGENT,
      ...(lang && { 'Accept-Language': lang }),
    },
  });

  const html = await pageRes.text();

  if (html.includes('class="g-recaptcha"')) {
    return { output: 'YouTube から CAPTCHA を要求されました。しばらく待ってから再試行してください。', isError: true };
  }

  // ytInitialPlayerResponse から字幕情報を抽出
  const jsonMatch = html.match(/var ytInitialPlayerResponse\s*=\s*(\{.+?\});/s);
  if (!jsonMatch) {
    return { output: 'この動画の字幕情報が見つかりませんでした。字幕が無効化されている可能性があります。', isError: true };
  }

  try {
    const playerData = JSON.parse(jsonMatch[1]) as Record<string, unknown>;
    const captions = playerData['captions'] as Record<string, unknown> | undefined;
    const tracklistRenderer = captions?.['playerCaptionsTracklistRenderer'] as Record<string, unknown> | undefined;
    const tracks = tracklistRenderer?.['captionTracks'] as Array<Record<string, unknown>> | undefined;

    if (!Array.isArray(tracks) || tracks.length === 0) {
      return { output: 'この動画には字幕がありません。', isError: true };
    }

    const selectedTrack = lang
      ? tracks.find(t => t['languageCode'] === lang) || tracks[0]
      : tracks[0];

    const baseUrl = selectedTrack['baseUrl'] as string;
    const transcriptRes = await fetch(baseUrl, {
      headers: { 'User-Agent': USER_AGENT },
    });

    const xml = await transcriptRes.text();
    const segments = parseTranscriptXml(xml);

    if (segments.length === 0) {
      return { output: 'この動画の字幕を解析できませんでした。', isError: true };
    }

    const header = [
      `# ${title}`,
      `URL: https://www.youtube.com/watch?v=${videoId}`,
      `言語: ${selectedTrack['languageCode']}`,
      `字幕セグメント数: ${segments.length}`,
      '',
      '---',
      '',
    ].join('\n');

    const body = segments
      .map(s => `[${formatTime(s.offset)}] ${s.text}`)
      .join('\n');

    return { output: header + body, isError: false };
  } catch {
    return { output: 'この動画の字幕情報の解析に失敗しました。', isError: true };
  }
}

// --- SearchYouTube ---

async function executeSearchYouTube(
  input: Record<string, unknown>,
): Promise<ToolResult> {
  const query = input['query'] as string;
  const limit = Math.min(Math.max((input['limit'] as number) || 5, 1), 20);

  try {
    // InnerTube Search API を使用
    const response = await fetch(INNERTUBE_SEARCH_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': USER_AGENT,
      },
      body: JSON.stringify({
        context: {
          client: {
            clientName: 'WEB',
            clientVersion: '2.20240101.00.00',
            hl: 'ja',
            gl: 'JP',
          },
        },
        query,
      }),
    });

    if (!response.ok) {
      throw new Error(`YouTube 検索 API エラー: ${response.status} ${response.statusText}`);
    }

    const data = await response.json() as Record<string, unknown>;

    // レスポンスから動画情報を抽出
    const contents = (data['contents'] as Record<string, unknown>)
      ?.['twoColumnSearchResultsRenderer'] as Record<string, unknown>;
    const primaryContents = contents?.['primaryContents'] as Record<string, unknown>;
    const sectionList = primaryContents?.['sectionListRenderer'] as Record<string, unknown>;
    const sections = sectionList?.['contents'] as Array<Record<string, unknown>>;

    if (!sections || sections.length === 0) {
      return { output: `"${query}" の検索結果が見つかりませんでした。`, isError: false };
    }

    const results: string[] = [];
    const videoItems: YouTubeVideoItem[] = [];

    for (const section of sections) {
      const itemSection = section['itemSectionRenderer'] as Record<string, unknown> | undefined;
      if (!itemSection) continue;

      const items = itemSection['contents'] as Array<Record<string, unknown>>;
      if (!items) continue;

      for (const item of items) {
        if (results.length >= limit) break;

        const videoRenderer = item['videoRenderer'] as Record<string, unknown> | undefined;
        if (!videoRenderer) continue;

        const videoId = videoRenderer['videoId'] as string;
        const titleRuns = (videoRenderer['title'] as Record<string, unknown>)?.['runs'] as Array<Record<string, unknown>> | undefined;
        const videoTitle = titleRuns?.map(r => r['text']).join('') || '(タイトルなし)';

        const channelRuns = (videoRenderer['ownerText'] as Record<string, unknown>)?.['runs'] as Array<Record<string, unknown>> | undefined;
        const channelName = channelRuns?.map(r => r['text']).join('') || '(不明)';

        const viewCountText = (videoRenderer['viewCountText'] as Record<string, unknown>)?.['simpleText'] as string || '';
        const publishedText = (videoRenderer['publishedTimeText'] as Record<string, unknown>)?.['simpleText'] as string || '';
        const lengthText = (videoRenderer['lengthText'] as Record<string, unknown>)?.['simpleText'] as string || '';

        const descSnippetRuns = (videoRenderer['detailedMetadataSnippets'] as Array<Record<string, unknown>>)?.[0];
        const snippetRuns = (descSnippetRuns?.['snippetText'] as Record<string, unknown>)?.['runs'] as Array<Record<string, unknown>> | undefined;
        const description = snippetRuns?.map(r => r['text']).join('') || '';

        const entry = [
          `${results.length + 1}. ${videoTitle}`,
          `   URL: https://www.youtube.com/watch?v=${videoId}`,
          `   チャンネル: ${channelName}`,
          lengthText ? `   動画時間: ${lengthText}` : '',
          viewCountText ? `   再生回数: ${viewCountText}` : '',
          publishedText ? `   投稿日: ${publishedText}` : '',
          description ? `   概要: ${description}` : '',
        ].filter(Boolean).join('\n');

        results.push(entry);
        videoItems.push({
          videoId,
          title: videoTitle,
          channelName,
          thumbnailUrl: `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`,
          videoUrl: `https://www.youtube.com/watch?v=${videoId}`,
          viewCount: viewCountText,
          publishedAt: publishedText,
          duration: lengthText,
          description,
        });
      }
    }

    if (results.length === 0) {
      return { output: `"${query}" の動画検索結果が見つかりませんでした。`, isError: false };
    }

    const output = `YouTube 検索結果: "${query}" (${results.length}件)\n\n${results.join('\n\n')}`;

    if (videoItems.length > 0) {
      const refId = `youtube-${Date.now()}`;
      const structuredBlocks: StructuredBlock[] = [{
        refId,
        type: 'youtube_videos',
        title: `YouTube 検索結果: 「${query}」`,
        data: { query, videos: videoItems },
      }];
      return { output: `${output}\n\n[[embed:${refId}]]`, isError: false, structuredBlocks };
    }
    return { output, isError: false };
  } catch (e: unknown) {
    // InnerTube API が失敗した場合、HTML スクレイピングにフォールバック
    logger.warn(`[youtube] InnerTube search failed, falling back to HTML scraping: ${e}`);
    return await searchYouTubeViaHtml(query, limit);
  }
}

async function searchYouTubeViaHtml(
  query: string,
  limit: number,
): Promise<ToolResult> {
  try {
    const searchUrl = `https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`;
    const response = await fetch(searchUrl, {
      headers: {
        'User-Agent': USER_AGENT,
        'Accept-Language': 'ja',
      },
    });

    if (!response.ok) {
      throw new Error(`YouTube 検索エラー: ${response.status}`);
    }

    const html = await response.text();

    // ytInitialData から検索結果を抽出
    const jsonMatch = html.match(/var ytInitialData\s*=\s*(\{.+?\});/s);
    if (!jsonMatch) {
      return { output: 'YouTube 検索結果の解析に失敗しました。', isError: true };
    }

    const searchData = JSON.parse(jsonMatch[1]) as Record<string, unknown>;
    const contents = (searchData['contents'] as Record<string, unknown>)
      ?.['twoColumnSearchResultsRenderer'] as Record<string, unknown>;
    const primaryContents = contents?.['primaryContents'] as Record<string, unknown>;
    const sectionList = primaryContents?.['sectionListRenderer'] as Record<string, unknown>;
    const sections = sectionList?.['contents'] as Array<Record<string, unknown>>;

    if (!sections) {
      return { output: `"${query}" の検索結果が見つかりませんでした。`, isError: false };
    }

    const results: string[] = [];

    for (const section of sections) {
      const itemSection = section['itemSectionRenderer'] as Record<string, unknown> | undefined;
      if (!itemSection) continue;

      const items = itemSection['contents'] as Array<Record<string, unknown>>;
      if (!items) continue;

      for (const item of items) {
        if (results.length >= limit) break;

        const videoRenderer = item['videoRenderer'] as Record<string, unknown> | undefined;
        if (!videoRenderer) continue;

        const videoId = videoRenderer['videoId'] as string;
        const titleRuns = (videoRenderer['title'] as Record<string, unknown>)?.['runs'] as Array<Record<string, unknown>> | undefined;
        const videoTitle = titleRuns?.map(r => r['text']).join('') || '(タイトルなし)';

        const channelRuns = (videoRenderer['ownerText'] as Record<string, unknown>)?.['runs'] as Array<Record<string, unknown>> | undefined;
        const channelName = channelRuns?.map(r => r['text']).join('') || '(不明)';

        const viewCountText = (videoRenderer['viewCountText'] as Record<string, unknown>)?.['simpleText'] as string || '';
        const publishedText = (videoRenderer['publishedTimeText'] as Record<string, unknown>)?.['simpleText'] as string || '';
        const lengthText = (videoRenderer['lengthText'] as Record<string, unknown>)?.['simpleText'] as string || '';

        const entry = [
          `${results.length + 1}. ${videoTitle}`,
          `   URL: https://www.youtube.com/watch?v=${videoId}`,
          `   チャンネル: ${channelName}`,
          lengthText ? `   動画時間: ${lengthText}` : '',
          viewCountText ? `   再生回数: ${viewCountText}` : '',
          publishedText ? `   投稿日: ${publishedText}` : '',
        ].filter(Boolean).join('\n');

        results.push(entry);
      }
    }

    if (results.length === 0) {
      return { output: `"${query}" の動画検索結果が見つかりませんでした。`, isError: false };
    }

    const output = `YouTube 検索結果: "${query}" (${results.length}件)\n\n${results.join('\n\n')}`;
    return { output, isError: false };
  } catch (e: unknown) {
    logger.warn(`[youtube] HTML search failed: ${e}`);
    return { output: `YouTube 検索に失敗しました: ${(e as Error).message}`, isError: true };
  }
}

// --- エクスポート ---

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