import { promises as fs } from "fs";
import path from "path";
import type { BrandProfile, Project } from "@/types/workflow";

type ScrapedBrandVertical = "fax" | "sms" | "email" | "generic";

const CACHE_DIR = path.join(process.cwd(), "data", "website-cache");
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
const FETCH_TIMEOUT_MS = 15_000;
const MAX_HTML_BYTES = 1_500_000;

export interface WebsiteIntelligence {
  url: string;
  fetchedAt: string;
  ok: boolean;
  error?: string;
  title: string;
  description: string;
  tagline: string;
  companyName: string;
  primaryService: string;
  services: Array<{ label: string; detail: string }>;
  benefits: string[];
  keywords: string[];
  themeColor: string | null;
  accentColor: string | null;
  vertical: ScrapedBrandVertical;
  productVisualHint: string;
  headings: string[];
  logoUrl: string | null;
  /** Public contact phone scraped from tel: links or page text. */
  contactPhone: string;
  /** Public contact email scraped from mailto: links or page text. */
  contactEmail: string;
  analysisMarkdown: string;
}

interface CacheEntry {
  fetchedAt: string;
  data: WebsiteIntelligence;
}

function normalizeWebsiteUrl(raw: string): string | null {
  const trimmed = raw.trim();
  if (!trimmed) return null;
  if (/^https?:\/\//i.test(trimmed)) return trimmed.replace(/\/$/, "");
  return `https://${trimmed.replace(/\/$/, "")}`;
}

function decodeHtmlEntities(text: string): string {
  return text
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/&nbsp;/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function stripTags(html: string): string {
  return decodeHtmlEntities(html.replace(/<[^>]+>/g, " "));
}

function extractMetaContent(html: string, key: string, attr: "name" | "property" = "name"): string {
  const patterns = [
    new RegExp(`<meta[^>]+${attr}=["']${key}["'][^>]+content=["']([^"']+)["']`, "i"),
    new RegExp(`<meta[^>]+content=["']([^"']+)["'][^>]+${attr}=["']${key}["']`, "i"),
  ];
  for (const re of patterns) {
    const match = html.match(re);
    if (match?.[1]) return decodeHtmlEntities(match[1]);
  }
  return "";
}

function extractTitle(html: string): string {
  const og = extractMetaContent(html, "og:title", "property");
  if (og) return og;
  const match = html.match(/<title[^>]*>([^<]+)<\/title>/i);
  return match?.[1] ? decodeHtmlEntities(match[1]) : "";
}

function extractHeadings(html: string, tag: "h1" | "h2" | "h3", limit: number): string[] {
  const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gi");
  const results: string[] = [];
  let match: RegExpExecArray | null;
  while ((match = re.exec(html)) && results.length < limit) {
    const text = stripTags(match[1] ?? "").trim();
    if (text.length >= 4 && text.length <= 120) results.push(text);
  }
  return results;
}

function extractListItems(html: string, limit: number): string[] {
  const re = /<li[^>]*>([\s\S]*?)<\/li>/gi;
  const results: string[] = [];
  let match: RegExpExecArray | null;
  while ((match = re.exec(html)) && results.length < limit) {
    const text = stripTags(match[1] ?? "").trim();
    if (text.length >= 12 && text.length <= 140 && !/^https?:\/\//i.test(text)) {
      results.push(text);
    }
  }
  return results;
}

function extractThemeColor(html: string): string | null {
  const meta = extractMetaContent(html, "theme-color");
  if (/^#[0-9a-f]{3,8}$/i.test(meta)) return meta;
  const cssMatch = html.match(/(?:--primary|--brand|theme-color)\s*:\s*(#[0-9a-f]{3,8})/i);
  return cssMatch?.[1] ?? null;
}

function detectVerticalFromText(text: string): ScrapedBrandVertical {
  const hay = text.toLowerCase();
  // Service portfolio first — brand name alone must not force fax
  if (/dnc|do.?not.?call|lead.?scrub|deduplicat|data.?clean/i.test(hay)) {
    // map extended categories onto legacy scrape vertical for cache compatibility
    return "generic";
  }
  if (/call.?track|\bdni\b|dialer|ringpilot/i.test(hay)) return "generic";
  if (/sms.?broadcast|sms.?marketing|text.?broadcast|bulk.?sms|mms|\bsms\b/.test(hay)) return "sms";
  if (/email.?broadcast|email.?marketing|mass.?email|newsletter|bulk.?email/.test(hay)) return "email";
  if (/efax|e-fax|fax.?broadcast|b2b.?fax|\bfax\b/.test(hay) && !/b2befax/.test(hay.replace(/fax/g, ""))) {
    return "fax";
  }
  // If only brand string matches b2befax without clear fax service language in services section
  if (/\bfax\b|e-?fax|fax.?broadcast/i.test(hay)) return "fax";
  return "generic";
}

function productVisualHintForVertical(vertical: ScrapedBrandVertical, services: string[]): string {
  const serviceHint = services.slice(0, 3).join(", ");
  if (serviceHint) return serviceHint;
  switch (vertical) {
    case "fax":
      return "secure cloud document delivery dashboard — no physical fax machines or paper distress";
    case "sms":
      return "mobile messaging and SMS campaign dashboards";
    case "email":
      return "email marketing and inbox delivery dashboards";
    default:
      return "modern operations dashboard and cloud infrastructure in a bright office";
  }
}

function splitServiceLine(line: string): { label: string; detail: string } {
  const cleaned = line.trim();
  const colon = cleaned.indexOf(":");
  if (colon > 8 && colon < 60) {
    return {
      label: cleaned.slice(0, colon).trim(),
      detail: cleaned.slice(colon + 1).trim(),
    };
  }
  const words = cleaned.split(/\s+/);
  return {
    label: words.slice(0, Math.min(6, words.length)).join(" "),
    detail: cleaned,
  };
}

function buildServices(
  headings: string[],
  listItems: string[],
  description: string,
): Array<{ label: string; detail: string }> {
  const candidates = [...headings.slice(1, 5), ...listItems.slice(0, 8)];
  const unique: string[] = [];
  for (const item of candidates) {
    const norm = item.toLowerCase();
    if (!unique.some((u) => u.toLowerCase() === norm)) unique.push(item);
    if (unique.length >= 4) break;
  }

  if (unique.length === 0 && description) {
    const sentences = description.split(/[.!?]/).map((s) => s.trim()).filter((s) => s.length > 20);
    for (const sentence of sentences.slice(0, 4)) unique.push(sentence);
  }

  return unique.slice(0, 4).map(splitServiceLine);
}

function buildBenefits(services: Array<{ label: string; detail: string }>, description: string): string[] {
  const fromLabels = services.map((s) => s.label.split(/\s+/).slice(0, 3).join(" ").toUpperCase());
  if (fromLabels.length >= 3) return fromLabels.slice(0, 4);

  const words = description.toLowerCase();
  const benefits: string[] = [];
  if (/cost|save|budget/.test(words)) benefits.push("REDUCE COSTS");
  if (/secure|compliance|trust/.test(words)) benefits.push("STAY SECURE");
  if (/fast|instant|quick/.test(words)) benefits.push("SAVE TIME");
  if (/grow|scale|reach/.test(words)) benefits.push("GROW MORE");
  return benefits.length > 0 ? benefits.slice(0, 4) : fromLabels.slice(0, 4);
}

function extractContactEmail(html: string): string {
  const mailto = html.match(/mailto:([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i);
  if (mailto?.[1]) return mailto[1].toLowerCase();

  const plain = html.match(
    /(?:contact|email|support|sales|info)[^a-zA-Z0-9._%+-]{0,40}([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i,
  );
  if (plain?.[1]) return plain[1].toLowerCase();

  const any = html.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);
  return any?.[1]?.toLowerCase() ?? "";
}

function normalizePhone(raw: string): string {
  const cleaned = raw.replace(/[^\d+().\-\s]/g, "").replace(/\s+/g, " ").trim();
  const digits = cleaned.replace(/\D/g, "");
  if (digits.length < 7 || digits.length > 15) return "";
  return cleaned.slice(0, 32);
}

function extractContactPhone(html: string): string {
  const tel = html.match(/tel:([+\d][\d\s().-]{6,20}\d)/i);
  if (tel?.[1]) {
    const normalized = normalizePhone(tel[1]);
    if (normalized) return normalized;
  }

  const labeled = html.match(
    /(?:phone|tel|call|mobile|contact)[^0-9+]{0,24}(\+?\d[\d\s().-]{6,18}\d)/i,
  );
  if (labeled?.[1]) {
    const normalized = normalizePhone(labeled[1]);
    if (normalized) return normalized;
  }

  return "";
}

function deriveAccentColor(primary: string | null): string | null {
  if (!primary) return null;
  const match = primary.match(/^#([0-9a-f]{6})$/i);
  if (!match?.[1]) return null;
  const hex = match[1];
  const r = parseInt(hex.slice(0, 2), 16);
  const g = parseInt(hex.slice(2, 4), 16);
  const b = parseInt(hex.slice(4, 6), 16);
  const accentR = Math.min(255, r + 40);
  const accentG = Math.min(255, g + 60);
  const accentB = Math.max(0, b - 20);
  return `#${accentR.toString(16).padStart(2, "0")}${accentG.toString(16).padStart(2, "0")}${accentB.toString(16).padStart(2, "0")}`;
}

function emptyWebsiteIntel(url: string, error: string): WebsiteIntelligence {
  return {
    url,
    fetchedAt: new Date().toISOString(),
    ok: false,
    error,
    title: "",
    description: "",
    tagline: "",
    companyName: "",
    primaryService: "",
    services: [],
    benefits: [],
    keywords: [],
    themeColor: null,
    accentColor: null,
    vertical: "generic",
    productVisualHint: "",
    headings: [],
    logoUrl: null,
    contactPhone: "",
    contactEmail: "",
    analysisMarkdown: `Website scrape failed: ${error}`,
  };
}

/** Fetch and parse public homepage content for automated branding. */
export async function scrapeWebsite(url: string): Promise<WebsiteIntelligence> {
  const normalized = normalizeWebsiteUrl(url);
  if (!normalized) return emptyWebsiteIntel(url, "No website URL provided");

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);

  try {
    const res = await fetch(normalized, {
      signal: controller.signal,
      headers: {
        "User-Agent": "PostSyncPro/1.0 (brand-intelligence; +https://postsyncpro.gventure.info)",
        Accept: "text/html,application/xhtml+xml",
      },
      redirect: "follow",
    });

    if (!res.ok) return emptyWebsiteIntel(normalized, `HTTP ${res.status}`);

    const buffer = Buffer.from(await res.arrayBuffer());
    if (buffer.length > MAX_HTML_BYTES) {
      return emptyWebsiteIntel(normalized, "Page too large to parse");
    }

    const html = buffer.toString("utf-8");
    const cleanedHtml = html
      .replace(/<script[\s\S]*?<\/script>/gi, "")
      .replace(/<style[\s\S]*?<\/style>/gi, "")
      .replace(/<!--[\s\S]*?-->/g, "");

    const title = extractTitle(cleanedHtml);
    const description =
      extractMetaContent(cleanedHtml, "description") ||
      extractMetaContent(cleanedHtml, "og:description", "property");
    const siteName = extractMetaContent(cleanedHtml, "og:site_name", "property");
    const keywordsRaw = extractMetaContent(cleanedHtml, "keywords");
    const keywords = keywordsRaw
      ? keywordsRaw.split(",").map((k) => k.trim()).filter(Boolean).slice(0, 12)
      : [];

    const h1 = extractHeadings(cleanedHtml, "h1", 3);
    const h2 = extractHeadings(cleanedHtml, "h2", 8);
    const h3 = extractHeadings(cleanedHtml, "h3", 6);
    const headings = [...h1, ...h2, ...h3];
    const listItems = extractListItems(cleanedHtml, 12);

    const companyName = siteName || h1[0] || title.split(/[|\-–—]/)[0]?.trim() || "";
    const tagline = description || h2[0] || h1[0] || "";
    const services = buildServices(headings, listItems, description);
    const primaryService = services[0]?.label || h1[0] || h2[0] || title;
    const combinedText = [title, description, ...headings, ...listItems, keywords.join(" ")].join(" ");
    const vertical = detectVerticalFromText(combinedText);
    const themeColor = extractThemeColor(cleanedHtml);
    const accentColor = deriveAccentColor(themeColor);
    const logoUrl =
      extractMetaContent(cleanedHtml, "og:image", "property") ||
      extractMetaContent(cleanedHtml, "twitter:image", "property") ||
      null;
    const benefits = buildBenefits(services, description);
    const productVisualHint = productVisualHintForVertical(
      vertical,
      services.map((s) => s.label),
    );
    const contactEmail = extractContactEmail(cleanedHtml);
    const contactPhone = extractContactPhone(cleanedHtml);

    const analysisMarkdown = [
      "## Website Intelligence (scraped)",
      `URL: ${normalized}`,
      `Title: ${title || "—"}`,
      `Description: ${description || "—"}`,
      `Company: ${companyName || "—"}`,
      `Primary service: ${primaryService || "—"}`,
      services.length ? `Services:\n${services.map((s) => `- ${s.label}: ${s.detail}`).join("\n")}` : "",
      contactPhone ? `Phone: ${contactPhone}` : "",
      contactEmail ? `Email: ${contactEmail}` : "",
      keywords.length ? `Keywords: ${keywords.join(", ")}` : "",
      themeColor ? `Theme color: ${themeColor}` : "",
    ]
      .filter(Boolean)
      .join("\n");

    return {
      url: normalized,
      fetchedAt: new Date().toISOString(),
      ok: true,
      title,
      description,
      tagline,
      companyName,
      primaryService,
      services,
      benefits,
      keywords,
      themeColor,
      accentColor,
      vertical,
      productVisualHint,
      headings,
      logoUrl,
      contactPhone,
      contactEmail,
      analysisMarkdown,
    };
  } catch (err) {
    const message = err instanceof Error ? err.message : "Fetch failed";
    return emptyWebsiteIntel(normalized, message);
  } finally {
    clearTimeout(timer);
  }
}

async function readCache(projectId: string): Promise<CacheEntry | null> {
  try {
    const filePath = path.join(CACHE_DIR, `${projectId}.json`);
    const raw = await fs.readFile(filePath, "utf-8");
    return JSON.parse(raw) as CacheEntry;
  } catch {
    return null;
  }
}

async function writeCache(projectId: string, data: WebsiteIntelligence): Promise<void> {
  await fs.mkdir(CACHE_DIR, { recursive: true });
  const entry: CacheEntry = { fetchedAt: data.fetchedAt, data };
  await fs.writeFile(path.join(CACHE_DIR, `${projectId}.json`), JSON.stringify(entry, null, 2), "utf-8");
}

/** Resolve website intelligence with per-project cache (24h TTL). */
export async function resolveWebsiteIntelligence(
  projectId: string,
  project: Project,
  _brand: BrandProfile,
  options?: { forceRefresh?: boolean },
): Promise<WebsiteIntelligence | null> {
  const url = project.website?.trim();
  if (!url) return null;

  if (!options?.forceRefresh) {
    const cached = await readCache(projectId);
    if (cached && Date.now() - new Date(cached.fetchedAt).getTime() < CACHE_TTL_MS) {
      return cached.data;
    }
  }

  const data = await scrapeWebsite(url);
  await writeCache(projectId, data);
  return data;
}

export function formatWebsiteIntelligenceMarkdown(website: WebsiteIntelligence | null | undefined): string {
  if (!website) return "## Website Intelligence\nNot available — no website URL configured.";
  return website.analysisMarkdown;
}
