import type { ChannelCapabilities, ChannelPlugin } from "../../channels/plugins/types.js";
import type { OpenClawConfig } from "../../config/config.js";
import { resolveChannelDefaultAccountId } from "../../channels/plugins/helpers.js";
import { getChannelPlugin, listChannelPlugins } from "../../channels/plugins/index.js";
import { fetchChannelPermissionsDiscord } from "../../discord/send.js";
import { parseDiscordTarget } from "../../discord/targets.js";
import { danger } from "../../globals.js";
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
import { fetchSlackScopes, type SlackScopesResult } from "../../slack/scopes.js";
import { theme } from "../../terminal/theme.js";
import { formatChannelAccountLabel, requireValidConfig } from "./shared.js";

export type ChannelsCapabilitiesOptions = {
  channel?: string;
  account?: string;
  target?: string;
  timeout?: string;
  json?: boolean;
};

type DiscordTargetSummary = {
  raw?: string;
  normalized?: string;
  kind?: "channel" | "user";
  channelId?: string;
};

type DiscordPermissionsReport = {
  channelId?: string;
  guildId?: string;
  isDm?: boolean;
  channelType?: number;
  permissions?: string[];
  missingRequired?: string[];
  raw?: string;
  error?: string;
};

type ChannelCapabilitiesReport = {
  channel: string;
  accountId: string;
  accountName?: string;
  configured?: boolean;
  enabled?: boolean;
  support?: ChannelCapabilities;
  actions?: string[];
  probe?: unknown;
  slackScopes?: Array<{
    tokenType: "bot" | "user";
    result: SlackScopesResult;
  }>;
  target?: DiscordTargetSummary;
  channelPermissions?: DiscordPermissionsReport;
};

const REQUIRED_DISCORD_PERMISSIONS = ["ViewChannel", "SendMessages"] as const;

const TEAMS_GRAPH_PERMISSION_HINTS: Record<string, string> = {
  "ChannelMessage.Read.All": "channel history",
  "Chat.Read.All": "chat history",
  "Channel.ReadBasic.All": "channel list",
  "Team.ReadBasic.All": "team list",
  "TeamsActivity.Read.All": "teams activity",
  "Sites.Read.All": "files (SharePoint)",
  "Files.Read.All": "files (OneDrive)",
};

function normalizeTimeout(raw: unknown, fallback = 10_000) {
  const value = typeof raw === "string" ? Number(raw) : Number(raw);
  if (!Number.isFinite(value) || value <= 0) {
    return fallback;
  }
  return value;
}

function formatSupport(capabilities?: ChannelCapabilities) {
  if (!capabilities) {
    return "unknown";
  }
  const bits: string[] = [];
  if (capabilities.chatTypes?.length) {
    bits.push(`chatTypes=${capabilities.chatTypes.join(",")}`);
  }
  if (capabilities.polls) {
    bits.push("polls");
  }
  if (capabilities.reactions) {
    bits.push("reactions");
  }
  if (capabilities.edit) {
    bits.push("edit");
  }
  if (capabilities.unsend) {
    bits.push("unsend");
  }
  if (capabilities.reply) {
    bits.push("reply");
  }
  if (capabilities.effects) {
    bits.push("effects");
  }
  if (capabilities.groupManagement) {
    bits.push("groupManagement");
  }
  if (capabilities.threads) {
    bits.push("threads");
  }
  if (capabilities.media) {
    bits.push("media");
  }
  if (capabilities.nativeCommands) {
    bits.push("nativeCommands");
  }
  if (capabilities.blockStreaming) {
    bits.push("blockStreaming");
  }
  return bits.length ? bits.join(" ") : "none";
}

function summarizeDiscordTarget(raw?: string): DiscordTargetSummary | undefined {
  if (!raw) {
    return undefined;
  }
  const target = parseDiscordTarget(raw, { defaultKind: "channel" });
  if (!target) {
    return { raw };
  }
  if (target.kind === "channel") {
    return {
      raw,
      normalized: target.normalized,
      kind: "channel",
      channelId: target.id,
    };
  }
  if (target.kind === "user") {
    return {
      raw,
      normalized: target.normalized,
      kind: "user",
    };
  }
  return { raw, normalized: target.normalized };
}

function formatDiscordIntents(intents?: {
  messageContent?: string;
  guildMembers?: string;
  presence?: string;
}) {
  if (!intents) {
    return "unknown";
  }
  return [
    `messageContent=${intents.messageContent ?? "unknown"}`,
    `guildMembers=${intents.guildMembers ?? "unknown"}`,
    `presence=${intents.presence ?? "unknown"}`,
  ].join(" ");
}

