import { isAllowedParsedChatSender } from "openclaw/plugin-sdk/allow-from";
import {
  parseChatAllowTargetPrefixes,
  parseChatTargetPrefixesOrThrow,
  type ParsedChatTarget,
  resolveServicePrefixedAllowTarget,
  resolveServicePrefixedTarget,
} from "openclaw/plugin-sdk/imessage-core";

export type BlueBubblesService = "imessage" | "sms" | "auto";

export type BlueBubblesTarget =
  | { kind: "chat_id"; chatId: number }
  | { kind: "chat_guid"; chatGuid: string }
  | { kind: "chat_identifier"; chatIdentifier: string }
  | { kind: "handle"; to: string; service: BlueBubblesService };

export type BlueBubblesAllowTarget = ParsedChatTarget | { kind: "handle"; handle: string };

const CHAT_ID_PREFIXES = ["chat_id:", "chatid:", "chat:"];
const CHAT_GUID_PREFIXES = ["chat_guid:", "chatguid:", "guid:"];
const CHAT_IDENTIFIER_PREFIXES = ["chat_identifier:", "chatidentifier:", "chatident:"];
const SERVICE_PREFIXES: Array<{ prefix: string; service: BlueBubblesService }> = [
  { prefix: "imessage:", service: "imessage" },
  { prefix: "sms:", service: "sms" },
  { prefix: "auto:", service: "auto" },
];
const CHAT_IDENTIFIER_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const CHAT_IDENTIFIER_HEX_RE = /^[0-9a-f]{24,64}$/i;

function parseRawChatGuid(value: string): string | null {
  const trimmed = value.trim();
  if (!trimmed) {
    return null;
  }
  const parts = trimmed.split(";");
  if (parts.length !== 3) {
    return null;
  }
  const service = parts[0]?.trim();
  const separator = parts[1]?.trim();
  const identifier = parts[2]?.trim();
  if (!service || !identifier) {
    return null;
  }
  if (separator !== "+" && separator !== "-") {
    return null;
  }
  return `${service};${separator};${identifier}`;
}

function stripPrefix(value: string, prefix: string): string {
  return value.slice(prefix.length).trim();
}

function stripBlueBubblesPrefix(value: string): string {
  const trimmed = value.trim();
  if (!trimmed) {
    return "";
  }
  if (!trimmed.toLowerCase().startsWith("bluebubbles:")) {
    return trimmed;
  }
  return trimmed.slice("bluebubbles:".length).trim();
}

function looksLikeRawChatIdentifier(value: string): boolean {
  const trimmed = value.trim();
  if (!trimmed) {
    return false;
  }
  if (/^chat\d+$/i.test(trimmed)) {
    return true;
  }
  return CHAT_IDENTIFIER_UUID_RE.test(trimmed) || CHAT_IDENTIFIER_HEX_RE.test(trimmed);
}

function parseGroupTarget(params: {
  trimmed: string;
  lower: string;
  requireValue: boolean;
}): { kind: "chat_id"; chatId: number } | { kind: "chat_guid"; chatGuid: string } | null {
  if (!params.lower.startsWith("group:")) {
    return null;
  }
  const value = stripPrefix(params.trimmed, "group:");
  const chatId = Number.parseInt(value, 10);
  if (Number.isFinite(chatId)) {
    return { kind: "chat_id", chatId };
  }
  if (value) {
    return { kind: "chat_guid", chatGuid: value };
  }
  if (params.requireValue) {
    throw new Error("group target is required");
  }
  return null;
}

function parseRawChatIdentifierTarget(
  trimmed: string,
): { kind: "chat_identifier"; chatIdentifier: string } | null {
  if (/^chat\d+$/i.test(trimmed)) {
    return { kind: "chat_identifier", chatIdentifier: trimmed };
  }
  if (looksLikeRawChatIdentifier(trimmed)) {
    return { kind: "chat_identifier", chatIdentifier: trimmed };
  }
  return null;
}

export function normalizeBlueBubblesHandle(raw: string): string {
  const trimmed = raw.trim();
  if (!trimmed) {
    return "";
  }
  const lowered = trimmed.toLowerCase();
  if (lowered.startsWith("imessage:")) {
    return normalizeBlueBubblesHandle(trimmed.slice(9));
  }
  if (lowered.startsWith("sms:")) {
    return normalizeBlueBubblesHandle(trimmed.slice(4));
  }
  if (lowered.startsWith("auto:")) {
    return normalizeBlueBubblesHandle(trimmed.slice(5));
  }
  if (trimmed.includes("@")) {
    return trimmed.toLowerCase();
  }
  return trimmed.replace(/\s+/g, "");
}

