import type { Redis } from 'ioredis';
import {
  capsRatio,
  countEmojis,
  extractUrls,
  hasZalgo,
  isDiscordInvite,
  normalizeHost,
  type AutoModRuleType,
  type AutoModThreshold,
  type AutoModWordList
} from '@nexumi/shared';

const PHISHING_CACHE_KEY = 'automod:phishing-domains';

export type FilterContext = {
  content: string;
  authorId: string;
  channelId: string;
  roleIds: string[];
  redis: Redis;
  phishingDomains: Set<string>;
};

export type FilterResult = {
  matched: boolean;
  ruleType: AutoModRuleType;
  reason: string;
};

export async function loadPhishingDomains(redis: Redis): Promise<Set<string>> {
  const cached = await redis.smembers(PHISHING_CACHE_KEY);
  if (cached.length > 0) {
    return new Set(cached);
  }
  return new Set<string>();
}

export async function refreshPhishingDomains(redis: Redis): Promise<number> {
  const response = await fetch(
    'https://raw.githubusercontent.com/Discord-AntiScam/scam-links/main/list.txt'
  );
  if (!response.ok) {
    throw new Error(`Failed to fetch phishing list: ${response.status}`);
  }
  const text = await response.text();
  const domains = text
    .split('\n')
    .map((line) => line.trim().toLowerCase())
    .filter((line) => line.length > 0 && !line.startsWith('#'));

  const pipeline = redis.pipeline();
  pipeline.del(PHISHING_CACHE_KEY);
  if (domains.length > 0) {
    pipeline.sadd(PHISHING_CACHE_KEY, ...domains);
  }
  pipeline.expire(PHISHING_CACHE_KEY, 60 * 60 * 24);
  await pipeline.exec();
  return domains.length;
}

async function checkSpam(
  ctx: FilterContext,
  threshold: AutoModThreshold
): Promise<FilterResult | null> {
  const maxMessages = threshold.maxMessages ?? 5;
  const windowSeconds = threshold.windowSeconds ?? 5;
  const key = `automod:spam:${ctx.authorId}:${ctx.channelId}`;
  const count = await ctx.redis.incr(key);
  if (count === 1) {
    await ctx.redis.expire(key, windowSeconds);
  }
  if (count > maxMessages) {
    return { matched: true, ruleType: 'SPAM', reason: 'Message rate exceeded' };
  }
  return null;
}

function checkMassMention(content: string, threshold: AutoModThreshold): FilterResult | null {
  const maxMentions = threshold.maxMentions ?? 5;
  const mentions = (content.match(/<@[!&]?\d+>/g) ?? []).length;
  if (mentions >= maxMentions) {
    return { matched: true, ruleType: 'MASS_MENTION', reason: `${mentions} mentions` };
  }
  return null;
}

function checkCaps(content: string, threshold: AutoModThreshold): FilterResult | null {
  const minLength = threshold.minLength ?? 10;
  const capsPercent = threshold.capsPercent ?? 70;
  if (content.length < minLength) {
    return null;
  }
  if (capsRatio(content) >= capsPercent) {
    return { matched: true, ruleType: 'CAPS', reason: 'Excessive caps' };
  }
  return null;
}

function checkInviteLink(content: string): FilterResult | null {
  if (isDiscordInvite(content)) {
    return { matched: true, ruleType: 'INVITE_LINK', reason: 'Discord invite link' };
  }
  return null;
}

function checkExternalLink(content: string, threshold: AutoModThreshold): FilterResult | null {
  const whitelist = (threshold.linkWhitelist ?? []).map((h) => h.toLowerCase());
  const blacklist = (threshold.linkBlacklist ?? []).map((h) => h.toLowerCase());
  const urls = extractUrls(content);
  for (const url of urls) {
    const host = normalizeHost(url);
    if (!host) {
      continue;
    }
    if (blacklist.some((entry) => host.includes(entry))) {
      return { matched: true, ruleType: 'EXTERNAL_LINK', reason: `Blocked host: ${host}` };
    }
    if (whitelist.length > 0 && !whitelist.some((entry) => host.includes(entry))) {
      return { matched: true, ruleType: 'EXTERNAL_LINK', reason: `Non-whitelisted host: ${host}` };
    }
  }
  return null;
}

