import fs from 'node:fs';
import path from 'node:path';
import { getDb, nowIso } from './db.js';
import { BRAND_DIR, ROOT, ensureDirs, relToRoot } from './paths.js';

const BRAND_CONFIG_KEY = 'brand_profile';
const DEFAULT_GUIDELINES = path.join(ROOT, 'brand-assets', 'brand-guidelines.html');
const DEFAULT_LOGO = path.join(ROOT, 'brand-assets', 'madarx-logo-primary.svg');

export const DEFAULT_BRAND_PROFILE = {
  active: true,
  company_name: 'madarX',
  tagline: '',
  colors: [
    { name: 'Onyx', value: '#0F0F11' },
    { name: 'Platinum', value: '#E8E8E5' },
    { name: 'Bone', value: '#DCDCD8' },
    { name: 'Graphite', value: '#4A4A4D' },
    { name: 'Mars', value: '#C2452A' },
  ],
  fonts: ['Inter', 'Cairo', 'IBM Plex Mono'],
  voice: ['Direct', 'disciplined', 'precise', 'premium', 'local-first'],
  document_types: ['Report', 'Brief', 'Strategy memo', 'Proposal'],
  presentation_style: 'Dark editorial system with restrained layouts, Inter/Cairo/IBM Plex Mono typography, onyx backgrounds, platinum text, and Mars red accents.',
  logo_path: fs.existsSync(DEFAULT_LOGO) ? relToRoot(DEFAULT_LOGO) : null,
  logo_data_url: fileToDataUrl(DEFAULT_LOGO),
  guidelines_path: fs.existsSync(DEFAULT_GUIDELINES) ? relToRoot(DEFAULT_GUIDELINES) : null,
  source_name: 'brand-guidelines.html',
  extracted_at: null,
  updated_at: null,
};

function safeJson(value, fallback) {
  try { return JSON.parse(value); } catch { return fallback; }
}

function titleCase(input) {
  const next = String(input || '')
    .replace(/^--/, '')
    .replace(/[-_]+/g, ' ')
    .replace(/\b\w/g, c => c.toUpperCase())
    .trim();
  return next
    .replace(/^Ibm\b/, 'IBM')
    .replace(/\bUi\b/g, 'UI');
}

function unique(values) {
  return [...new Set(values.filter(Boolean).map(v => String(v).trim()).filter(Boolean))];
}