/**
 * Extracts the handle from a chat_guid if it's a DM (1:1 chat).
 * BlueBubbles chat_guid format for DM: "service;-;handle" (e.g., "iMessage;-;+19257864429")
 * Group chat format: "service;+;groupId" (has "+" instead of "-")
 */
export function extractHandleFromChatGuid(chatGuid: string): string | null {
  const parts = chatGuid.split(";");
  // DM format: service;-;handle (3 parts, middle is "-")
  if (parts.length === 3 && parts[1] === "-") {
    const handle = parts[2]?.trim();
    if (handle) {
      return normalizeBlueBubblesHandle(handle);
    }
  }
  return null;
}

export function normalizeBlueBubblesMessagingTarget(raw: string): string | undefined {
  let trimmed = raw.trim();
  if (!trimmed) {
    return undefined;
  }
  trimmed = stripBlueBubblesPrefix(trimmed);
  if (!trimmed) {
    return undefined;
  }
  try {
    const parsed = parseBlueBubblesTarget(trimmed);
    if (parsed.kind === "chat_id") {
      return `chat_id:${parsed.chatId}`;
    }
    if (parsed.kind === "chat_guid") {
      // For DM chat_guids, normalize to just the handle for easier comparison.
      // This allows "chat_guid:iMessage;-;+1234567890" to match "+1234567890".
      const handle = extractHandleFromChatGuid(parsed.chatGuid);
      if (handle) {
        return handle;
      }
      // For group chats or unrecognized formats, keep the full chat_guid
      return `chat_guid:${parsed.chatGuid}`;
    }
    if (parsed.kind === "chat_identifier") {
      return `chat_identifier:${parsed.chatIdentifier}`;
    }
    const handle = normalizeBlueBubblesHandle(parsed.to);
    if (!handle) {
      return undefined;
    }
    return parsed.service === "auto" ? handle : `${parsed.service}:${handle}`;
  } catch {
    return trimmed;
  }
}

export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string): boolean {
  const trimmed = raw.trim();
  if (!trimmed) {
    return false;
  }
  const candidate = stripBlueBubblesPrefix(trimmed);
  if (!candidate) {
    return false;
  }
  if (parseRawChatGuid(candidate)) {
    return true;
  }
  const lowered = candidate.toLowerCase();
  if (/^(imessage|sms|auto):/.test(lowered)) {
    return true;
  }
  if (
    /^(chat_id|chatid|chat|chat_guid|chatguid|guid|chat_identifier|chatidentifier|chatident|group):/.test(
      lowered,
    )
  ) {
    return true;
  }
  // Recognize chat<digits> patterns (e.g., "chat660250192681427962") as chat IDs
  if (/^chat\d+$/i.test(candidate)) {
    return true;
  }
  if (looksLikeRawChatIdentifier(candidate)) {
    return true;
  }
  if (candidate.includes("@")) {
    return true;
  }
  const digitsOnly = candidate.replace(/[\s().-]/g, "");
  if (/^\+?\d{3,}$/.test(digitsOnly)) {
    return true;
  }
  if (normalized) {
    const normalizedTrimmed = normalized.trim();
    if (!normalizedTrimmed) {
      return false;
    }
    const normalizedLower = normalizedTrimmed.toLowerCase();
    if (
      /^(imessage|sms|auto):/.test(normalizedLower) ||
      /^(chat_id|chat_guid|chat_identifier):/.test(normalizedLower)
    ) {
      return true;
    }
  }
  return false;
}

export function looksLikeBlueBubblesExplicitTargetId(raw: string, normalized?: string): boolean {
  const trimmed = raw.trim();
  if (!trimmed) {
    return false;
  }
  const candidate = stripBlueBubblesPrefix(trimmed);
  if (!candidate) {
    return false;
  }
  const lowered = candidate.toLowerCase();
  if (/^(imessage|sms|auto):/.test(lowered)) {
    return true;
  }
  if (
    /^(chat_id|chatid|chat|chat_guid|chatguid|guid|chat_identifier|chatidentifier|chatident|group):/.test(
      lowered,
    )
  ) {
    return true;
  }
  if (parseRawChatGuid(candidate) || looksLikeRawChatIdentifier(candidate)) {
    return true;
  }
  if (normalized) {
    const normalizedTrimmed = normalized.trim();
    if (!normalizedTrimmed) {
      return false;
    }
    const normalizedLower = normalizedTrimmed.toLowerCase();
    if (
      /^(imessage|sms|auto):/.test(normalizedLower) ||
      /^(chat_id|chat_guid|chat_identifier):/.test(normalizedLower)
    ) {
      return true;
    }
  }
  return false;
}