function formatProbeLines(channelId: string, probe: unknown): string[] {
  const lines: string[] = [];
  if (!probe || typeof probe !== "object") {
    return lines;
  }
  const probeObj = probe as Record<string, unknown>;

  if (channelId === "discord") {
    const bot = probeObj.bot as { id?: string | null; username?: string | null } | undefined;
    if (bot?.username) {
      const botId = bot.id ? ` (${bot.id})` : "";
      lines.push(`Bot: ${theme.accent(`@${bot.username}`)}${botId}`);
    }
    const app = probeObj.application as { intents?: Record<string, unknown> } | undefined;
    if (app?.intents) {
      lines.push(`Intents: ${formatDiscordIntents(app.intents)}`);
    }
  }

  if (channelId === "telegram") {
    const bot = probeObj.bot as { username?: string | null; id?: number | null } | undefined;
    if (bot?.username) {
      const botId = bot.id ? ` (${bot.id})` : "";
      lines.push(`Bot: ${theme.accent(`@${bot.username}`)}${botId}`);
    }
    const flags: string[] = [];
    const canJoinGroups = (bot as { canJoinGroups?: boolean | null })?.canJoinGroups;
    const canReadAll = (bot as { canReadAllGroupMessages?: boolean | null })
      ?.canReadAllGroupMessages;
    const inlineQueries = (bot as { supportsInlineQueries?: boolean | null })
      ?.supportsInlineQueries;
    if (typeof canJoinGroups === "boolean") {
      flags.push(`joinGroups=${canJoinGroups}`);
    }
    if (typeof canReadAll === "boolean") {
      flags.push(`readAllGroupMessages=${canReadAll}`);
    }
    if (typeof inlineQueries === "boolean") {
      flags.push(`inlineQueries=${inlineQueries}`);
    }
    if (flags.length > 0) {
      lines.push(`Flags: ${flags.join(" ")}`);
    }
    const webhook = probeObj.webhook as { url?: string | null } | undefined;
    if (webhook?.url !== undefined) {
      lines.push(`Webhook: ${webhook.url || "none"}`);
    }
  }

  if (channelId === "slack") {
    const bot = probeObj.bot as { name?: string } | undefined;
    const team = probeObj.team as { name?: string; id?: string } | undefined;
    if (bot?.name) {
      lines.push(`Bot: ${theme.accent(`@${bot.name}`)}`);
    }
    if (team?.name || team?.id) {
      const id = team?.id ? ` (${team.id})` : "";
      lines.push(`Team: ${team?.name ?? "unknown"}${id}`);
    }
  }

  if (channelId === "signal") {
    const version = probeObj.version as string | null | undefined;
    if (version) {
      lines.push(`Signal daemon: ${version}`);
    }
  }

  if (channelId === "msteams") {
    const appId = typeof probeObj.appId === "string" ? probeObj.appId.trim() : "";
    if (appId) {
      lines.push(`App: ${theme.accent(appId)}`);
    }
    const graph = probeObj.graph as
      | { ok?: boolean; roles?: unknown; scopes?: unknown; error?: string }
      | undefined;
    if (graph) {
      const roles = Array.isArray(graph.roles)
        ? graph.roles.map((role) => String(role).trim()).filter(Boolean)
        : [];
      const scopes =
        typeof graph.scopes === "string"
          ? graph.scopes
              .split(/\s+/)
              .map((scope) => scope.trim())
              .filter(Boolean)
          : Array.isArray(graph.scopes)
            ? graph.scopes.map((scope) => String(scope).trim()).filter(Boolean)
            : [];
      if (graph.ok === false) {
        lines.push(`Graph: ${theme.error(graph.error ?? "failed")}`);
      } else if (roles.length > 0 || scopes.length > 0) {
        const formatPermission = (permission: string) => {
          const hint = TEAMS_GRAPH_PERMISSION_HINTS[permission];
          return hint ? `${permission} (${hint})` : permission;
        };
        if (roles.length > 0) {
          lines.push(`Graph roles: ${roles.map(formatPermission).join(", ")}`);
        }
        if (scopes.length > 0) {
          lines.push(`Graph scopes: ${scopes.map(formatPermission).join(", ")}`);
        }
      } else if (graph.ok === true) {
        lines.push("Graph: ok");
      }
    }
  }

  const ok = typeof probeObj.ok === "boolean" ? probeObj.ok : undefined;
  if (ok === true && lines.length === 0) {
    lines.push("Probe: ok");
  }
  if (ok === false) {
    const error =
      typeof probeObj.error === "string" && probeObj.error ? ` (${probeObj.error})` : "";
    lines.push(`Probe: ${theme.error(`failed${error}`)}`);
  }
  return lines;
}

