import { normalizePhone, normalizeUrl } from "../quality.js";
import { normalizeText, uniqueBy } from "../utils.js";

const DEFAULT_TIMEOUT_MS = Math.max(3000, Number(process.env.HERMES_DIRECTORY_FETCH_TIMEOUT_MS || 15000));
const MAX_HTML_CHARS = Math.max(50000, Number(process.env.HERMES_DIRECTORY_MAX_HTML_CHARS || 250000));

const SOURCE_BUILDERS = [
  {
    id: "yelp",
    url: ({ niche, city }) => `https://www.yelp.com/search?find_desc=${encodeURIComponent(niche)}&find_loc=${encodeURIComponent(city)}`
  },
  {
    id: "bbb",
    url: ({ niche, city }) => `https://www.bbb.org/search?find_text=${encodeURIComponent(niche)}&find_loc=${encodeURIComponent(city)}`
  },
  {
    id: "angi",
    url: ({ niche, city }) => `https://www.angi.com/companylist/us/${encodeURIComponent(city.toLowerCase().replace(/,\s*usa$/i, "").replace(/[^a-z0-9]+/g, "-"))}/${encodeURIComponent(niche.toLowerCase().replace(/[^a-z0-9]+/g, "-"))}.htm`
  }
];

const SOLAR_SOURCE_BUILDERS = [
  {
    id: "energysage",
    url: ({ city }) => `https://www.energysage.com/supplier/search/?q=${encodeURIComponent(city)}`
  },
  {
    id: "nabcep",
    url: ({ city }) => `https://directories.nabcep.org/?q=${encodeURIComponent(city)}`
  },
  {
    id: "seia",
    url: ({ city }) => `https://www.seia.org/directory?search=${encodeURIComponent(city)}`
  }
];

const CONTRACTOR_SOURCE_BUILDERS = [
  {
    id: "contractor-license",
    url: ({ stateName, niche }) => `https://www.google.com/search?q=${encodeURIComponent(`${stateName} ${niche} contractor license lookup business owner`)}`
  },
  {
    id: "secretary-of-state",
    url: ({ stateName, niche }) => `https://www.google.com/search?q=${encodeURIComponent(`${stateName} secretary of state business search ${niche} owner registered agent`)}`
  }
];

export class DirectoryDiscoveryAdapter {
  constructor({ fetcher = fetch, scrapling = null } = {}) {
    this.fetcher = fetcher;
    this.scrapling = scrapling;
  }

  async searchDirectoryOfficialSite({ city, niche, countryScope = "United States", limit = 20 } = {}) {
    const sourceBreakdown = [];
    const stateName = stateNameFromLocation(city);
    const builders = [
      ...SOURCE_BUILDERS,
      ...(isSolarNiche(niche) ? SOLAR_SOURCE_BUILDERS : []),
      ...(isContractorLikeNiche(niche) ? CONTRACTOR_SOURCE_BUILDERS : [])
    ];
    const rows = [];

    for (const builder of builders) {
      if (rows.length >= limit) break;
      const url = builder.url({ city, niche, stateName });
      const result = await this.fetchSource(url);
      sourceBreakdown.push({ source: builder.id, url, ok: result.ok, count: 0, error: result.error || "" });
      if (!result.ok) continue;
      const parsedRows = parseDirectoryHtml(result.html, {
        city,
        niche,
        countryScope,
        source: builder.id,
        sourceUrl: url
      });
      sourceBreakdown[sourceBreakdown.length - 1].count = parsedRows.length;
      rows.push(...parsedRows);
    }

    const seeds = uniqueBy(rows, directoryLeadKey)
      .filter((row) => isUsSeed(row, countryScope))
      .slice(0, limit);
    return { seeds, sourceBreakdown };
  }

