/**
 * Brand Identity Extraction Service
 * ------------------------------------
 * Fetches a website's homepage and extracts:
 *   - Logo URL (og:image, favicon, header image)
 *   - Brand colors (from inline styles and meta tags)
 *   - Typography (font-family from CSS, Google Fonts)
 *   - Industry + description (via Claude AI)
 *
 * All extraction is best-effort — failures return null per field.
 */

import { prisma } from "@/lib/prisma";
import { log } from "@/lib/logger";

// ─── Types ───────────────────────────────────────────────────────────────────

export interface ExtractedBrand {
  logoUrl: string | null;
  primaryColor: string | null;
  secondaryColor: string | null;
  accentColor: string | null;
  fontFamily: string | null;
  industry: string | null;
  shortDescription: string | null;
  brandVoice: string | null;
  toneHints: string | null;
  lightBackground: string | null;
  darkBackground: string | null;
  textColor: string | null;
}

// ─── Main extraction function ────────────────────────────────────────────────

export async function extractBrandIdentity(domain: string): Promise<ExtractedBrand> {
  const result: ExtractedBrand = {
    logoUrl: null,
    primaryColor: null,
    secondaryColor: null,
    accentColor: null,
    fontFamily: null,
    industry: null,
    shortDescription: null,
    brandVoice: null,
    toneHints: null,
    lightBackground: null,
    darkBackground: null,
    textColor: null,
  };

  const url = domain.startsWith("http") ? domain : `https://${domain}`;

  let html = "";
  try {
    const res = await fetch(url, {
      headers: { "User-Agent": "meSEO-Bot/1.0 (brand-extraction)" },
      signal: AbortSignal.timeout(10000),
    });
    if (!res.ok) {
      log.warn("integration", `Failed to fetch ${url}: ${res.status}`);
      return result;
    }
    html = await res.text();
  } catch (err) {
    log.warn("integration", `Could not fetch ${url}: ${err instanceof Error ? err.message : "timeout"}`);
    return result;
  }

  // ── Extract logo ──
  result.logoUrl = extractLogo(html, url);

  // ── Extract colors ──
  const colors = extractColors(html);
  result.primaryColor = colors.primary;
  result.secondaryColor = colors.secondary;
  result.accentColor = colors.accent;

  // ── Extract typography ──
  result.fontFamily = extractFont(html);

  // ── AI enrichment (industry + description + brand voice) ──
  const ai = await aiEnrich(html, domain);
  result.industry = ai.industry;
  result.shortDescription = ai.shortDescription;
  result.brandVoice = ai.brandVoice;
  result.toneHints = ai.toneHints;

  // ── Generate theme from extracted primary color ──
  if (result.primaryColor) {
    const theme = generateTheme(result.primaryColor);
    result.lightBackground = theme.lightBackground;
    result.darkBackground = theme.darkBackground;
    result.textColor = theme.textColor;
  }

  log.info("integration", `Extracted brand identity for ${domain}`, {
    metadata: {
      hasLogo: !!result.logoUrl,
      hasColors: !!result.primaryColor,
      hasFont: !!result.fontFamily,
      industry: result.industry,
      brandVoice: result.brandVoice,
    },
  });

  return result;
}

// ─── Save extraction to database ─────────────────────────────────────────────

export async function saveExtractedBrand(brandId: string, data: ExtractedBrand): Promise<void> {
  await prisma.brandProfile.upsert({
    where: { brandId },
    create: {
      brandId,
      logoUrl: data.logoUrl,
      primaryColor: data.primaryColor,
      secondaryColor: data.secondaryColor,
      accentColor: data.accentColor,
      fontFamily: data.fontFamily,
      industry: data.industry,
      shortDescription: data.shortDescription,
      brandVoice: data.brandVoice,
      toneHints: data.toneHints,
      lightBackground: data.lightBackground,
      darkBackground: data.darkBackground,
      textColor: data.textColor,
      brandColors: [data.primaryColor, data.secondaryColor, data.accentColor].filter(Boolean) as string[],
      extractedAt: new Date(),
    },
    update: {
      logoUrl: data.logoUrl ?? undefined,
      primaryColor: data.primaryColor ?? undefined,
      secondaryColor: data.secondaryColor ?? undefined,
      accentColor: data.accentColor ?? undefined,
      fontFamily: data.fontFamily ?? undefined,
      industry: data.industry ?? undefined,
      shortDescription: data.shortDescription ?? undefined,
      brandVoice: data.brandVoice ?? undefined,
      toneHints: data.toneHints ?? undefined,
      lightBackground: data.lightBackground ?? undefined,
      darkBackground: data.darkBackground ?? undefined,
      textColor: data.textColor ?? undefined,
      brandColors: [data.primaryColor, data.secondaryColor, data.accentColor].filter(Boolean) as string[],
      extractedAt: new Date(),
    },
  });

  // Also update the brand's industry if it was null
  if (data.industry) {
    await prisma.brand.update({
      where: { id: brandId },
      data: { industry: data.industry },
    });
  }
}

