import * as net from "net";
import { formatDomain, getRootDomain } from "./utils";

/**
 * Interface for WHOIS data
 */
export interface WhoisData {
  // Registration information
  registrar?: string;
  registrarUrl?: string;
  registrarIanaId?: string;

  // Dates
  creationDate?: Date;
  updatedDate?: Date;
  expirationDate?: Date;

  // Contact information (redacted in many cases due to privacy)
  registrant?: {
    organization?: string;
    country?: string;
    email?: string;
  };

  // Status codes
  statusCodes?: string[];

  // Name servers
  nameServers?: string[];

  // Raw WHOIS response
  rawText: string;
}

/**
 * Map of TLDs to their WHOIS servers
 */
const WHOIS_SERVERS: { [key: string]: string } = {
  // Generic Top-Level Domains (gTLDs)
  com: "whois.verisign-grs.com",
  net: "whois.verisign-grs.com",
  org: "whois.pir.org",
  info: "whois.afilias.net",
  biz: "whois.neulevel.biz",
  io: "whois.nic.io",
  co: "whois.nic.co",
  app: "whois.nic.google",
  dev: "whois.nic.google",
  page: "whois.nic.google",
  cloud: "whois.nic.cloud",
  xyz: "whois.nic.xyz",
  site: "whois.nic.site",
  online: "whois.nic.online",
  top: "whois.nic.top",
  store: "whois.nic.store",
  shop: "whois.nic.shop",
  blog: "whois.nic.blog",
  tech: "whois.nic.tech",
  design: "whois.nic.design",
  money: "whois.nic.money",
  email: "whois.nic.email",
  live: "whois.nic.live",
  club: "whois.nic.club",
  news: "whois.nic.news",
  wiki: "whois.nic.wiki",
  global: "whois.nic.global",
  digital: "whois.nic.digital",

  // Country Code Top-Level Domains (ccTLDs)
  us: "whois.nic.us",
  uk: "whois.nic.uk",
  de: "whois.denic.de",
  fr: "whois.nic.fr",
  ca: "whois.cira.ca",
  au: "whois.auda.org.au",
  nl: "whois.domain-registry.nl",
  ru: "whois.tcinet.ru",
  ch: "whois.nic.ch",
  es: "whois.nic.es",
  it: "whois.nic.it",
  jp: "whois.jprs.jp",
  cn: "whois.cnnic.cn",
  in: "whois.registry.in",
  br: "whois.registro.br",
  mx: "whois.mx",
  nz: "whois.nic.nz",
  se: "whois.iis.se",
  no: "whois.norid.no",
  dk: "whois.dk-hostmaster.dk",
  fi: "whois.fi",
  kr: "whois.kr",
  pl: "whois.dns.pl",
  be: "whois.dns.be",
  at: "whois.nic.at",
  hk: "whois.hkirc.hk",
  sg: "whois.sgnic.sg",
  tw: "whois.twnic.net.tw",
  ie: "whois.iedr.ie",
  za: "whois.registry.net.za",
  tr: "whois.nic.tr",
  il: "whois.isoc.org.il",
  ua: "whois.ua",
  gr: "whois.nic.gr",
  ro: "whois.rotld.ro",
  th: "whois.thnic.co.th",
  my: "whois.mynic.my",
  pt: "whois.dns.pt",
  hu: "whois.nic.hu",
  cz: "whois.nic.cz",
  sk: "whois.sk-nic.sk",
  ar: "whois.nic.ar",
  cl: "whois.nic.cl",
  pe: "kero.yachay.pe",
  ec: "whois.nic.ec",
  uy: "whois.nic.org.uy",
  ve: "whois.nic.ve",
  ac: "whois.nic.ac",
  ae: "whois.aeda.net.ae",
  af: "whois.nic.af",
  ag: "whois.nic.ag",
  ai: "whois.nic.ai",
  al: "whois.ripe.net",
  am: "whois.amnic.net",
  as: "whois.nic.as",
  asia: "whois.nic.asia",
  az: "whois.ripe.net",
  ba: "whois.ripe.net",
  bg: "whois.register.bg",
  bi: "whois.nic.bi",
  bj: "whois.nic.bj",
  bm: "whois.afilias-srs.net",
  bn: "whois.bn",
  bo: "whois.nic.bo",
  by: "whois.cctld.by",
  bz: "whois.afilias-grs.info",
  cat: "whois.nic.cat",
  cc: "whois.nic.cc",
  cd: "whois.nic.cd",
  cf: "whois.dot.cf",
  ci: "whois.nic.ci",
  cm: "whois.netcom.cm",
  cr: "whois.nic.cr",
  cu: "whois.nic.cu",
  cx: "whois.nic.cx",
  cy: "whois.ripe.net",
  dz: "whois.nic.dz",
  ee: "whois.tld.ee",
  eu: "whois.eu",
  fm: "whois.nic.fm",
  fo: "whois.nic.fo",
  ga: "whois.dot.ga",
  gd: "whois.nic.gd",
  gf: "whois.mediaserv.net",
  gg: "whois.gg",
  gi: "whois2.afilias-grs.net",
  gl: "whois.nic.gl",
  gp: "whois.nic.gp",
  gs: "whois.nic.gs",
  gt: "whois.nic.gt",
  gy: "whois.registry.gy",
  hm: "whois.registry.hm",
  hn: "whois.nic.hn",
  hr: "whois.dns.hr",
  ht: "whois.nic.ht",
  im: "whois.nic.im",
  iq: "whois.cmc.iq",
  ir: "whois.nic.ir",
  is: "whois.isnic.is",
  je: "whois.je",
  jo: "whois.nic.jo",
  ke: "whois.kenic.or.ke",
  kg: "whois.domain.kg",
  ki: "whois.nic.ki",
  kn: "whois.nic.kn",
  kz: "whois.nic.kz",
  la: "whois.nic.la",
  lc: "whois.nic.lc",
  li: "whois.nic.li",
  lk: "whois.nic.lk",
  lt: "whois.domreg.lt",
  lu: "whois.dns.lu",
  lv: "whois.nic.lv",
  ly: "whois.nic.ly",
  ma: "whois.registre.ma",
  mc: "whois.ripe.net",
  md: "whois.nic.md",
  me: "whois.nic.me",
  mg: "whois.nic.mg",
  mk: "whois.marnet.mk",
  ml: "whois.dot.ml",
  mm: "whois.nic.mm",
  mn: "whois.nic.mn",
  mo: "whois.monic.mo",
  mp: "whois.nic.mp",
  ms: "whois.nic.ms",
  mu: "whois.nic.mu",
  mw: "whois.nic.mw",
  mz: "whois.nic.mz",
  na: "whois.na-nic.com.na",
  nc: "whois.nc",
  nf: "whois.nic.nf",
  ng: "whois.nic.net.ng",
  nu: "whois.iis.nu",
  om: "whois.registry.om",
  pr: "whois.nic.pr",
  ps: "whois.pnina.ps",
  qa: "whois.registry.qa",
  re: "whois.nic.re",
  rs: "whois.rnids.rs",
  sa: "whois.nic.net.sa",
  sb: "whois.nic.sb",
  sc: "whois.nic.sc",
  sd: "whois.sd",
  si: "whois.register.si",
  sl: "whois.nic.sl",
  sm: "whois.nic.sm",
  sn: "whois.nic.sn",
  so: "whois.nic.so",
  sr: "whois.nic.sr",
  st: "whois.nic.st",
  su: "whois.tcinet.ru",
  sx: "whois.sx",
  sy: "whois.tld.sy",
  tc: "whois.nic.tc",
  tf: "whois.nic.tf",
  tg: "whois.nic.tg",
  tj: "whois.nic.tj",
  tk: "whois.dot.tk",
  tl: "whois.nic.tl",
  tm: "whois.nic.tm",
  tn: "whois.ati.tn",
  to: "whois.tonic.to",
  tv: "whois.nic.tv",
  tz: "whois.tznic.or.tz",
  ug: "whois.co.ug",
  vc: "whois.nic.vc",
  vg: "whois.nic.vg",
  vu: "whois.nic.vu",
  wf: "whois.nic.wf",
  ws: "whois.website.ws",
  yt: "whois.nic.yt",

  // New Generic TLDs
  academy: "whois.nic.academy",
  accountant: "whois.nic.accountant",
  actor: "whois.nic.actor",
  agency: "whois.nic.agency",
  airforce: "whois.nic.airforce",
  apartments: "whois.nic.apartments",
  army: "whois.nic.army",
  art: "whois.nic.art",
  associates: "whois.nic.associates",
  attorney: "whois.nic.attorney",
  auction: "whois.nic.auction",
  audio: "whois.nic.audio",
  auto: "whois.nic.auto",
  band: "whois.nic.band",
  bar: "whois.nic.bar",
  bargains: "whois.nic.bargains",
  beer: "whois.nic.beer",
  best: "whois.nic.best",
  bid: "whois.nic.bid",
  bike: "whois.nic.bike",
  bingo: "whois.nic.bingo",
  black: "whois.nic.black",
  blue: "whois.nic.blue",
  boutique: "whois.nic.boutique",
  builders: "whois.nic.builders",
  business: "whois.nic.business",
  buzz: "whois.nic.buzz",
  cab: "whois.nic.cab",
  cafe: "whois.nic.cafe",
  camera: "whois.nic.camera",
  camp: "whois.nic.camp",
  capital: "whois.nic.capital",
  cards: "whois.nic.cards",
  care: "whois.nic.care",
  careers: "whois.nic.careers",
  cash: "whois.nic.cash",
  casino: "whois.nic.casino",
  catering: "whois.nic.catering",
  center: "whois.nic.center",
  ceo: "whois.nic.ceo",
  chat: "whois.nic.chat",
  cheap: "whois.nic.cheap",
  christmas: "whois.nic.christmas",
  church: "whois.nic.church",
  city: "whois.nic.city",
  claims: "whois.nic.claims",
  cleaning: "whois.nic.cleaning",
  click: "whois.nic.click",
  clinic: "whois.nic.clinic",
  clothing: "whois.nic.clothing",
  coach: "whois.nic.coach",
  codes: "whois.nic.codes",
  coffee: "whois.nic.coffee",
  community: "whois.nic.community",
  company: "whois.nic.company",
  computer: "whois.nic.computer",
  condos: "whois.nic.condos",
  construction: "whois.nic.construction",
  consulting: "whois.nic.consulting",
  contractors: "whois.nic.contractors",
  cool: "whois.nic.cool",
  coupons: "whois.nic.coupons",
  credit: "whois.nic.credit",
  creditcard: "whois.nic.creditcard",
  cruises: "whois.nic.cruises",
  dance: "whois.nic.dance",
  dating: "whois.nic.dating",
  deals: "whois.nic.deals",
  degree: "whois.nic.degree",
  delivery: "whois.nic.delivery",
  democrat: "whois.nic.democrat",
  dental: "whois.nic.dental",
  dentist: "whois.nic.dentist",
  diamonds: "whois.nic.diamonds",
  diet: "whois.nic.diet",
  direct: "whois.nic.direct",
  directory: "whois.nic.directory",
  discount: "whois.nic.discount",
  doctor: "whois.nic.doctor",
  dog: "whois.nic.dog",
  domains: "whois.nic.domains",
  education: "whois.nic.education",
  energy: "whois.nic.energy",
  engineer: "whois.nic.engineer",
  engineering: "whois.nic.engineering",
  enterprises: "whois.nic.enterprises",
  equipment: "whois.nic.equipment",
  estate: "whois.nic.estate",
  events: "whois.nic.events",
  exchange: "whois.nic.exchange",
  expert: "whois.nic.expert",
  exposed: "whois.nic.exposed",
  express: "whois.nic.express",
  fail: "whois.nic.fail",
  faith: "whois.nic.faith",
  family: "whois.nic.family",
  fans: "whois.nic.fans",
  farm: "whois.nic.farm",
  fashion: "whois.nic.fashion",
  film: "whois.nic.film",
  finance: "whois.nic.finance",
  financial: "whois.nic.financial",
  fish: "whois.nic.fish",
  fishing: "whois.nic.fishing",
  fit: "whois.nic.fit",
  fitness: "whois.nic.fitness",
  flights: "whois.nic.flights",
  florist: "whois.nic.florist",
  football: "whois.nic.football",
  forex: "whois.nic.forex",
  forsale: "whois.nic.forsale",
  foundation: "whois.nic.foundation",
  fund: "whois.nic.fund",
  furniture: "whois.nic.furniture",
  futbol: "whois.nic.futbol",
  fyi: "whois.nic.fyi",
  gallery: "whois.nic.gallery",
  games: "whois.nic.games",
  garden: "whois.nic.garden",
  gift: "whois.nic.gift",
  gifts: "whois.nic.gifts",
  gives: "whois.nic.gives",
  glass: "whois.nic.glass",
  golf: "whois.nic.golf",
  graphics: "whois.nic.graphics",
  gratis: "whois.nic.gratis",
  green: "whois.nic.green",
  gripe: "whois.nic.gripe",
  group: "whois.nic.group",
  guide: "whois.nic.guide",
  guitars: "whois.nic.guitars",
  guru: "whois.nic.guru",
  haus: "whois.nic.haus",
  healthcare: "whois.nic.healthcare",
  help: "whois.nic.help",
  hiphop: "whois.nic.hiphop",
  hockey: "whois.nic.hockey",
  holdings: "whois.nic.holdings",
  holiday: "whois.nic.holiday",
  homes: "whois.nic.homes",
  horse: "whois.nic.horse",
  hospital: "whois.nic.hospital",
  host: "whois.nic.host",
  hosting: "whois.nic.hosting",
  house: "whois.nic.house",
  how: "whois.nic.how",
  ink: "whois.nic.ink",
  institute: "whois.nic.institute",
  insurance: "whois.nic.insurance",
  international: "whois.nic.international",
  investments: "whois.nic.investments",
  jetzt: "whois.nic.jetzt",
  jewelry: "whois.nic.jewelry",
  jobs: "whois.nic.jobs",
  juegos: "whois.nic.juegos",
  kaufen: "whois.nic.kaufen",
  kim: "whois.nic.kim",
  kitchen: "whois.nic.kitchen",
  land: "whois.nic.land",
  lawyer: "whois.nic.lawyer",
  lease: "whois.nic.lease",
  legal: "whois.nic.legal",
  lgbt: "whois.nic.lgbt",
  life: "whois.nic.life",
  lighting: "whois.nic.lighting",
  limited: "whois.nic.limited",
  limo: "whois.nic.limo",
  link: "whois.nic.link",
  loan: "whois.nic.loan",
  loans: "whois.nic.loans",
  lol: "whois.nic.lol",
  love: "whois.nic.love",
  ltd: "whois.nic.ltd",
  luxe: "whois.nic.luxe",
  luxury: "whois.nic.luxury",
  maison: "whois.nic.maison",
  management: "whois.nic.management",
  market: "whois.nic.market",
  marketing: "whois.nic.marketing",
  mba: "whois.nic.mba",
  media: "whois.nic.media",
  memorial: "whois.nic.memorial",
  men: "whois.nic.men",
  menu: "whois.nic.menu",
  mobi: "whois.nic.mobi",
  moda: "whois.nic.moda",
  mom: "whois.nic.mom",
  mortgage: "whois.nic.mortgage",
  motorcycles: "whois.nic.motorcycles",
  movie: "whois.nic.movie",
  navy: "whois.nic.navy",
  network: "whois.nic.network",
  ninja: "whois.nic.ninja",
  one: "whois.nic.one",
  onl: "whois.nic.onl",
  ooo: "whois.nic.ooo",
  partners: "whois.nic.partners",
  parts: "whois.nic.parts",
  party: "whois.nic.party",
  pet: "whois.nic.pet",
  photo: "whois.nic.photo",
  photography: "whois.nic.photography",
  photos: "whois.nic.photos",
  pics: "whois.nic.pics",
  pictures: "whois.nic.pictures",
  pink: "whois.nic.pink",
  pizza: "whois.nic.pizza",
  place: "whois.nic.place",
  plumbing: "whois.nic.plumbing",
  plus: "whois.nic.plus",
  poker: "whois.nic.poker",
  porn: "whois.nic.porn",
  press: "whois.nic.press",
  productions: "whois.nic.productions",
  promo: "whois.nic.promo",
  properties: "whois.nic.properties",
  property: "whois.nic.property",
  pub: "whois.nic.pub",
  racing: "whois.nic.racing",
  recipes: "whois.nic.recipes",
  red: "whois.nic.red",
  rehab: "whois.nic.rehab",
  reisen: "whois.nic.reisen",
  rentals: "whois.nic.rentals",
  repair: "whois.nic.repair",
  report: "whois.nic.report",
  republican: "whois.nic.republican",
  rest: "whois.nic.rest",
  restaurant: "whois.nic.restaurant",
  review: "whois.nic.review",
  reviews: "whois.nic.reviews",
  rip: "whois.nic.rip",
  rocks: "whois.nic.rocks",
  run: "whois.nic.run",
  sale: "whois.nic.sale",
  salon: "whois.nic.salon",
  sarl: "whois.nic.sarl",
  school: "whois.nic.school",
  science: "whois.nic.science",
  security: "whois.nic.security",
  services: "whois.nic.services",
  sex: "whois.nic.sex",
  sexy: "whois.nic.sexy",
  shoes: "whois.nic.shoes",
  show: "whois.nic.show",
  singles: "whois.nic.singles",
  ski: "whois.nic.ski",
  soccer: "whois.nic.soccer",
  social: "whois.nic.social",
  software: "whois.nic.software",
  solar: "whois.nic.solar",
  solutions: "whois.nic.solutions",
  space: "whois.nic.space",
  storage: "whois.nic.storage",
  studio: "whois.nic.studio",
  style: "whois.nic.style",
  sucks: "whois.nic.sucks",
  supplies: "whois.nic.supplies",
  supply: "whois.nic.supply",
  support: "whois.nic.support",
  surgery: "whois.nic.surgery",
  systems: "whois.nic.systems",
  tattoo: "whois.nic.tattoo",
  tax: "whois.nic.tax",
  taxi: "whois.nic.taxi",
  team: "whois.nic.team",
  tennis: "whois.nic.tennis",
  theater: "whois.nic.theater",
  theatre: "whois.nic.theatre",
  tickets: "whois.nic.tickets",
  tienda: "whois.nic.tienda",
  tips: "whois.nic.tips",
  tires: "whois.nic.tires",
  today: "whois.nic.today",
  tools: "whois.nic.tools",
  tours: "whois.nic.tours",
  town: "whois.nic.town",
  toys: "whois.nic.toys",
  trade: "whois.nic.trade",
  trading: "whois.nic.trading",
  training: "whois.nic.training",
  tube: "whois.nic.tube",
  university: "whois.nic.university",
  uno: "whois.nic.uno",
  vacations: "whois.nic.vacations",
  vegas: "whois.nic.vegas",
  ventures: "whois.nic.ventures",
  vet: "whois.nic.vet",
  viajes: "whois.nic.viajes",
  video: "whois.nic.video",
  villas: "whois.nic.villas",
  vision: "whois.nic.vision",
  voyage: "whois.nic.voyage",
  watch: "whois.nic.watch",
  webcam: "whois.nic.webcam",
  website: "whois.nic.website",
  wedding: "whois.nic.wedding",
  whoswho: "whois.nic.whoswho",
  win: "whois.nic.win",
  wine: "whois.nic.wine",
  work: "whois.nic.work",
  works: "whois.nic.works",
  world: "whois.nic.world",
  wtf: "whois.nic.wtf",
  zone: "whois.nic.zone",

  // Fallback server
  default: "whois.iana.org",
};