  async fetchSource(url) {
    if (this.scrapling?.available?.()) {
      const scraped = await this.scrapling.enrich({ url, mode: "stealth", timeout: Math.ceil(DEFAULT_TIMEOUT_MS / 1000) });
      if (scraped.ok && (scraped.text || scraped.links?.length)) {
        const linkHtml = (scraped.links || []).map((link) => `<a href="${escapeHtmlAttr(link)}">${escapeHtmlAttr(link)}</a>`).join("\n");
        return {
          ok: true,
          html: `${scraped.title || ""}\n${scraped.text || ""}\n${linkHtml}`.slice(0, MAX_HTML_CHARS),
          via: "scrapling-stealth"
        };
      }
    }
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
    try {
      const response = await this.fetcher(url, {
        signal: controller.signal,
        headers: {
          "user-agent": "HermesDirectoryDiscovery/0.1 (+local workflow)",
          "accept": "text/html,application/xhtml+xml"
        }
      });
      if (!response?.ok) return { ok: false, error: `HTTP ${response?.status || "failed"}` };
      const html = String(await response.text()).slice(0, MAX_HTML_CHARS);
      return { ok: true, html };
    } catch (error) {
      return { ok: false, error: error.name === "AbortError" ? "timeout" : error.message };
    } finally {
      clearTimeout(timer);
    }
  }
}

function escapeHtmlAttr(value = "") {
  return String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
}

function parseDirectoryHtml(html, { city, niche, countryScope, source, sourceUrl }) {
  const text = stripTags(html);
  const phones = uniqueMatches(text, /\+?1?[\s.-]?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}\b/g)
    .map(normalizePhone)
    .filter(Boolean);
  const websites = extractWebsiteUrls(html).filter((url) => !isDirectoryHost(url)).slice(0, 30);
  const names = extractBusinessNames(html, text, niche).slice(0, Math.max(phones.length, websites.length, 12));
  const rows = [];
  const count = Math.max(names.length, phones.length, websites.length);
  for (let index = 0; index < count; index += 1) {
    const name = names[index] || names[0] || businessNameFromUrl(websites[index]) || "";
    if (!name || isBadBusinessName(name, niche)) continue;
    rows.push({
      name,
      category: niche,
      address: city,
      city,
      country: countryScope,
      phone: phones[index] || "",
      website: websites[index] || "",
      source,
      sourceUrl,
      discoveryMode: "directory_official_site"
    });
  }
  return rows;
}

function extractBusinessNames(html, text, niche) {
  const candidates = [];
  const jsonLd = html.match(/<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi) || [];
  for (const block of jsonLd) {
    const body = stripTags(block);
    for (const match of body.matchAll(/"name"\s*:\s*"([^"]{3,100})"/gi)) candidates.push(cleanName(match[1]));
  }
  for (const match of html.matchAll(/(?:aria-label|title|alt)=["']([^"']{3,100})["']/gi)) candidates.push(cleanName(match[1]));
  for (const line of text.split(/\n| {2,}/)) {
    const clean = cleanName(line);
    if (looksLikeBusinessName(clean, niche)) candidates.push(clean);
  }
  return uniqueBy(candidates.filter(Boolean), (name) => name.toLowerCase());
}