function cleanText(input) {
  return String(input || '')
    .replace(/<script[\s\S]*?<\/script>/gi, ' ')
    .replace(/<style[\s\S]*?<\/style>/gi, ' ')
    .replace(/<[^>]+>/g, ' ')
    .replace(/&mdash;|&#8212;/g, '-')
    .replace(/&[a-z]+;/gi, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

function extractMeta(content, name) {
  const re = new RegExp(`<meta[^>]+name=["']${name}["'][^>]+content=["']([^"']+)["']`, 'i');
  return content.match(re)?.[1]?.trim() || '';
}

function extractTitle(content) {
  return content.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.replace(/\s+/g, ' ').trim() || '';
}

function extractCssVariableColors(content) {
  const colors = [];
  const re = /--([a-z0-9_-]+)\s*:\s*(#[0-9a-f]{3,8})\b/gi;
  let match;
  while ((match = re.exec(content))) {
    colors.push({ name: titleCase(match[1]), value: match[2].toUpperCase() });
  }
  return colors;
}

function extractColors(content) {
  const fromVars = extractCssVariableColors(content);
  const seen = new Set(fromVars.map(c => c.value.toUpperCase()));
  const re = /#[0-9a-f]{3,8}\b/gi;
  let match;
  while ((match = re.exec(content)) && fromVars.length < 10) {
    const value = match[0].toUpperCase();
    if (!seen.has(value)) {
      seen.add(value);
      fromVars.push({ name: `Color ${fromVars.length + 1}`, value });
    }
  }
  return fromVars.slice(0, 10);
}

function extractFonts(content) {
  const fonts = [];
  for (const match of content.matchAll(/@fontsource\/([^@/'")]+)/gi)) {
    fonts.push(titleCase(match[1]));
  }
  for (const match of content.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
    const first = match[1].split(',')[0].replace(/["']/g, '').trim();
    if (first && !/^(var|system-ui|-apple-system|sans-serif|serif|monospace)$/i.test(first)) fonts.push(first);
  }
  return unique(fonts).slice(0, 8);
}

function inferCompanyName(content, filename) {
  const title = extractTitle(content);
  const first = title.split(/[—|-]/)[0]?.trim();
  if (first) return first.replace(/\s+Brand Guidelines.*$/i, '').trim();
  return path.basename(filename || 'Brand', path.extname(filename || '')).replace(/[-_]+/g, ' ');
}

function inferVoice(text) {
  const lower = text.toLowerCase();
  const candidates = [
    'direct', 'disciplined', 'precise', 'premium', 'minimal', 'editorial',
    'confident', 'restrained', 'local-first', 'bilingual', 'operational',
  ];
  return candidates.filter(word => lower.includes(word)).slice(0, 8);
}

function inferPresentationStyle(profile) {
  const primary = profile.colors?.[0]?.value || '#111318';
  const accent = profile.colors?.find(c => !['#FFFFFF', '#000000'].includes(c.value))?.value || primary;
  const fonts = profile.fonts?.length ? profile.fonts.join(', ') : 'brand typography';
  return `Use ${fonts}; lead with ${profile.company_name || 'the brand'} identity, restrained hierarchy, ${primary} as the base color, and ${accent} as the accent.`;
}

export function extractBrandProfileFromText(content, filename = 'brand-guidelines') {
  const text = cleanText(content);
  const profile = {
    ...DEFAULT_BRAND_PROFILE,
    company_name: inferCompanyName(content, filename) || DEFAULT_BRAND_PROFILE.company_name,
    tagline: extractMeta(content, 'description') || '',
    colors: extractColors(content),
    fonts: extractFonts(content),
    voice: inferVoice(text),
    source_name: filename,
    extracted_at: nowIso(),
    updated_at: nowIso(),
  };
  if (!profile.colors.length) profile.colors = DEFAULT_BRAND_PROFILE.colors;
  if (!profile.fonts.length) profile.fonts = DEFAULT_BRAND_PROFILE.fonts;
  if (!profile.voice.length) profile.voice = DEFAULT_BRAND_PROFILE.voice;
  profile.presentation_style = inferPresentationStyle(profile);
  return profile;
}

function fileToDataUrl(filePath) {
  try {
    if (!filePath || !fs.existsSync(filePath)) return null;
    const ext = path.extname(filePath).toLowerCase();
    const mime = ext === '.svg' ? 'image/svg+xml'
      : ext === '.png' ? 'image/png'
        : ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg'
          : ext === '.webp' ? 'image/webp'
            : 'application/octet-stream';
    const data = fs.readFileSync(filePath);
    if (mime === 'image/svg+xml') return `data:${mime};utf8,${encodeURIComponent(data.toString('utf8'))}`;
    return `data:${mime};base64,${data.toString('base64')}`;
  } catch {
    return null;
  }
}

function defaultExtractedProfile() {
  try {
    if (!fs.existsSync(DEFAULT_GUIDELINES)) return { ...DEFAULT_BRAND_PROFILE };
    const content = fs.readFileSync(DEFAULT_GUIDELINES, 'utf8');
    return {
      ...extractBrandProfileFromText(content, 'brand-guidelines.html'),
      logo_path: fs.existsSync(DEFAULT_LOGO) ? relToRoot(DEFAULT_LOGO) : null,
      logo_data_url: fileToDataUrl(DEFAULT_LOGO),
      guidelines_path: relToRoot(DEFAULT_GUIDELINES),
    };
  } catch {
    return { ...DEFAULT_BRAND_PROFILE };
  }
}

export function getBrandProfile() {
  const db = getDb();
  const row = db.prepare('SELECT value FROM config WHERE key = ?').get(BRAND_CONFIG_KEY);
  if (!row) return defaultExtractedProfile();
  return { ...DEFAULT_BRAND_PROFILE, ...safeJson(row.value, {}) };
}

export function saveBrandProfile(profile) {
  const db = getDb();
  const next = { ...DEFAULT_BRAND_PROFILE, ...profile, updated_at: nowIso() };
  db.prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)').run(BRAND_CONFIG_KEY, JSON.stringify(next));
  return next;
}

export function updateBrandProfile(fields = {}) {
  return saveBrandProfile({ ...getBrandProfile(), ...fields });
}

/**
 * Seed the default madarX brand profile on first install (idempotent).
 *
 * Persists the brand into the `config` table under `brand_profile` so every
 * fresh install ships with the madarX brand template active by default. The
 * user (or an agent) can edit it later via PUT /api/brand, the /brand editor,
 * or the import_brand_guidelines tool — all of which write the same config row.
 *
 * Idempotent: only writes when no `brand_profile` row exists. If the user edits
 * the brand it stays edited; this never overwrites a user-customized profile.
 * Seeds the disk-extracted profile (logo data URL + colors from brand-assets/
 * brand-guidelines.html when present), falling back to DEFAULT_BRAND_PROFILE.
 */
export function seedBrandProfile(db = getDb()) {
  const existing = db.prepare('SELECT value FROM config WHERE key = ?').get(BRAND_CONFIG_KEY);
  if (existing) return false;
  const profile = defaultExtractedProfile();
  db.prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)')
    .run(BRAND_CONFIG_KEY, JSON.stringify({ ...profile, updated_at: nowIso() }));
  return true;
}

export function saveBrandUpload(file, kind = 'guidelines') {
  ensureDirs();
  const original = file?.name || `${kind}.txt`;
  const ext = path.extname(original).toLowerCase() || '.txt';
  const maxBytes = kind === 'logo'
    ? Number(process.env.MADARX_BRAND_LOGO_MAX_BYTES || 1_000_000)
    : Number(process.env.MADARX_BRAND_GUIDELINES_MAX_BYTES || 2_000_000);
  if (Number(file?.size || 0) > maxBytes) throw new Error('file too large');
  const safeName = `${kind}-${Date.now().toString(36)}${ext}`.replace(/[^a-z0-9_.-]/gi, '-');
  const abs = path.join(BRAND_DIR, safeName);
  return file.arrayBuffer().then(buffer => {
    if (buffer.byteLength > maxBytes) throw new Error('file too large');
    fs.writeFileSync(abs, Buffer.from(buffer));
    return { abs, rel: relToRoot(abs), name: original, ext };
  });
}

export async function importBrandGuidelines(file) {
  const saved = await saveBrandUpload(file, 'guidelines');
  const buffer = fs.readFileSync(saved.abs);
  const textLike = ['.html', '.htm', '.md', '.txt', '.css', '.svg'].includes(saved.ext);
  const extracted = textLike
    ? extractBrandProfileFromText(buffer.toString('utf8'), saved.name)
    : { ...getBrandProfile(), source_name: saved.name, extracted_at: nowIso() };
  return saveBrandProfile({
    ...getBrandProfile(),
    ...extracted,
    guidelines_path: saved.rel,
    source_name: saved.name,
    extracted_at: nowIso(),
  });
}

export function importBrandGuidelinesFromPath(absPath) {
  ensureDirs();
  const ext = path.extname(absPath).toLowerCase();
  const name = path.basename(absPath);
  const safeName = `guidelines-${Date.now().toString(36)}${ext || '.txt'}`.replace(/[^a-z0-9_.-]/gi, '-');
  const dest = path.join(BRAND_DIR, safeName);
  fs.copyFileSync(absPath, dest);
  const buffer = fs.readFileSync(dest);
  const textLike = ['.html', '.htm', '.md', '.txt', '.css', '.svg'].includes(ext);
  const extracted = textLike
    ? extractBrandProfileFromText(buffer.toString('utf8'), name)
    : { ...getBrandProfile(), source_name: name, extracted_at: nowIso() };
  return saveBrandProfile({
    ...getBrandProfile(),
    ...extracted,
    guidelines_path: relToRoot(dest),
    source_name: name,
    extracted_at: nowIso(),
  });
}

export function importBrandGuidelinesFromContent(content, filename = 'inline-brand-guidelines.md') {
  ensureDirs();
  const text = String(content || '');
  const maxBytes = Number(process.env.MADARX_BRAND_GUIDELINES_MAX_BYTES || 2_000_000);
  if (Buffer.byteLength(text, 'utf8') > maxBytes) throw new Error('brand guidelines content too large');
  const ext = path.extname(filename).toLowerCase() || '.md';
  const safeName = `guidelines-${Date.now().toString(36)}${ext}`.replace(/[^a-z0-9_.-]/gi, '-');
  const dest = path.join(BRAND_DIR, safeName);
  fs.writeFileSync(dest, text, 'utf8');
  const extracted = extractBrandProfileFromText(text, filename);
  return saveBrandProfile({
    ...getBrandProfile(),
    ...extracted,
    guidelines_path: relToRoot(dest),
    source_name: filename,
    extracted_at: nowIso(),
  });
}

export async function importBrandLogo(file) {
  const saved = await saveBrandUpload(file, 'logo');
  return updateBrandProfile({
    logo_path: saved.rel,
    logo_data_url: fileToDataUrl(saved.abs),
  });
}

export function importBrandLogoFromPath(absPath) {
  ensureDirs();
  const ext = path.extname(absPath).toLowerCase();
  const safeName = `logo-${Date.now().toString(36)}${ext || '.img'}`.replace(/[^a-z0-9_.-]/gi, '-');
  const dest = path.join(BRAND_DIR, safeName);
  fs.copyFileSync(absPath, dest);
  return updateBrandProfile({
    logo_path: relToRoot(dest),
    logo_data_url: fileToDataUrl(dest),
  });
}

export function brandInstructionText(profile = getBrandProfile()) {
  if (!profile?.active) return '';
  const colors = (profile.colors || []).map(c => `${c.name}: ${c.value}`).join(', ');
  const fonts = (profile.fonts || []).join(', ');
  const voice = (profile.voice || []).join(', ');
  const docs = (profile.document_types || []).join(', ');
  return `Brand system:
- Use ${profile.company_name || 'the configured brand'} as the default brand for documents, presentations, and UI artifacts.
- Colors: ${colors || 'use configured brand colors'}.
- Fonts: ${fonts || 'use configured brand fonts'}.
- Voice: ${voice || 'match the configured brand voice'}.
- Document types: ${docs || 'reports, briefs, proposals, presentations'}.
- Presentation style: ${profile.presentation_style || 'follow the configured brand guidelines'}.
- Do not invent off-brand palettes, logos, typography, or tone unless the owner explicitly asks for a different brand.`;
}