function checkWordFilter(content: string, wordList: AutoModWordList): FilterResult | null {
  const lower = content.toLowerCase();
  for (const word of wordList.words) {
    if (word.length > 0 && lower.includes(word.toLowerCase())) {
      return { matched: true, ruleType: 'WORD_FILTER', reason: 'Blocked word' };
    }
  }
  for (const pattern of wordList.regexPatterns) {
    try {
      const regex = new RegExp(pattern, 'i');
      if (regex.test(content)) {
        return { matched: true, ruleType: 'WORD_FILTER', reason: 'Blocked pattern' };
      }
    } catch {
      continue;
    }
  }
  return null;
}

async function checkDuplicate(
  ctx: FilterContext,
  threshold: AutoModThreshold
): Promise<FilterResult | null> {
  const duplicateCount = threshold.duplicateCount ?? 3;
  const windowSeconds = threshold.windowSeconds ?? 30;
  const normalized = ctx.content.trim().toLowerCase();
  if (normalized.length < 3) {
    return null;
  }
  const key = `automod:dup:${ctx.authorId}:${ctx.channelId}`;
  const count = await ctx.redis.incr(`${key}:${normalized}`);
  if (count === 1) {
    await ctx.redis.expire(`${key}:${normalized}`, windowSeconds);
  }
  if (count >= duplicateCount) {
    return { matched: true, ruleType: 'DUPLICATE', reason: 'Duplicate message' };
  }
  return null;
}

function checkEmojiSpam(content: string, threshold: AutoModThreshold): FilterResult | null {
  const maxEmojis = threshold.maxEmojis ?? 10;
  const emojiCount = countEmojis(content);
  if (emojiCount >= maxEmojis) {
    return { matched: true, ruleType: 'EMOJI_SPAM', reason: `${emojiCount} emojis` };
  }
  return null;
}

function checkZalgo(content: string): FilterResult | null {
  if (hasZalgo(content)) {
    return { matched: true, ruleType: 'ZALGO', reason: 'Zalgo text detected' };
  }
  return null;
}

function checkPhishing(content: string, domains: Set<string>): FilterResult | null {
  if (domains.size === 0) {
    return null;
  }
  for (const url of extractUrls(content)) {
    const host = normalizeHost(url);
    if (!host) {
      continue;
    }
    for (const domain of domains) {
      if (host === domain || host.endsWith(`.${domain}`)) {
        return { matched: true, ruleType: 'PHISHING', reason: `Phishing domain: ${host}` };
      }
    }
  }
  return null;
}

export async function evaluateRule(
  ruleType: AutoModRuleType,
  ctx: FilterContext,
  threshold: AutoModThreshold,
  wordList: AutoModWordList
): Promise<FilterResult | null> {
  switch (ruleType) {
    case 'SPAM':
      return checkSpam(ctx, threshold);
    case 'MASS_MENTION':
      return checkMassMention(ctx.content, threshold);
    case 'CAPS':
      return checkCaps(ctx.content, threshold);
    case 'INVITE_LINK':
      return checkInviteLink(ctx.content);
    case 'EXTERNAL_LINK':
      return checkExternalLink(ctx.content, threshold);
    case 'WORD_FILTER':
      return checkWordFilter(ctx.content, wordList);
    case 'DUPLICATE':
      return checkDuplicate(ctx, threshold);
    case 'EMOJI_SPAM':
      return checkEmojiSpam(ctx.content, threshold);
    case 'ZALGO':
      return checkZalgo(ctx.content);
    case 'PHISHING':
      return checkPhishing(ctx.content, ctx.phishingDomains);
    default:
      return null;
  }
}