function extractWebsiteUrls(html) {
  const urls = [];
  for (const match of html.matchAll(/https?:\/\/[^\s"'<>\\)]+/gi)) {
    const url = normalizeUrl(match[0].replace(/[.,;]+$/, ""));
    if (url) urls.push(url);
  }
  for (const match of html.matchAll(/href=["']([^"']+)["']/gi)) {
    const url = normalizeUrl(match[1]);
    if (url) urls.push(url);
  }
  return uniqueBy(urls, hostKey);
}

function stripTags(value) {
  return String(value || "")
    .replace(/<script[\s\S]*?<\/script>/gi, "\n")
    .replace(/<style[\s\S]*?<\/style>/gi, "\n")
    .replace(/<[^>]+>/g, "\n")
    .replace(/&amp;/g, "&")
    .replace(/&quot;/g, "\"")
    .replace(/&#39;/g, "'")
    .replace(/&nbsp;/g, " ");
}

function cleanName(value) {
  return normalizeText(String(value || "").replace(/\\u0026/g, "&").replace(/\s+-\s+.*$/, ""));
}

function looksLikeBusinessName(value, niche) {
  if (value.length < 4 || value.length > 90) return false;
  if (!/[a-z]/i.test(value) || /^[\d\s().+-]+$/.test(value)) return false;
  if (/\b(results|reviews|near me|search|login|privacy|terms|sponsored|advertis|cookie|copyright)\b/i.test(value)) return false;
  const nicheText = normalizeText(niche).toLowerCase();
  const text = value.toLowerCase();
  if (isSolarNiche(nicheText)) return /\b(solar|energy|renewable|photovoltaic|pv)\b/i.test(text);
  if (/\bplumb/i.test(nicheText)) return /\b(plumb|rooter|drain|pipe|mechanical)\b/i.test(text);
  return nicheText.split(/\s+/).some((token) => token.length > 3 && text.includes(token.replace(/s$/, "")));
}

function isBadBusinessName(name, niche) {
  return !looksLikeBusinessName(name, niche);
}

function uniqueMatches(text, regex) {
  return uniqueBy([...String(text || "").matchAll(regex)].map((match) => match[0]), (value) => value.replace(/\D/g, ""));
}

function isDirectoryHost(url) {
  const host = hostKey(url);
  return /(yelp|bbb|angi|angieslist|google|facebook|linkedin|twitter|x\.com|instagram|energysage|nabcep|seia)\./i.test(host);
}

function hostKey(url) {
  try {
    return new URL(url).hostname.replace(/^www\./, "").toLowerCase();
  } catch {
    return String(url || "").toLowerCase();
  }
}

function directoryLeadKey(row) {
  return [normalizeText(row.name).toLowerCase(), hostKey(row.website), normalizeText(row.phone)].filter(Boolean).join("|");
}

function businessNameFromUrl(url) {
  const host = hostKey(url).split(".")[0] || "";
  return host.split(/[-_]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
}

function isUsSeed(row, countryScope) {
  const text = `${row.address || ""} ${row.city || ""} ${row.country || ""}`.toLowerCase();
  if (/\b(canada|uk|united kingdom|australia|india|pakistan|mexico)\b/.test(text)) return false;
  return /united states|usa|,\s*[a-z]{2}\b/i.test(text) || /united states/i.test(countryScope);
}

function isSolarNiche(niche) {
  return /\b(solar|photovoltaic|pv|renewable energy)\b/i.test(String(niche || ""));
}

function isContractorLikeNiche(niche) {
  return /\b(solar|plumb|roof|hvac|electric|contractor|construction)\b/i.test(String(niche || ""));
}

const STATE_NAMES = {
  AL: "alabama", AK: "alaska", AZ: "arizona", AR: "arkansas", CA: "california", CO: "colorado", CT: "connecticut", DE: "delaware", FL: "florida", GA: "georgia", HI: "hawaii", ID: "idaho", IL: "illinois", IN: "indiana", IA: "iowa", KS: "kansas", KY: "kentucky", LA: "louisiana", ME: "maine", MD: "maryland", MA: "massachusetts", MI: "michigan", MN: "minnesota", MS: "mississippi", MO: "missouri", MT: "montana", NE: "nebraska", NV: "nevada", NH: "new hampshire", NJ: "new jersey", NM: "new mexico", NY: "new york", NC: "north carolina", ND: "north dakota", OH: "ohio", OK: "oklahoma", OR: "oregon", PA: "pennsylvania", RI: "rhode island", SC: "south carolina", SD: "south dakota", TN: "tennessee", TX: "texas", UT: "utah", VT: "vermont", VA: "virginia", WA: "washington", WV: "west virginia", WI: "wisconsin", WY: "wyoming", DC: "district of columbia"
};

function stateNameFromLocation(city) {
  const code = String(city || "").match(/,\s*([A-Z]{2})(?:,|\s|$)/i)?.[1]?.toUpperCase();
  if (code && STATE_NAMES[code]) return STATE_NAMES[code];
  return normalizeText(String(city || "").split(",")[0] || "United States");
}