/**
 * Cache for WHOIS queries to respect rate limits
 */
const whoisCache = new Map<string, { data: WhoisData; timestamp: number }>();
const CACHE_TTL = 3600000; // 1 hour cache

/**
 * Gets the appropriate WHOIS server for a domain
 * @param domain The domain to get the WHOIS server for
 * @returns The WHOIS server hostname
 */
function getWhoisServer(domain: string): string {
  const rootDomain = getRootDomain(domain);
  const tld = rootDomain.split(".").pop()?.toLowerCase();

  if (tld && WHOIS_SERVERS[tld]) {
    return WHOIS_SERVERS[tld];
  }

  return WHOIS_SERVERS.default;
}

/**
 * Parses a raw WHOIS response into structured data
 * @param rawData The raw WHOIS response
 * @returns Parsed WHOIS data
 */
function parseWhoisData(rawData: string): WhoisData {
  const result: WhoisData = {
    rawText: rawData,
  };

  // Try multiple patterns for registrar info
  const registrarPatterns = [
    /Registrar:\s*(.+?)(?:\n|$)/i,
    /Registrar Name:\s*(.+?)(?:\n|$)/i,
    /Sponsoring Registrar:\s*(.+?)(?:\n|$)/i,
    /Registration Service Provider:\s*(.+?)(?:\n|$)/i,
  ];

  for (const pattern of registrarPatterns) {
    const match = rawData.match(pattern);
    if (match && match[1].trim()) {
      result.registrar = match[1].trim();
      break;
    }
  }

  // If we still don't have a registrar, look for specific patterns in the response
  if (!result.registrar && rawData.includes("MarkMonitor")) {
    result.registrar = "MarkMonitor Inc.";
  } else if (!result.registrar) {
    // Try to extract from any line that has "Registrar:" followed by text on the same line
    const lines = rawData.split("\n");
    for (const line of lines) {
      if (
        line.includes("Registrar:") &&
        !line.includes("WHOIS Server") &&
        !line.includes("URL") &&
        !line.includes("IANA ID")
      ) {
        const parts = line.split("Registrar:");
        if (parts.length > 1 && parts[1].trim()) {
          result.registrar = parts[1].trim();
          break;
        }
      }
    }
  }

  // Try multiple patterns for URL
  const urlPatterns = [
    /Registrar URL:\s*(.+?)(?:\n|$)/i,
    /URL:\s*(.+?)(?:\n|$)/i,
    /Registrar Website:\s*(.+?)(?:\n|$)/i,
  ];

  for (const pattern of urlPatterns) {
    const match = rawData.match(pattern);
    if (match && match[1].trim()) {
      result.registrarUrl = match[1].trim();
      break;
    }
  }

  // Parse IANA ID
  const ianaMatch = rawData.match(/Registrar IANA ID:\s*(.+?)(?:\n|$)/i);
  if (ianaMatch && ianaMatch[1].trim()) {
    result.registrarIanaId = ianaMatch[1].trim();
  }

  // Parse dates with multiple possible formats
  const creationPatterns = [
    /Creation Date:\s*(.+?)(?:\n|$)/i,
    /Created on:\s*(.+?)(?:\n|$)/i,
    /Domain Registration Date:\s*(.+?)(?:\n|$)/i,
    /Domain Create Date:\s*(.+?)(?:\n|$)/i,
    /Created Date:\s*(.+?)(?:\n|$)/i,
    /Registered on:\s*(.+?)(?:\n|$)/i,
  ];

  for (const pattern of creationPatterns) {
    const match = rawData.match(pattern);
    if (match && match[1].trim()) {
      try {
        const date = new Date(match[1].trim());
        if (!isNaN(date.getTime())) {
          result.creationDate = date;
          break;
        }
      } catch {
        // Continue to next pattern if date parsing fails
      }
    }
  }

  const updatedPatterns = [
    /Updated Date:\s*(.+?)(?:\n|$)/i,
    /Last Modified:\s*(.+?)(?:\n|$)/i,
    /Last updated on:\s*(.+?)(?:\n|$)/i,
    /Domain Last Updated Date:\s*(.+?)(?:\n|$)/i,
  ];

  for (const pattern of updatedPatterns) {
    const match = rawData.match(pattern);
    if (match && match[1].trim()) {
      try {
        const date = new Date(match[1].trim());
        if (!isNaN(date.getTime())) {
          result.updatedDate = date;
          break;
        }
      } catch {
        // Continue to next pattern if date parsing fails
      }
    }
  }

  const expirationPatterns = [
    /(?:Registry Expiry Date|Expiration Date):\s*(.+?)(?:\n|$)/i,
    /Registrar Registration Expiration Date:\s*(.+?)(?:\n|$)/i,
    /Domain Expiration Date:\s*(.+?)(?:\n|$)/i,
    /Expiry date:\s*(.+?)(?:\n|$)/i,
    /Expiry Date:\s*(.+?)(?:\n|$)/i,
  ];

  for (const pattern of expirationPatterns) {
    const match = rawData.match(pattern);
    if (match && match[1].trim()) {
      try {
        const date = new Date(match[1].trim());
        if (!isNaN(date.getTime())) {
          result.expirationDate = date;
          break;
        }
      } catch {
        // Continue to next pattern if date parsing fails
      }
    }
  }

  // Parse registrant info
  result.registrant = {};

  const registrantOrgPatterns = [
    /Registrant Organization:\s*(.+?)(?:\n|$)/i,
    /Registrant:\s*(.+?)(?:\n|$)/i,
    /Organization:\s*(.+?)(?:\n|$)/i,
  ];

  for (const pattern of registrantOrgPatterns) {
    const match = rawData.match(pattern);
    if (
      match &&
      match[1].trim() &&
      !match[1].includes("REDACTED FOR PRIVACY")
    ) {
      result.registrant.organization = match[1].trim();
      break;
    }
  }

  const registrantCountryPatterns = [
    /Registrant Country:\s*(.+?)(?:\n|$)/i,
    /Country:\s*(.+?)(?:\n|$)/i,
  ];

  for (const pattern of registrantCountryPatterns) {
    const match = rawData.match(pattern);
    if (match && match[1].trim()) {
      result.registrant.country = match[1].trim();
      break;
    }
  }

  // Don't extract email if GDPR protected
  if (!rawData.includes("GDPR") && !rawData.includes("REDACTED FOR PRIVACY")) {
    const emailPatterns = [
      /Registrant Email:\s*(.+?)(?:\n|$)/i,
      /Email:\s*(.+?)(?:\n|$)/i,
    ];

    for (const pattern of emailPatterns) {
      const match = rawData.match(pattern);
      if (match && match[1].trim() && match[1].includes("@")) {
        result.registrant.email = match[1].trim();
        break;
      }
    }
  }

  // Status codes
  const statusCodes: string[] = [];
  const statusRegex = /Domain Status:\s*(.+?)(?:\n|$)/gi;
  let statusMatch: RegExpExecArray | null;

  while ((statusMatch = statusRegex.exec(rawData)) !== null) {
    const status = statusMatch[1].trim();
    if (status && !statusCodes.includes(status)) {
      statusCodes.push(status);
    }
  }

  if (statusCodes.length === 0) {
    // Try alternative status patterns
    const altStatusRegex = /Status:\s*(.+?)(?:\n|$)/gi;
    while ((statusMatch = altStatusRegex.exec(rawData)) !== null) {
      const status = statusMatch[1].trim();
      if (status && !statusCodes.includes(status)) {
        statusCodes.push(status);
      }
    }
  }

  if (statusCodes.length > 0) {
    result.statusCodes = statusCodes;
  }

  // Name servers
  const nameServers: string[] = [];
  const nsPatterns = [
    /Name Server:\s*(.+?)(?:\n|$)/gi,
    /Nameserver:\s*(.+?)(?:\n|$)/gi,
    /nserver:\s*(.+?)(?:\n|$)/gi,
  ];

  for (const pattern of nsPatterns) {
    let nsMatch: RegExpExecArray | null;
    while ((nsMatch = pattern.exec(rawData)) !== null) {
      const server = nsMatch[1].trim().toLowerCase();
      if (server && !nameServers.includes(server)) {
        nameServers.push(server);
      }
    }
  }

  if (nameServers.length > 0) {
    result.nameServers = nameServers;
  }

  // Special handling for reserved domains like example.com
  if (rawData.includes("IANA") && rawData.includes("RESERVED")) {
    result.registrar = "IANA (Reserved Domain)";
    if (!result.statusCodes) {
      result.statusCodes = ["RESERVED-IANA"];
    }
  }

  return result;
}

