import type { WebClient } from "@slack/web-api";
import { createSlackWebClient } from "./client.js";
import {
  collectSlackCursorItems,
  resolveSlackAllowlistEntries,
} from "./resolve-allowlist-common.js";

export type SlackUserLookup = {
  id: string;
  name: string;
  displayName?: string;
  realName?: string;
  email?: string;
  deleted: boolean;
  isBot: boolean;
  isAppUser: boolean;
};

export type SlackUserResolution = {
  input: string;
  resolved: boolean;
  id?: string;
  name?: string;
  email?: string;
  deleted?: boolean;
  isBot?: boolean;
  note?: string;
};

type SlackListUsersResponse = {
  members?: Array<{
    id?: string;
    name?: string;
    deleted?: boolean;
    is_bot?: boolean;
    is_app_user?: boolean;
    real_name?: string;
    profile?: {
      display_name?: string;
      real_name?: string;
      email?: string;
    };
  }>;
  response_metadata?: { next_cursor?: string };
};

function parseSlackUserInput(raw: string): { id?: string; name?: string; email?: string } {
  const trimmed = raw.trim();
  if (!trimmed) {
    return {};
  }
  const mention = trimmed.match(/^<@([A-Z0-9]+)>$/i);
  if (mention) {
    return { id: mention[1]?.toUpperCase() };
  }
  const prefixed = trimmed.replace(/^(slack:|user:)/i, "");
  if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) {
    return { id: prefixed.toUpperCase() };
  }
  if (trimmed.includes("@") && !trimmed.startsWith("@")) {
    return { email: trimmed.toLowerCase() };
  }
  const name = trimmed.replace(/^@/, "").trim();
  return name ? { name } : {};
}

async function listSlackUsers(client: WebClient): Promise<SlackUserLookup[]> {
  return collectSlackCursorItems({
    fetchPage: async (cursor) =>
      (await client.users.list({
        limit: 200,
        cursor,
      })) as SlackListUsersResponse,
    collectPageItems: (res) =>
      (res.members ?? [])
        .map((member) => {
          const id = member.id?.trim();
          const name = member.name?.trim();
          if (!id || !name) {
            return null;
          }
          const profile = member.profile ?? {};
          return {
            id,
            name,
            displayName: profile.display_name?.trim() || undefined,
            realName: profile.real_name?.trim() || member.real_name?.trim() || undefined,
            email: profile.email?.trim()?.toLowerCase() || undefined,
            deleted: Boolean(member.deleted),
            isBot: Boolean(member.is_bot),
            isAppUser: Boolean(member.is_app_user),
          } satisfies SlackUserLookup;
        })
        .filter(Boolean) as SlackUserLookup[],
  });
}

function scoreSlackUser(user: SlackUserLookup, match: { name?: string; email?: string }): number {
  let score = 0;
  if (!user.deleted) {
    score += 3;
  }
  if (!user.isBot && !user.isAppUser) {
    score += 2;
  }
  if (match.email && user.email === match.email) {
    score += 5;
  }
  if (match.name) {
    const target = match.name.toLowerCase();
    const candidates = [user.name, user.displayName, user.realName]
      .map((value) => value?.toLowerCase())
      .filter(Boolean) as string[];
    if (candidates.some((value) => value === target)) {
      score += 2;
    }
  }
  return score;
}

function resolveSlackUserFromMatches(
  input: string,
  matches: SlackUserLookup[],
  parsed: { name?: string; email?: string },
): SlackUserResolution {
  const scored = matches
    .map((user) => ({ user, score: scoreSlackUser(user, parsed) }))
    .toSorted((a, b) => b.score - a.score);
  const best = scored[0]?.user ?? matches[0];
  return {
    input,
    resolved: true,
    id: best.id,
    name: best.displayName ?? best.realName ?? best.name,
    email: best.email,
    deleted: best.deleted,
    isBot: best.isBot,
    note: matches.length > 1 ? "multiple matches; chose best" : undefined,
  };
}

export async function resolveSlackUserAllowlist(params: {
  token: string;
  entries: string[];
  client?: WebClient;
}): Promise<SlackUserResolution[]> {
  const client = params.client ?? createSlackWebClient(params.token);
  const users = await listSlackUsers(client);
  return resolveSlackAllowlistEntries<
    { id?: string; name?: string; email?: string },
    SlackUserLookup,
    SlackUserResolution
  >({
    entries: params.entries,
    lookup: users,
    parseInput: parseSlackUserInput,
    findById: (lookup, id) => lookup.find((user) => user.id === id),
    buildIdResolved: ({ input, parsed, match }) => ({
      input,
      resolved: true,
      id: parsed.id,
      name: match?.displayName ?? match?.realName ?? match?.name,
      email: match?.email,
      deleted: match?.deleted,
      isBot: match?.isBot,
    }),
    resolveNonId: ({ input, parsed, lookup }) => {
      if (parsed.email) {
        const matches = lookup.filter((user) => user.email === parsed.email);
        if (matches.length > 0) {
          return resolveSlackUserFromMatches(input, matches, parsed);
        }
      }
      if (parsed.name) {
        const target = parsed.name.toLowerCase();
        const matches = lookup.filter((user) => {
          const candidates = [user.name, user.displayName, user.realName]
            .map((value) => value?.toLowerCase())
            .filter(Boolean) as string[];
          return candidates.includes(target);
        });
        if (matches.length > 0) {
          return resolveSlackUserFromMatches(input, matches, parsed);
        }
      }
      return undefined;
    },
    buildUnresolved: (input) => ({ input, resolved: false }),
  });
}
