import type { ParsedMessage } from "@/utils/types";
import { z } from "zod";

const emailSchema = z.string().email();
const recipientSeparatorRegex = /,(?=(?:[^"]*"[^"]*")*[^"]*$)/;

// Converts "John Doe <john.doe@gmail>" to "John Doe"
// Converts "<john.doe@gmail>" to "john.doe@gmail"
// Converts "john.doe@gmail" to "john.doe@gmail"
export function extractNameFromEmail(email: string) {
  if (!email) return "";
  const firstPart = email.split("<")[0]?.trim();
  if (firstPart) return firstPart;
  const secondPart = email.split("<")?.[1]?.trim();
  if (secondPart) return secondPart.split(">")[0];
  return email;
}

// Extracts all email addresses from a comma-separated header string
// e.g., "John <john@example.com>, Jane <jane@example.com>" -> ["john@example.com", "jane@example.com"]
export function extractEmailAddresses(header: string): string[] {
  return splitRecipientList(header)
    .map((part) => extractEmailAddress(part))
    .filter((email) => email.length > 0);
}

export function splitRecipientList(recipientList: string): string[] {
  if (!recipientList) return [];

  return recipientList
    .split(recipientSeparatorRegex)
    .map((recipient) => recipient.trim())
    .filter(Boolean);
}

// Converts "John Doe <john.doe@gmail>" to "john.doe@gmail"
export function extractEmailAddress(email: string): string {
  if (!email) return "";

  // Trim the input once at the start to handle leading/trailing spaces
  const trimmedEmail = email.trim();

  // Try to extract from angle brackets first
  const bracketMatch = trimmedEmail.match(/<([^<>]+)>$/);
  if (bracketMatch) {
    const candidate = bracketMatch[1].trim();
    if (isValidEmail(candidate)) {
      return candidate;
    }
  }

  // If no brackets or invalid email in brackets, try the whole string
  if (isValidEmail(trimmedEmail)) {
    return trimmedEmail;
  }

  // As a last resort, look for any email-like pattern in the string
  const emailPattern = /\b[^\s<>]+@[^\s<>]+\.[^\s<>]+\b/g;
  const matches = trimmedEmail.match(emailPattern);
  if (matches) {
    // Try each match to find a valid email
    for (const match of matches) {
      if (isValidEmail(match)) {
        return match;
      }
    }
  }

  return "";
}

export function isSameEmailAddress(left: string, right: string) {
  return (
    extractEmailAddress(left).trim().toLowerCase() ===
    extractEmailAddress(right).trim().toLowerCase()
  );
}

export function messageRepliesToSourceSender({
  sentMessage,
  sourceMessage,
}: {
  sentMessage: Pick<ParsedMessage, "headers">;
  sourceMessage: Pick<ParsedMessage, "headers">;
}) {
  const replyTargetEmails = extractEmailAddresses(
    sourceMessage.headers["reply-to"] || sourceMessage.headers.from,
  ).map((email) => email.toLowerCase());
  if (!replyTargetEmails.length) return null;

  const recipientEmails = [
    ...extractEmailAddresses(sentMessage.headers.to),
    ...extractEmailAddresses(sentMessage.headers.cc ?? ""),
    ...extractEmailAddresses(sentMessage.headers.bcc ?? ""),
  ].map((email) => email.toLowerCase());

  if (!recipientEmails.length) return null;

  const recipientEmailSet = new Set(recipientEmails);
  return replyTargetEmails.some((email) => recipientEmailSet.has(email));
}

export function isValidEmail(email: string): boolean {
  return emailSchema.safeParse(email).success;
}

// Normalizes email addresses by:
// - Converting to lowercase
// - Removing all dots from local part
// - Removing all whitespace from local part
// - Preserving domain part unchanged
// Example: "John.Doe.Smith@gmail.com" -> "johndoesmith@gmail.com"
export function normalizeEmailAddress(email: string) {
  const [localPart, domain] = email.toLowerCase().split("@");
  if (!domain) return email.toLowerCase();
  // Remove all dots and whitespace from local part
  const normalizedLocal = localPart.trim().replace(/[\s.]+/g, "");
  return `${normalizedLocal}@${domain}`;
}

export function extractUniqueEmailAddresses(
  emails: string[],
  options?: { lowercase?: boolean },
) {
  const lowercase = options?.lowercase ?? false;

  return [
    ...new Set(
      emails
        .map((email) => extractEmailAddress(email))
        .filter(Boolean)
        .map((email) => (lowercase ? email.toLowerCase() : email)),
    ),
  ];
}

// Converts "Name <hey@domain.com>" to "domain.com"
export function extractDomainFromEmail(email: string): string {
  if (!email) return "";

  // Extract clean email address from formatted strings like "Name <email@domain.com>"
  const emailAddress = email.includes("<") ? extractEmailAddress(email) : email;

  // Validate email has exactly one @ symbol
  if ((emailAddress.match(/@/g) || []).length !== 1) return "";

  // Extract domain using regex that supports:
  // - International characters (via \p{L})
  // - Multiple subdomains (e.g. sub1.sub2.domain.com)
  // - Common domain characters (letters, numbers, dots, hyphens)
  // - TLDs of 2 or more characters
  const domain = emailAddress.match(
    /@([\p{L}a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/u,
  )?.[1];
  return domain || "";
}

// returns the other side of the conversation
// if we're the sender, then return the recipient
// if we're the recipient, then return the sender
export function participant(
  message: { headers: Pick<ParsedMessage["headers"], "from" | "to"> },
  userEmail: string,
) {
  if (!userEmail) return message.headers.from;
  if (message.headers.from.includes(userEmail)) return message.headers.to;
  return message.headers.from;
}

// Converts name and email to "Name <email@example.com>" or just "email@example.com" if no name
// This is the inverse of extractNameFromEmail/extractEmailAddress
export function formatEmailWithName(
  name: string | null | undefined,
  address: string | null | undefined,
): string {
  if (!address) return "";
  if (!name || name === address) return address;
  return `${name} <${address}>`;
}

export function getNewsletterSenderDisplayName({
  email,
  fromName,
  minFromName,
  maxFromName,
}: {
  email: string;
  fromName?: string | null;
  minFromName?: string | null;
  maxFromName?: string | null;
}) {
  const hasMultipleDisplayNames =
    !!minFromName && !!maxFromName && minFromName !== maxFromName;
  const domain = extractDomainFromEmail(email);

  if (hasMultipleDisplayNames && domain) return domain;

  return fromName?.trim() || "";
}

// Public email providers where we should search by full email address
// For company domains, we search by domain to catch emails from different people at same company
export const PUBLIC_EMAIL_DOMAINS = new Set([
  "gmail.com",
  "yahoo.com",
  "hotmail.com",
  "outlook.com",
  "aol.com",
  "icloud.com",
  "me.com",
  "protonmail.com",
  "zoho.com",
  "yandex.com",
  "fastmail.com",
  "gmx.com",
  "hey.com",
  "mail.com",
]);

export function isPublicEmailDomain(domain: string): boolean {
  return PUBLIC_EMAIL_DOMAINS.has(domain.trim().toLowerCase());
}

// Returns the search term to use when checking for previous communications
// For public email providers (gmail, yahoo, etc), returns the full email address
// For company domains, returns just the domain to catch emails from different people at same company
export function getSearchTermForSender(email: string): string {
  const domain = extractDomainFromEmail(email);
  if (!domain) return email;

  return isPublicEmailDomain(domain)
    ? extractEmailAddress(email) || email
    : domain;
}