/**
 * Queries a WHOIS server for domain information
 * @param domain The domain to query
 * @param server The WHOIS server to query
 * @returns Promise resolving to the WHOIS response
 */
function queryWhoisServer(domain: string, server: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const socket = new net.Socket();
    let data = "";

    // Set timeout for 10 seconds
    socket.setTimeout(10000);

    socket.connect(43, server, () => {
      socket.write(domain + "\r\n");
    });

    socket.on("data", (chunk) => {
      data += chunk.toString();
    });

    socket.on("close", () => {
      resolve(data);
    });

    socket.on("error", (error) => {
      reject(new Error(`WHOIS query failed: ${error.message}`));
    });

    socket.on("timeout", () => {
      socket.destroy();
      reject(new Error("WHOIS query timed out"));
    });
  });
}

/**
 * Gets WHOIS data for a domain
 * @param domain The domain to get WHOIS data for
 * @param useCache Whether to use cached data if available
 * @param maxReferrals Maximum number of referrals to follow (to prevent infinite loops)
 * @returns Promise resolving to WHOIS data
 */
export async function getWhoisData(
  domain: string,
  useCache = true,
  maxReferrals = 3
): Promise<WhoisData> {
  const formattedDomain = formatDomain(domain);

  // Check cache
  if (useCache) {
    const cached = whoisCache.get(formattedDomain);
    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
      return cached.data;
    }
  }

  try {
    // Start with the initial WHOIS server
    const initialServer = getWhoisServer(formattedDomain);
    let response = await queryWhoisServer(formattedDomain, initialServer);
    let currentServer = initialServer;
    let referralCount = 0;
    let finalResponse = response;

    // Check for WHOIS server redirection and follow if found
    while (referralCount < maxReferrals) {
      // Look for patterns that indicate a referral to another WHOIS server
      const referralPatterns = [
        /Registrar WHOIS Server:\s*(.+?)(?:\n|$)/i,
        /WHOIS Server:\s*(.+?)(?:\n|$)/i,
        /Referral URL:\s*(?:https?:\/\/)?(?:www\.)?(.+?)(?:\/|\n|$)/i,
        /refer:\s*(.+?)(?:\n|$)/i,
      ];

      let foundReferral = false;
      let nextServer = "";

      for (const pattern of referralPatterns) {
        const match = response.match(pattern);
        if (
          match &&
          match[1] &&
          match[1].trim() &&
          match[1].includes(".") &&
          match[1] !== currentServer
        ) {
          nextServer = match[1].trim();
          // Clean up the server name (remove http://, www., etc.)
          nextServer = nextServer
            .replace(/^https?:\/\//i, "")
            .replace(/^www\./i, "");

          // Don't follow if we're already at this server
          if (nextServer && nextServer !== currentServer) {
            foundReferral = true;
            break;
          }
        }
      }

      if (foundReferral && nextServer) {
        // Follow the referral
        try {
          console.log(`Following WHOIS referral to ${nextServer}`);
          const nextResponse = await queryWhoisServer(
            formattedDomain,
            nextServer
          );

          // If we get a meaningful response (not just a referral back)
          if (nextResponse && nextResponse.length > 100) {
            response = nextResponse;
            finalResponse += "\n\n# " + nextServer + "\n\n" + nextResponse;
            currentServer = nextServer;
          }
        } catch (error) {
          // If the referral fails, just use what we have
          console.warn(
            `Failed to follow WHOIS referral: ${
              error instanceof Error ? error.message : String(error)
            }`
          );
          break;
        }
      } else {
        // No more referrals
        break;
      }

      referralCount++;
    }

    // Parse the combined response data
    const parsedData = parseWhoisData(finalResponse);

    // Cache the result
    whoisCache.set(formattedDomain, {
      data: parsedData,
      timestamp: Date.now(),
    });

    return parsedData;
  } catch (error) {
    if (error instanceof Error) {
      throw new Error(
        `Could not fetch WHOIS data for domain ${formattedDomain}. Details: ${error.message}`
      );
    } else {
      throw new Error(
        `Could not fetch WHOIS data for domain ${formattedDomain}.`
      );
    }
  }
}