async function buildDiscordPermissions(params: {
  account: { token?: string; accountId?: string };
  target?: string;
}): Promise<{ target?: DiscordTargetSummary; report?: DiscordPermissionsReport }> {
  const target = summarizeDiscordTarget(params.target?.trim());
  if (!target) {
    return {};
  }
  if (target.kind !== "channel" || !target.channelId) {
    return {
      target,
      report: {
        error: "Target looks like a DM user; pass channel:<id> to audit channel permissions.",
      },
    };
  }
  const token = params.account.token?.trim();
  if (!token) {
    return {
      target,
      report: {
        channelId: target.channelId,
        error: "Discord bot token missing for permission audit.",
      },
    };
  }
  try {
    const perms = await fetchChannelPermissionsDiscord(target.channelId, {
      token,
      accountId: params.account.accountId ?? undefined,
    });
    const missing = REQUIRED_DISCORD_PERMISSIONS.filter(
      (permission) => !perms.permissions.includes(permission),
    );
    return {
      target,
      report: {
        channelId: perms.channelId,
        guildId: perms.guildId,
        isDm: perms.isDm,
        channelType: perms.channelType,
        permissions: perms.permissions,
        missingRequired: missing.length ? missing : [],
        raw: perms.raw,
      },
    };
  } catch (err) {
    return {
      target,
      report: {
        channelId: target.channelId,
        error: err instanceof Error ? err.message : String(err),
      },
    };
  }
}

async function resolveChannelReports(params: {
  plugin: ChannelPlugin;
  cfg: OpenClawConfig;
  timeoutMs: number;
  accountOverride?: string;
  target?: string;
}): Promise<ChannelCapabilitiesReport[]> {
  const { plugin, cfg, timeoutMs } = params;
  const accountIds = params.accountOverride
    ? [params.accountOverride]
    : (() => {
        const ids = plugin.config.listAccountIds(cfg);
        return ids.length > 0
          ? ids
          : [resolveChannelDefaultAccountId({ plugin, cfg, accountIds: ids })];
      })();
  const reports: ChannelCapabilitiesReport[] = [];
  const listedActions = plugin.actions?.listActions?.({ cfg }) ?? [];
  const actions = Array.from(
    new Set<string>(["send", "broadcast", ...listedActions.map((action) => String(action))]),
  );

  for (const accountId of accountIds) {
    const resolvedAccount = plugin.config.resolveAccount(cfg, accountId);
    const configured = plugin.config.isConfigured
      ? await plugin.config.isConfigured(resolvedAccount, cfg)
      : Boolean(resolvedAccount);
    const enabled = plugin.config.isEnabled
      ? plugin.config.isEnabled(resolvedAccount, cfg)
      : (resolvedAccount as { enabled?: boolean }).enabled !== false;
    let probe: unknown;
    if (configured && enabled && plugin.status?.probeAccount) {
      try {
        probe = await plugin.status.probeAccount({
          account: resolvedAccount,
          timeoutMs,
          cfg,
        });
      } catch (err) {
        probe = { ok: false, error: err instanceof Error ? err.message : String(err) };
      }
    }

    let slackScopes: ChannelCapabilitiesReport["slackScopes"];
    if (plugin.id === "slack" && configured && enabled) {
      const botToken = (resolvedAccount as { botToken?: string }).botToken?.trim();
      const userToken = (
        resolvedAccount as { config?: { userToken?: string } }
      ).config?.userToken?.trim();
      const scopeReports: NonNullable<ChannelCapabilitiesReport["slackScopes"]> = [];
      if (botToken) {
        scopeReports.push({
          tokenType: "bot",
          result: await fetchSlackScopes(botToken, timeoutMs),
        });
      } else {
        scopeReports.push({
          tokenType: "bot",
          result: { ok: false, error: "Slack bot token missing." },
        });
      }
      if (userToken) {
        scopeReports.push({
          tokenType: "user",
          result: await fetchSlackScopes(userToken, timeoutMs),
        });
      }
      slackScopes = scopeReports;
    }

    let discordTarget: DiscordTargetSummary | undefined;
    let discordPermissions: DiscordPermissionsReport | undefined;
    if (plugin.id === "discord" && params.target) {
      const perms = await buildDiscordPermissions({
        account: resolvedAccount as { token?: string; accountId?: string },
        target: params.target,
      });
      discordTarget = perms.target;
      discordPermissions = perms.report;
    }

    reports.push({
      channel: plugin.id,
      accountId,
      accountName:
        typeof (resolvedAccount as { name?: string }).name === "string"
          ? (resolvedAccount as { name?: string }).name?.trim() || undefined
          : undefined,
      configured,
      enabled,
      support: plugin.capabilities,
      probe,
      target: discordTarget,
      channelPermissions: discordPermissions,
      actions,
      slackScopes,
    });
  }
  return reports;
}