export function inferBlueBubblesTargetChatType(raw: string): "direct" | "group" | undefined {
  try {
    const parsed = parseBlueBubblesTarget(raw);
    if (parsed.kind === "handle") {
      return "direct";
    }
    if (parsed.kind === "chat_guid") {
      return parsed.chatGuid.includes(";+;") ? "group" : "direct";
    }
    if (parsed.kind === "chat_id" || parsed.kind === "chat_identifier") {
      return "group";
    }
  } catch {
    return undefined;
  }
  return undefined;
}

export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
  const trimmed = stripBlueBubblesPrefix(raw);
  if (!trimmed) {
    throw new Error("BlueBubbles target is required");
  }
  const lower = trimmed.toLowerCase();

  const servicePrefixed = resolveServicePrefixedTarget({
    trimmed,
    lower,
    servicePrefixes: SERVICE_PREFIXES,
    isChatTarget: (remainderLower) =>
      CHAT_ID_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
      CHAT_GUID_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
      CHAT_IDENTIFIER_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
      remainderLower.startsWith("group:"),
    parseTarget: parseBlueBubblesTarget,
  });
  if (servicePrefixed) {
    return servicePrefixed;
  }

  const chatTarget = parseChatTargetPrefixesOrThrow({
    trimmed,
    lower,
    chatIdPrefixes: CHAT_ID_PREFIXES,
    chatGuidPrefixes: CHAT_GUID_PREFIXES,
    chatIdentifierPrefixes: CHAT_IDENTIFIER_PREFIXES,
  });
  if (chatTarget) {
    return chatTarget;
  }

  const groupTarget = parseGroupTarget({ trimmed, lower, requireValue: true });
  if (groupTarget) {
    return groupTarget;
  }

  const rawChatGuid = parseRawChatGuid(trimmed);
  if (rawChatGuid) {
    return { kind: "chat_guid", chatGuid: rawChatGuid };
  }

  const rawChatIdentifierTarget = parseRawChatIdentifierTarget(trimmed);
  if (rawChatIdentifierTarget) {
    return rawChatIdentifierTarget;
  }

  return { kind: "handle", to: trimmed, service: "auto" };
}

export function parseBlueBubblesAllowTarget(raw: string): BlueBubblesAllowTarget {
  const trimmed = raw.trim();
  if (!trimmed) {
    return { kind: "handle", handle: "" };
  }
  const lower = trimmed.toLowerCase();

  const servicePrefixed = resolveServicePrefixedAllowTarget({
    trimmed,
    lower,
    servicePrefixes: SERVICE_PREFIXES,
    parseAllowTarget: parseBlueBubblesAllowTarget,
  });
  if (servicePrefixed) {
    return servicePrefixed;
  }

  const chatTarget = parseChatAllowTargetPrefixes({
    trimmed,
    lower,
    chatIdPrefixes: CHAT_ID_PREFIXES,
    chatGuidPrefixes: CHAT_GUID_PREFIXES,
    chatIdentifierPrefixes: CHAT_IDENTIFIER_PREFIXES,
  });
  if (chatTarget) {
    return chatTarget;
  }

  const groupTarget = parseGroupTarget({ trimmed, lower, requireValue: false });
  if (groupTarget) {
    return groupTarget;
  }

  const rawChatIdentifierTarget = parseRawChatIdentifierTarget(trimmed);
  if (rawChatIdentifierTarget) {
    return rawChatIdentifierTarget;
  }

  return { kind: "handle", handle: normalizeBlueBubblesHandle(trimmed) };
}

export function isAllowedBlueBubblesSender(params: {
  allowFrom: Array<string | number>;
  sender: string;
  chatId?: number | null;
  chatGuid?: string | null;
  chatIdentifier?: string | null;
}): boolean {
  return isAllowedParsedChatSender({
    allowFrom: params.allowFrom,
    sender: params.sender,
    chatId: params.chatId,
    chatGuid: params.chatGuid,
    chatIdentifier: params.chatIdentifier,
    normalizeSender: normalizeBlueBubblesHandle,
    parseAllowTarget: parseBlueBubblesAllowTarget,
  });
}

export function formatBlueBubblesChatTarget(params: {
  chatId?: number | null;
  chatGuid?: string | null;
  chatIdentifier?: string | null;
}): string {
  if (params.chatId && Number.isFinite(params.chatId)) {
    return `chat_id:${params.chatId}`;
  }
  const guid = params.chatGuid?.trim();
  if (guid) {
    return `chat_guid:${guid}`;
  }
  const identifier = params.chatIdentifier?.trim();
  if (identifier) {
    return `chat_identifier:${identifier}`;
  }
  return "";
}
