import {
  ChannelType,
  Client,
  EmbedBuilder,
  Events,
  GatewayIntentBits,
  type TextChannel,
} from "discord.js";
import { env } from "../core/env.js";
import { logger } from "../core/logger.js";
import { commandMap } from "../commands/index.js";
import { startNotifyWorker } from "../queue/notify-queue.js";
import { musicPlayer } from "../services/music/music-player.js";
import { newsService } from "../services/news/news-service.js";
import {
  summarizeService,
  type SummarizeEngine,
  type SummarizeMode,
} from "../services/summarize/summarize-service.js";
import { translateService } from "../services/translate/translate-service.js";
import { gdsAnimeService } from "../services/gds/gds-anime-service.js";
import { gdsMovieService } from "../services/gds/gds-movie-service.js";

export function createBotClient() {
  const PREFIX = "!";
  const client = new Client({
    intents: [
      GatewayIntentBits.Guilds,
      GatewayIntentBits.GuildMessages,
      GatewayIntentBits.GuildVoiceStates,
      GatewayIntentBits.MessageContent,
    ],
  });
  musicPlayer.init(client);

  client.once(Events.ClientReady, () => {
    logger.info({ user: client.user?.tag }, "Bot ready");
  });

  client.on(Events.InteractionCreate, async (interaction) => {
    if (!interaction.isChatInputCommand()) return;

    const cmd = commandMap.get(interaction.commandName);
    if (!cmd) {
      await interaction.reply({ content: "알 수 없는 명령입니다.", ephemeral: true });
      return;
    }

    try {
      await cmd.execute(interaction);
    } catch (error) {
      logger.error({ error, command: interaction.commandName }, "Command execution failed");
      const message = "명령 실행 중 오류가 발생했습니다.";
      if (interaction.replied || interaction.deferred) {
        await interaction.followUp({ content: message, ephemeral: true });
      } else {
        await interaction.reply({ content: message, ephemeral: true });
      }
    }
  });

  client.on(Events.MessageCreate, async (message) => {
    if (message.author.bot) return;
    if (!message.guildId) return;
    const content = String(message.content || "").trim();
    if (!content.startsWith(PREFIX)) return;

    const withoutPrefix = content.slice(PREFIX.length).trim();
    if (!withoutPrefix) return;
    const [rawCommand, ...rest] = withoutPrefix.split(/\s+/);
    const command = rawCommand.toLowerCase();
    const chunkText = (text: string, maxLen = 1800): string[] => {
      const chunks: string[] = [];
      let remaining = String(text || "").trim();
      while (remaining.length > maxLen) {
        const cut = remaining.lastIndexOf("\n", maxLen);
        const idx = cut > 200 ? cut : maxLen;
        chunks.push(remaining.slice(0, idx).trim());
        remaining = remaining.slice(idx).trim();
      }
      if (remaining) chunks.push(remaining);
      return chunks;
    };

    try {
      if (command === "play") {
        const query = rest.join(" ").trim();
        if (!query) {
          await message.reply("사용법: `!play <검색어 또는 URL>`");
          return;
        }
        const result = await musicPlayer.enqueueFromMessage(message, query);
        await message.reply(result);
        return;
      }

      if (command === "queue") {
        const queue = musicPlayer.list(message.guildId);
        if (queue.length === 0) {
          await message.reply("대기열이 비어 있습니다.");
          return;
        }
        await message.reply(`현재 대기열:\n${queue.slice(0, 10).join("\n")}`);
        return;
      }

      if (command === "skip") {
        const result = await musicPlayer.skip(message.guildId);
        await message.reply(result);
        return;
      }

      if (command === "stop") {
        const result = await musicPlayer.stop(message.guildId);
        await message.reply(result);
        return;
      }

      if (command === "뉴스" || command === "news") {
        const query = rest.join(" ").trim();
        const items = await newsService.fetchGoogleNews(query, 10);
        const title = query ? `구글 뉴스: ${query}` : "구글 뉴스: 최신";
        const chunks = newsService.toDiscordMessageChunks(title, items, 1900);
        const first = await message.reply({
          content: chunks[0],
          allowedMentions: { parse: [] },
        });
        for (let i = 1; i < chunks.length; i += 1) {
          await first.channel.send({
            content: chunks[i],
            allowedMentions: { parse: [] },
          });
        }
        return;
      }

      if (command === "요약" || command === "summarize") {
        if (rest.length === 0) {
          await message.reply("사용법: `!요약 <url> [auto|fast|quality] [ai|basic|both]`");
          return;
        }
        const tokens = [...rest];
        let engine: SummarizeEngine | "both" = "basic";
        const lastToken = String(tokens[tokens.length - 1] || "").toLowerCase();
        if (lastToken === "ai" || lastToken === "basic" || lastToken === "both" || lastToken === "비교") {
          engine = lastToken === "비교" ? "both" : (lastToken as SummarizeEngine | "both");
          tokens.pop();
        }

        const maybeMode = String(tokens[tokens.length - 1] || "").toLowerCase();
        const mode: SummarizeMode =
          maybeMode === "fast" || maybeMode === "quality" || maybeMode === "auto"
            ? (maybeMode as SummarizeMode)
            : "auto";
        const urlTokens =
          maybeMode === "auto" || maybeMode === "fast" || maybeMode === "quality"
            ? tokens.slice(0, -1)
            : tokens;
        const url = urlTokens.join(" ").trim();
        if (!url) {
          await message.reply("사용법: `!요약 <url> [auto|fast|quality] [ai|basic|both]`");
          return;
        }

        const aiResult =
          engine === "ai" || engine === "both"
            ? await summarizeService.summarizeUrl(url, mode, "ai")
            : null;
        const basicResult =
          engine === "basic" || engine === "both"
            ? await summarizeService.summarizeUrl(url, mode, "basic")
            : null;

        const ref = aiResult || basicResult;
        const sections: string[] = [];
        if (aiResult) {
          sections.push(`**[AI 요약]** model=\`${aiResult.model}\` call#${aiResult.callNo}\n${aiResult.summary}`);
        }
        if (basicResult) {
          sections.push(
            `**[비AI 요약]** model=\`${basicResult.model}\` call#${basicResult.callNo}\n${basicResult.summary}`,
          );
        }
        const header = `**요약 완료**\n제목: ${ref?.title || "N/A"}\n원문: <${ref?.sourceUrl || url}>`;
        const chunks = chunkText(`${header}\n\n${sections.join("\n\n")}`, 1800);
        const first = await message.reply(chunks[0]);
        for (let i = 1; i < chunks.length; i += 1) {
          await first.channel.send(chunks[i]);
        }
        return;
      }

      if (command === "번역" || command === "translate") {
        if (rest.length === 0) {
          await message.reply(
            `사용법: \`!번역 [source->target] <텍스트>\`\n예: \`!번역 ja->ko こんにちは\`\n기본: auto->ko, 최대 ${translateService.getMaxInputChars()}자`,
          );
          return;
        }

        let source = "auto";
        let target = "ko";
        let tokens = [...rest];
        const firstToken = String(tokens[0] || "");
        if (/^[a-zA-Z-]{2,12}->[a-zA-Z-]{2,12}$/.test(firstToken)) {
          const [src, dst] = firstToken.split("->");
          source = src.toLowerCase();
          target = dst.toLowerCase();
          tokens = tokens.slice(1);
        }

        const text = tokens.join(" ").trim();
        if (!text) {
          await message.reply("번역할 텍스트를 입력하세요.");
          return;
        }

        const result = await translateService.translate(text, source, target);
        const header = `**번역 완료** engine=\`${result.engine}\` chars=${result.inputChars} chunks=${result.chunkCount}`;
        const chunks = chunkText(`${header}\n\n${result.translatedText}`, 1800);
        const first = await message.reply(chunks[0]);
        for (let i = 1; i < chunks.length; i += 1) {
          await first.channel.send(chunks[i]);
        }
        return;
      }

      if (command === "ani" || command === "영화" || command === "movie") {
        const query = rest.join(" ").trim();
        if (!query) {
          const usage = command === "ani" ? '`!ani "제목"`' : '`!영화 "제목"`';
          await message.reply(`사용법: ${usage}`);
          return;
        }
        const isMovie = command === "영화" || command === "movie";
        const list = isMovie
          ? await gdsMovieService.searchByTitle(query, 5)
          : await gdsAnimeService.searchByTitle(query, 5);
        if (list.length === 0) {
          await message.reply(`검색 결과 없음: ${query}`);
          return;
        }

        const trim = (s: string, n: number) => (s.length > n ? `${s.slice(0, n - 1)}…` : s);
        const buildExternalVideoModalLink = (item: {
          name?: string;
          path?: string;
          source_id?: string | number;
          stream_url?: string;
          meta_poster?: string;
          poster?: string;
          thumb?: string;
        }): string => {
          const toUrlSafeBase64 = (input: string): string => {
            const b64 = Buffer.from(String(input || ""), "utf8").toString("base64");
            return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
          };
          try {
            const stream = String(item.stream_url || "").trim();
            if (!stream) return "";
            const externalPlayer = String(env.EXTERNAL_VIDEO_PLAYER_URL || "").trim();
            if (!externalPlayer) return "";
            const p = new URL(externalPlayer);
            const explorerBase = String(env.GDS_DVIEWER_BASE_URL || "").trim();
            let apikey = String(env.GDS_DVIEWER_API_KEY || "").trim();
            if (!apikey && item.stream_url) {
              try {
                const su = new URL(item.stream_url);
                apikey = String(su.searchParams.get("apikey") || "").trim();
              } catch {}
            }
            const sid = String(item.source_id ?? env.GDS_DVIEWER_SOURCE_ID ?? "0").trim() || "0";
            const autoplayPath = String(item.path || "").trim();
            if (explorerBase && autoplayPath) {
              p.searchParams.set("b", toUrlSafeBase64(autoplayPath));
              p.searchParams.set("s", sid);
              if (apikey) p.searchParams.set("k", apikey);
            }
            return p.toString();
          } catch {
            return "";
          }
        };
        const resolvePosterUrl = (item: {
          path?: string;
          source_id?: string | number;
          meta_poster?: string;
          poster?: string;
          thumb?: string;
          stream_url?: string;
        }): string => {
          const isPlaceholderPoster = (url: string): boolean => {
            const u = String(url || "").toLowerCase();
            if (!u) return true;
            return (
              u.includes("no_poster") ||
              u.includes("no-image") ||
              u.includes("no_image") ||
              u.includes("placeholder") ||
              u.endsWith("/no_poster.png") ||
              u.endsWith("/no_poster.svg")
            );
          };
          let poster =
            String(item.meta_poster || "").trim() ||
            String(item.poster || "").trim() ||
            String(item.thumb || "").trim();
          if (poster && !isPlaceholderPoster(poster)) {
            try {
              if (poster.startsWith("//")) return `https:${poster}`;
              if (poster.startsWith("/")) {
                if (!env.GDS_DVIEWER_BASE_URL) return poster;
                const base = new URL(env.GDS_DVIEWER_BASE_URL);
                return `${base.origin}${poster}`;
              }
              return poster;
            } catch {
              return poster;
            }
          }

          // Fallback 1: path 기반 thumbnail endpoint 사용 (가장 안정적)
          try {
            const base = String(env.GDS_DVIEWER_BASE_URL || "").trim();
            if (base && item.path) {
              const bu = new URL(base);
              const pkg = (bu.pathname.split("/").filter(Boolean)[0] || "gds_dviewer").trim();
              const t = new URL(`${bu.origin}/${pkg}/normal/thumbnail`);
              t.searchParams.set("path", String(item.path || ""));
              const sid = String(item.source_id ?? env.GDS_DVIEWER_SOURCE_ID ?? "0").trim();
              if (sid) t.searchParams.set("source_id", sid);
              let apikey = String(env.GDS_DVIEWER_API_KEY || "").trim();
              if (!apikey && item.stream_url) {
                try {
                  const su = new URL(item.stream_url);
                  apikey = String(su.searchParams.get("apikey") || "").trim();
                } catch {}
              }
              if (apikey) t.searchParams.set("apikey", apikey);
              t.searchParams.set("w", "400");
              return t.toString();
            }
          } catch {}

          // Fallback: stream_url의 bpath로 thumbnail URL 생성
          try {
            if (!item.stream_url) return "";
            const u = new URL(item.stream_url);
            const bpath = u.searchParams.get("bpath");
            if (!bpath) return "";
            const thumb = new URL(u.toString());
            thumb.pathname = thumb.pathname.replace(/\/stream$/, "/thumbnail");
            thumb.search = "";
            thumb.searchParams.set("bpath", bpath);
            const sourceId = u.searchParams.get("source_id") || env.GDS_DVIEWER_SOURCE_ID;
            if (sourceId) thumb.searchParams.set("source_id", sourceId);
            const apikey = u.searchParams.get("apikey") || env.GDS_DVIEWER_API_KEY || "";
            if (apikey) thumb.searchParams.set("apikey", apikey);
            return thumb.toString();
          } catch {
            return "";
          }
        };
        const getEpisode = (text: string): string => {
          const src = String(text || "");
          const patterns = [
            /(?:^|[\s._-])(E\d{1,4})(?:$|[\s._-])/i,
            /(EP\.?\s*\d{1,4})/i,
            /(제\s*\d{1,4}\s*화)/i,
            /(\d{1,4}\s*화)/i,
            /(\bS\d+\s*E\d+\b)/i,
          ];
          for (const p of patterns) {
            const m = src.match(p);
            if (m?.[1]) return m[1].replace(/\s+/g, " ").trim().toUpperCase();
          }
          return "-";
        };
        const getCleanTitle = (name: string): string => {
          let t = String(name || "").replace(/\.[a-z0-9]{2,5}$/i, "");
          t = t.replace(/(EP\.?\s*\d{1,4}|제\s*\d{1,4}\s*화|\d{1,4}\s*화|\bS\d+\s*E\d+\b)/gi, "");
          t = t.replace(/[\[\(\{].*?[\]\)\}]/g, " ");
          t = t.replace(/\s+/g, " ").trim();
          return t || String(name || "제목 없음");
        };
        const summaryEmbed = new EmbedBuilder()
          .setColor(0x4f46e5)
          .setTitle(isMovie ? "영화 검색 결과" : "ANI 검색 결과")
          .setDescription(`query: \`${trim(query, 80)}\`\n총 ${list.length}건`)
          .setFooter({ text: "gds_dviewer search" })
          .setTimestamp(new Date());

        const itemEmbeds = list.slice(0, 3).map((item, idx) => {
          const poster = resolvePosterUrl(item);
          const ep = getEpisode(`${item.name} ${item.path}`);
          const cleanTitle = getCleanTitle(item.name || "");
          const descLines = [`제목: **${trim(cleanTitle, 100)}**`, `회차: **${ep}**`];
          const modalLink = item.stream_url ? buildExternalVideoModalLink(item) : "";
          if (modalLink) {
            descLines.push(`재생: [외부 플레이어 열기](${modalLink})`);
          } else if (item.stream_url) {
            descLines.push("외부 플레이어 링크 설정 필요");
          } else {
            descLines.push("재생 링크 없음");
          }
          const e = new EmbedBuilder()
            .setColor(0x1d4ed8)
            .setTitle(trim(`${idx + 1}`, 32))
            .setDescription(trim(descLines.join("\n"), 1200));
          if (poster) {
            e.setThumbnail(poster);
          }
          (e as any).__modalLink = modalLink;
          return e;
        });

        await message.reply({ embeds: [summaryEmbed, ...itemEmbeds] });
        return;
      }
    } catch (error) {
      logger.error({ error, command, from: "prefix" }, "Prefix command execution failed");
      await message.reply(
        `실행 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`,
      );
    }
  });

  startNotifyWorker(async (job) => {
    const channel = await client.channels.fetch(job.channelId);
    if (!channel || channel.type !== ChannelType.GuildText) return;
    await (channel as TextChannel).send(job.message);
  });

  return {
    client,
    async start() {
      await client.login(env.DISCORD_TOKEN);
    },
  };
}