export async function channelsCapabilitiesCommand(
  opts: ChannelsCapabilitiesOptions,
  runtime: RuntimeEnv = defaultRuntime,
) {
  const cfg = await requireValidConfig(runtime);
  if (!cfg) {
    return;
  }
  const timeoutMs = normalizeTimeout(opts.timeout, 10_000);
  const rawChannel = typeof opts.channel === "string" ? opts.channel.trim().toLowerCase() : "";
  const rawTarget = typeof opts.target === "string" ? opts.target.trim() : "";

  if (opts.account && (!rawChannel || rawChannel === "all")) {
    runtime.error(danger("--account requires a specific --channel."));
    runtime.exit(1);
    return;
  }
  if (rawTarget && rawChannel !== "discord") {
    runtime.error(danger("--target requires --channel discord."));
    runtime.exit(1);
    return;
  }

  const plugins = listChannelPlugins();
  const selected =
    !rawChannel || rawChannel === "all"
      ? plugins
      : (() => {
          const plugin = getChannelPlugin(rawChannel);
          if (!plugin) {
            return null;
          }
          return [plugin];
        })();

  if (!selected || selected.length === 0) {
    runtime.error(danger(`Unknown channel "${rawChannel}".`));
    runtime.exit(1);
    return;
  }

  const reports: ChannelCapabilitiesReport[] = [];
  for (const plugin of selected) {
    const accountOverride = opts.account?.trim() || undefined;
    reports.push(
      ...(await resolveChannelReports({
        plugin,
        cfg,
        timeoutMs,
        accountOverride,
        target: rawTarget && plugin.id === "discord" ? rawTarget : undefined,
      })),
    );
  }

  if (opts.json) {
    runtime.log(JSON.stringify({ channels: reports }, null, 2));
    return;
  }

  const lines: string[] = [];
  for (const report of reports) {
    const label = formatChannelAccountLabel({
      channel: report.channel,
      accountId: report.accountId,
      name: report.accountName,
      channelStyle: theme.accent,
      accountStyle: theme.heading,
    });
    lines.push(theme.heading(label));
    lines.push(`Support: ${formatSupport(report.support)}`);
    if (report.actions && report.actions.length > 0) {
      lines.push(`Actions: ${report.actions.join(", ")}`);
    }
    if (report.configured === false || report.enabled === false) {
      const configuredLabel = report.configured === false ? "not configured" : "configured";
      const enabledLabel = report.enabled === false ? "disabled" : "enabled";
      lines.push(`Status: ${configuredLabel}, ${enabledLabel}`);
    }
    const probeLines = formatProbeLines(report.channel, report.probe);
    if (probeLines.length > 0) {
      lines.push(...probeLines);
    } else if (report.configured && report.enabled) {
      lines.push(theme.muted("Probe: unavailable"));
    }
    if (report.channel === "slack" && report.slackScopes) {
      for (const entry of report.slackScopes) {
        const source = entry.result.source ? ` (${entry.result.source})` : "";
        const label = entry.tokenType === "user" ? "User scopes" : "Bot scopes";
        if (entry.result.ok && entry.result.scopes?.length) {
          lines.push(`${label}${source}: ${entry.result.scopes.join(", ")}`);
        } else if (entry.result.error) {
          lines.push(`${label}: ${theme.error(entry.result.error)}`);
        }
      }
    }
    if (report.channel === "discord" && report.channelPermissions) {
      const perms = report.channelPermissions;
      if (perms.error) {
        lines.push(`Permissions: ${theme.error(perms.error)}`);
      } else {
        const list = perms.permissions?.length ? perms.permissions.join(", ") : "none";
        const label = perms.channelId ? ` (${perms.channelId})` : "";
        lines.push(`Permissions${label}: ${list}`);
        if (perms.missingRequired && perms.missingRequired.length > 0) {
          lines.push(`${theme.warn("Missing required:")} ${perms.missingRequired.join(", ")}`);
        } else {
          lines.push(theme.success("Missing required: none"));
        }
      }
    } else if (report.channel === "discord" && rawTarget && !report.channelPermissions) {
      lines.push(theme.muted("Permissions: skipped (no target)."));
    }
    lines.push("");
  }

  runtime.log(lines.join("\n").trimEnd());
}