// ─── Logo extraction ─────────────────────────────────────────────────────────

function extractLogo(html: string, baseUrl: string): string | null {
  // Priority 1: og:image
  const ogMatch = html.match(/<meta[^>]+property=["']og:image["'][^>]+content=["']([^"']+)["']/i)
    ?? html.match(/<meta[^>]+content=["']([^"']+)["'][^>]+property=["']og:image["']/i);
  if (ogMatch?.[1]) return resolveUrl(ogMatch[1], baseUrl);

  // Priority 2: favicon (apple-touch-icon preferred, then standard)
  const appleIcon = html.match(/<link[^>]+rel=["']apple-touch-icon["'][^>]+href=["']([^"']+)["']/i);
  if (appleIcon?.[1]) return resolveUrl(appleIcon[1], baseUrl);

  const favicon = html.match(/<link[^>]+rel=["'](?:shortcut )?icon["'][^>]+href=["']([^"']+)["']/i)
    ?? html.match(/<link[^>]+href=["']([^"']+)["'][^>]+rel=["'](?:shortcut )?icon["']/i);
  if (favicon?.[1]) return resolveUrl(favicon[1], baseUrl);

  // Priority 3: first image in header/nav with "logo" in src, alt, or class
  const logoImg = html.match(/<(?:header|nav)[^>]*>[\s\S]*?<img[^>]+src=["']([^"']+)["'][^>]*>/i);
  if (logoImg?.[1]) return resolveUrl(logoImg[1], baseUrl);

  // Priority 4: any img with "logo" in attributes
  const anyLogo = html.match(/<img[^>]*(?:class|alt|id)=["'][^"']*logo[^"']*["'][^>]+src=["']([^"']+)["']/i)
    ?? html.match(/<img[^>]+src=["']([^"']+logo[^"']*\.(?:png|svg|jpg|webp))["']/i);
  if (anyLogo?.[1]) return resolveUrl(anyLogo[1], baseUrl);

  return null;
}

// ─── Color extraction ────────────────────────────────────────────────────────

function extractColors(html: string): { primary: string | null; secondary: string | null; accent: string | null } {
  const hexColors: string[] = [];

  // Extract from meta theme-color
  const themeColor = html.match(/<meta[^>]+name=["']theme-color["'][^>]+content=["'](#[0-9a-fA-F]{3,8})["']/i);
  if (themeColor?.[1]) hexColors.push(themeColor[1]);

  // Extract hex colors from inline styles and style tags
  const styleBlocks = html.match(/<style[^>]*>([\s\S]*?)<\/style>/gi) ?? [];
  const allStyles = styleBlocks.join(" ") + " " + (html.match(/style=["'][^"']*["']/gi) ?? []).join(" ");

  const hexMatches = allStyles.match(/#[0-9a-fA-F]{6}\b/g) ?? [];
  for (const hex of hexMatches) {
    if (!hexColors.includes(hex) && !isNearWhiteOrBlack(hex)) {
      hexColors.push(hex);
    }
  }

  // Extract from CSS custom properties (--primary-color, --brand-color, etc.)
  const cssVarMatches = allStyles.match(/--(?:primary|brand|main|accent|secondary)[^:]*:\s*(#[0-9a-fA-F]{6})/gi) ?? [];
  for (const match of cssVarMatches) {
    const hex = match.match(/#[0-9a-fA-F]{6}/)?.[0];
    if (hex && !hexColors.includes(hex)) hexColors.unshift(hex); // prioritize named vars
  }

  return {
    primary: hexColors[0] ?? null,
    secondary: hexColors[1] ?? null,
    accent: hexColors[2] ?? null,
  };
}

function isNearWhiteOrBlack(hex: string): boolean {
  const r = parseInt(hex.slice(1, 3), 16);
  const g = parseInt(hex.slice(3, 5), 16);
  const b = parseInt(hex.slice(5, 7), 16);
  const brightness = (r + g + b) / 3;
  return brightness > 240 || brightness < 15;
}

// ─── Font extraction ─────────────────────────────────────────────────────────

function extractFont(html: string): string | null {
  // Check for Google Fonts link
  const googleFont = html.match(/fonts\.googleapis\.com\/css2?\?family=([^"'&]+)/i);
  if (googleFont?.[1]) {
    return decodeURIComponent(googleFont[1]).replace(/\+/g, " ").split(":")[0];
  }

  // Extract from CSS font-family declarations
  const fontMatch = html.match(/font-family\s*:\s*["']?([^;}"']+)/i);
  if (fontMatch?.[1]) {
    const font = fontMatch[1].trim().split(",")[0].replace(/["']/g, "").trim();
    if (font && font !== "inherit" && font !== "initial") return font;
  }

  return null;
}

// ─── AI enrichment (Claude) ──────────────────────────────────────────────────

async function aiEnrich(html: string, domain: string): Promise<{
  industry: string | null; shortDescription: string | null;
  brandVoice: string | null; toneHints: string | null;
}> {
  const apiKey = process.env.ANTHROPIC_API_KEY;
  if (!apiKey) return { industry: null, shortDescription: null, brandVoice: null, toneHints: null };

  // Extract text content (strip HTML tags, limit to 2000 chars)
  const textContent = html
    .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
    .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
    .replace(/<[^>]+>/g, " ")
    .replace(/\s+/g, " ")
    .trim()
    .slice(0, 2000);

  // Extract title and meta description
  const title = html.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1]?.trim() ?? "";
  const metaDesc = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']+)["']/i)?.[1]?.trim() ?? "";

  try {
    const res = await fetch("https://api.anthropic.com/v1/messages", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": apiKey,
        "anthropic-version": "2023-06-01",
      },
      body: JSON.stringify({
        model: process.env.AI_MODEL_ID || "claude-sonnet-4-20250514",
        max_tokens: 200,
        messages: [{
          role: "user",
          content: `Analyze this website and respond with ONLY valid JSON (no markdown, no explanation):
{"industry":"one word industry category","shortDescription":"one sentence describing what this company does","brandVoice":"one word: professional OR medical OR premium OR friendly OR technical OR corporate OR creative","toneHints":"one sentence describing the communication style and personality of this brand"}

Website: ${domain}
Title: ${title}
Description: ${metaDesc}
Content preview: ${textContent.slice(0, 500)}`,
        }],
      }),
    });

    if (!res.ok) return { industry: null, shortDescription: null, brandVoice: null, toneHints: null };

    const data = await res.json();
    const text = data.content?.[0]?.text ?? "";

    // Parse JSON from response (handle potential markdown wrapping)
    const jsonStr = text.replace(/```json\n?|\n?```/g, "").trim();
    const parsed = JSON.parse(jsonStr);

    return {
      industry: parsed.industry ?? null,
      shortDescription: parsed.shortDescription ?? null,
      brandVoice: parsed.brandVoice ?? null,
      toneHints: parsed.toneHints ?? null,
    };
  } catch {
    return { industry: null, shortDescription: null, brandVoice: null, toneHints: null };
  }
}

// ─── Theme generation ────────────────────────────────────────────────────────

function generateTheme(primaryHex: string): { lightBackground: string; darkBackground: string; textColor: string } {
  // Parse hex to RGB
  const r = parseInt(primaryHex.slice(1, 3), 16);
  const g = parseInt(primaryHex.slice(3, 5), 16);
  const b = parseInt(primaryHex.slice(5, 7), 16);

  // Light background: very faint tint of the primary color (95% white + 5% color)
  const lr = Math.round(255 * 0.95 + r * 0.05);
  const lg = Math.round(255 * 0.95 + g * 0.05);
  const lb = Math.round(255 * 0.95 + b * 0.05);
  const lightBackground = `#${lr.toString(16).padStart(2, "0")}${lg.toString(16).padStart(2, "0")}${lb.toString(16).padStart(2, "0")}`;

  // Dark background: dark version of the primary (20% brightness)
  const dr = Math.round(r * 0.2);
  const dg = Math.round(g * 0.2);
  const db = Math.round(b * 0.2);
  const darkBackground = `#${dr.toString(16).padStart(2, "0")}${dg.toString(16).padStart(2, "0")}${db.toString(16).padStart(2, "0")}`;

  // Text color: white if primary is dark, dark if primary is light
  const brightness = (r * 299 + g * 587 + b * 114) / 1000;
  const textColor = brightness > 128 ? "#1e293b" : "#ffffff";

  return { lightBackground, darkBackground, textColor };
}

// ─── URL resolver ────────────────────────────────────────────────────────────

function resolveUrl(path: string, baseUrl: string): string {
  if (path.startsWith("http")) return path;
  if (path.startsWith("//")) return `https:${path}`;
  try {
    return new URL(path, baseUrl).href;
  } catch {
    return path;
  }
}

// ─── Service Extraction ───────────────────────────────────────────────────────

export interface ExtractedServices {
  primaryServices: string[];
  secondaryServices: string[];
}

export async function extractBrandServices(domain: string): Promise<ExtractedServices | null> {
  const apiKey = process.env.ANTHROPIC_API_KEY;
  if (!apiKey) return null;

  const url = domain.startsWith("http") ? domain : `https://${domain}`;

  let html = "";
  try {
    const res = await fetch(url, {
      headers: { "User-Agent": "meSEO-Bot/1.0 (brand-extraction)" },
      signal: AbortSignal.timeout(10000),
    });
    if (!res.ok) {
      log.warn("integration", `extractBrandServices: failed to fetch ${url}: ${res.status}`);
      return null;
    }
    html = await res.text();
  } catch (err) {
    log.warn("integration", `extractBrandServices: could not fetch ${url}: ${err instanceof Error ? err.message : "timeout"}`);
    return null;
  }

  // Strip HTML to plain text — mirrors the aiEnrich() approach in extractBrandIdentity
  const textContent = html
    .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
    .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
    .replace(/<[^>]+>/g, " ")
    .replace(/\s+/g, " ")
    .trim()
    .slice(0, 12000);

  const title = html.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1]?.trim() ?? "";
  const metaDesc = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']+)["']/i)?.[1]?.trim() ?? "";

  const prompt = `Analyze this website's content and extract service categories the business offers.

PRIMARY SERVICES: The main offerings the business is built around. Usually 1-5 services. These are what the business leads with on the homepage, in the hero section, or in primary navigation.

SECONDARY SERVICES: Complementary or supporting services. Usually 2-10 services. These appear in service pages, footers, or as add-ons.

Each service should be 1-4 words. Use proper capitalization. No descriptions.

Examples:
- HVAC company → PRIMARY: ["AC Installation", "HVAC Repair", "Heating Services"], SECONDARY: ["Duct Cleaning", "Air Quality Testing", "Maintenance Plans"]
- Medical practice → PRIMARY: ["Cardiology", "Vascular Surgery"], SECONDARY: ["Diagnostic Imaging", "Lifestyle Counseling", "Preventive Care"]
- Lawn care → PRIMARY: ["Lawn Mowing", "Fertilization", "Weed Control"], SECONDARY: ["Aeration", "Tree Trimming", "Pest Control", "Seasonal Cleanup"]

Return ONLY valid JSON in this shape — no preamble, no markdown fences:
{"primaryServices": ["..."], "secondaryServices": ["..."]}

WEBSITE CONTENT:
Title: ${title}
Meta description: ${metaDesc}
${textContent}`;

  try {
    const res = await fetch("https://api.anthropic.com/v1/messages", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": apiKey,
        "anthropic-version": "2023-06-01",
      },
      body: JSON.stringify({
        model: process.env.AI_MODEL_ID || "claude-sonnet-4-20250514",
        max_tokens: 400,
        messages: [{ role: "user", content: prompt }],
      }),
    });

    if (!res.ok) return null;

    const data = await res.json();
    const text: string = data.content?.[0]?.text ?? "";
    const cleaned = text.replace(/```json\s*|\s*```/g, "").trim();
    const parsed = JSON.parse(cleaned);

    if (!Array.isArray(parsed.primaryServices) || !Array.isArray(parsed.secondaryServices)) {
      return null;
    }

    const primary: string[] = [...new Set<string>(
      (parsed.primaryServices as unknown[]).map((s) => String(s).trim()).filter(Boolean)
    )].slice(0, 5);
    const secondary: string[] = [...new Set<string>(
      (parsed.secondaryServices as unknown[]).map((s) => String(s).trim()).filter(Boolean)
    )].slice(0, 10);

    return { primaryServices: primary, secondaryServices: secondary };
  } catch (err) {
    console.error("extractBrandServices Claude call failed:", err);
    return null;
  }
}
