import { Blogs } from '../models/Blogs';
import { CustomPage } from '../models/CustomPage';
import { ToolPage } from '../models/ToolPage';
import { Setting } from '../models/Setting';
import fs from 'fs';
import path from 'path';
import * as cheerio from 'cheerio';

const isProduction = process.env.NODE_ENV === 'production';
const baseDir = path.join(__dirname, '..', '..', '..');

const getTargetContentDirs = (): string[] => {
  const candidates: string[] = [];
  if (process.env.CONTENT_DIR) candidates.push(process.env.CONTENT_DIR);
  candidates.push(path.join(baseDir, 'project', 'public', 'content'));
  if (fs.existsSync(path.join(baseDir, 'project', 'dist', 'client'))) {
    candidates.push(path.join(baseDir, 'project', 'dist', 'client', 'content'));
  }
  if (fs.existsSync(path.join(baseDir, 'nytsolvers', 'dist', 'client'))) {
    candidates.push(path.join(baseDir, 'nytsolvers', 'dist', 'client', 'content'));
  }
  if (fs.existsSync('/var/www/nytsolvers/dist/client')) {
    candidates.push('/var/www/nytsolvers/dist/client/content');
  }
  if (fs.existsSync('/var/www/project/dist/client')) {
    candidates.push('/var/www/project/dist/client/content');
  }
  return Array.from(new Set(candidates.map((p) => path.resolve(p))));
};

const contentDir = process.env.CONTENT_DIR || (isProduction
  ? (fs.existsSync(path.join(baseDir, 'nytsolvers', 'dist', 'client'))
      ? path.join(baseDir, 'nytsolvers', 'dist', 'client', 'content')
      : path.join(baseDir, 'project', 'dist', 'client', 'content'))
  : path.join(baseDir, 'project', 'public', 'content')
);





const blogsDir = path.join(contentDir, 'blogs');
const pagesDir = path.join(contentDir, 'pages');
const toolsDir = path.join(contentDir, 'tools');

const slugify = (text: string) => {
  if (!text) return '';
  return text
    .toString()
    .toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/[^\w\-]+/g, '')
    .replace(/\-\-+/g, '-')
    .replace(/^-+/, '')
    .replace(/-+$/, '');
};


const STATIC_LEGAL_SLUGS = [
  "about-us",
  "contact-us",
  "privacy-policy",
  "terms-conditions",
  "disclaimer",
];

const DAILY_HINT_SLUGS = [
  "daily-answers",
  "wordle-hints",
  "connections-hints",
  "spelling-bee-hints",
  "spelling-bee-answers",
  "letter-boxed-hints",
  "strands-hints",
  "nyt-strands-hints",
  "crossword-hints",
  "crossword-answers",
  "mini-crossword-hints",
  "connections-sports-hints",
  "pips-hints",
  "betweenle-hints",
  "conexo-hints",
  "blossom-hints",
  "quordle-hints",
  "la-times-mini-hints",
  "la-times-crossword-hints",
  "word-salad-hints",
  "word-hurdle-hints",
  "antiwordle-hints",
  "contexto-hints",
  "phrazle-hints",
  "octordle-hints",
  "sedecordle-hints",
];

const HIGH_PRIORITY_HINTS = new Set([
  "connections-hints",
  "mini-crossword-hints",
  "blossom-hints",
]);

function getTodayET(): string {
  try {
    return new Intl.DateTimeFormat("en-CA", {
      timeZone: "America/New_York",
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
    }).format(new Date());
  } catch (e) {
    return new Date().toISOString().split("T")[0];
  }
}

function formatIsoDate(dateVal?: any): string {
  if (!dateVal) return `${getTodayET()}T00:00:00+00:00`;
  try {
    const ymd = new Date(dateVal).toISOString().split("T")[0];
    return `${ymd}T00:00:00+00:00`;
  } catch {
    return `${getTodayET()}T00:00:00+00:00`;
  }
}

export function generateSitemapXmlString(
  blogs: any[],
  toolPages: any[],
  customPages: any[],
  manualUrls: string[] = []
): string {
  const todayET = getTodayET();
  const todayIso = `${todayET}T00:00:00+00:00`;

  const homepageEntry = `  <url>
    <loc>https://nytsolvers.com/</loc>
    <lastmod>${todayIso}</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>`;

  const staticLegalEntries: string[] = [];
  const dailyHintEntries: string[] = [];
  const solverEntries: string[] = [];
  const blogEntries: string[] = [];

  // Map custom pages
  const customMap = new Map<string, any>();
  for (const page of customPages) {
    if (!page.page_slug || page.noindex) continue;
    customMap.set(page.page_slug.trim().replace(/^\/+/, ""), page);
  }

  // Map tool pages
  const toolMap = new Map<string, any>();
  for (const tool of toolPages) {
    if (!tool.page_slug || tool.page_slug === "index" || tool.noindex) continue;
    toolMap.set(tool.page_slug.trim().replace(/^\/+/, ""), tool);
  }

  // 1. Static Legal Pages (exact order requested)
  for (const slug of STATIC_LEGAL_SLUGS) {
    const page = customMap.get(slug) || toolMap.get(slug);
    const lastmod = formatIsoDate(page?.updated_at || page?.created_at || "2026-09-17");
    staticLegalEntries.push(`  <url>
    <loc>https://nytsolvers.com/${slug}</loc>
    <lastmod>${lastmod}</lastmod>
    <priority>0.5</priority>
  </url>`);
    customMap.delete(slug);
    toolMap.delete(slug);
  }

  // 2. Daily Hint Pages (ordered by DAILY_HINT_SLUGS)
  for (const slug of DAILY_HINT_SLUGS) {
    const tool = toolMap.get(slug);
    if (tool) {
      const priority = HIGH_PRIORITY_HINTS.has(slug) ? "0.9" : "0.8";
      dailyHintEntries.push(`  <url>
    <loc>https://nytsolvers.com/${slug}</loc>
    <lastmod>${todayIso}</lastmod>
    <changefreq>daily</changefreq>
    <priority>${priority}</priority>
  </url>`);
      toolMap.delete(slug);
    }
  }

  // 3. Solver Tools (Core solvers first, then rest)
  for (const slug of ["wordle-solver", "spelling-bee-solver", "letter-boxed-solver", "scrabble-word-finder"]) {
    const tool = toolMap.get(slug);
    if (tool) {
      solverEntries.push(`  <url>
    <loc>https://nytsolvers.com/${slug}</loc>
    <lastmod>${todayIso}</lastmod>
    <changefreq>daily</changefreq>
    <priority>0.9</priority>
  </url>`);
      toolMap.delete(slug);
    }
  }

  // Rest of tools
  for (const [slug, tool] of toolMap.entries()) {
    const isDaily = DAILY_HINT_SLUGS.includes(slug);
    const lastmod = isDaily ? todayIso : formatIsoDate(tool.updated_at || tool.created_at);
    const changefreq = isDaily ? "daily" : "weekly";
    const priority = "0.8";

    solverEntries.push(`  <url>
    <loc>https://nytsolvers.com/${slug}</loc>
    <lastmod>${lastmod}</lastmod>
    <changefreq>${changefreq}</changefreq>
    <priority>${priority}</priority>
  </url>`);
  }

  // Rest of custom pages (if any)
  for (const [slug, page] of customMap.entries()) {
    const lastmod = formatIsoDate(page.updated_at || page.created_at);
    solverEntries.push(`  <url>
    <loc>https://nytsolvers.com/${slug}</loc>
    <lastmod>${lastmod}</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.5</priority>
  </url>`);
  }

  // Manual URLs from settings
  for (const rawUrl of manualUrls) {
    if (!rawUrl) continue;
    const clean = rawUrl.replace(/^\/+/, "");
    solverEntries.push(`  <url>
    <loc>${clean.startsWith("http") ? clean : `https://nytsolvers.com/${clean}`}</loc>
    <lastmod>${todayIso}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>`);
  }

  // 4. Blogs
  for (const blog of blogs) {
    if (!blog.slug || blog.noindex) continue;
    const cleanSlug = blog.slug.trim().replace(/^\/+/, "");
    const lastmod = formatIsoDate(blog.updated_at || blog.created_at);
    blogEntries.push(`  <url>
    <loc>https://nytsolvers.com/blog/${cleanSlug}</loc>
    <lastmod>${lastmod}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.7</priority>
  </url>`);
  }

  const blogIndexEntry = `  <url>
    <loc>https://nytsolvers.com/blog</loc>
    <lastmod>${todayIso}</lastmod>
    <changefreq>daily</changefreq>
    <priority>0.8</priority>
  </url>`;

  const allEntries = [
    homepageEntry,
    ...staticLegalEntries,
    ...dailyHintEntries,
    ...solverEntries,
    blogIndexEntry,
    ...blogEntries,
  ];

  return `<?xml version='1.0' encoding='UTF-8'?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${allEntries.join("\n")}
</urlset>
`;
}

export async function generateContent() {
  try {
    // Ensure directories exist
    [contentDir, blogsDir, pagesDir, toolsDir].forEach(dir => {
      if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
    });

    const manifest: { slug: string; path: string; type: string }[] = [];

    // -----------------------------
    // BLOGS
    // -----------------------------
    const blogs = await Blogs.query().where('is_active', true).withGraphFetched('relatedBlogs');
    const blogsManifest: any[] = [];

    for (const blog of blogs) {
      const cleanSlug = (blog.slug || '').trim().replace(/^\/+|\/+$/g, '');
      if (!cleanSlug) continue;

      // Auto-heal database and disk if blog slug had trailing or leading spaces
      if (blog.slug !== cleanSlug) {
        try {
          await Blogs.query().patch({ slug: cleanSlug }).where('id', blog.id);
          console.log(`[Auto-Heal] Fixed blog #${blog.id} slug: "${blog.slug}" -> "${cleanSlug}"`);
        } catch (e: any) {
          console.warn(`[Auto-Heal] Could not patch blog #${blog.id}:`, e.message);
        }
        try {
          const oldBadDir = path.join(blogsDir, blog.slug);
          if (fs.existsSync(oldBadDir) && oldBadDir !== path.join(blogsDir, cleanSlug)) {
            fs.rmSync(oldBadDir, { recursive: true, force: true });
          }
        } catch (e: any) {}
        blog.slug = cleanSlug;
      }

      let relatedBlogsData: any[] = [];
      if (blog.relatedBlogs && blog.relatedBlogs.length > 0) {
        relatedBlogsData = blog.relatedBlogs
          .filter((rb: any) => rb && rb.slug && (rb.is_active === undefined || Boolean(rb.is_active)))
          .map((rb: any) => ({
            title: rb.title,
            description: rb.description,
            slug: (rb.slug || '').trim().replace(/^\/+|\/+$/g, ''),
            featured_image: rb.featured_image,
            created_at: rb.created_at,
          }));
      }

      let relatedToolsData: string[] = [];
      if (blog.related_tools) {
        if (typeof blog.related_tools === 'string') {
          try {
            relatedToolsData = JSON.parse(blog.related_tools);
          } catch {
            relatedToolsData = blog.related_tools.split(',').map((s: string) => s.trim()).filter(Boolean);
          }
        } else if (Array.isArray(blog.related_tools)) {
          relatedToolsData = blog.related_tools;
        }
      }

      const blogData: any = {
        ...blog,
        slug: cleanSlug,
        related_blogs: relatedBlogsData,
        related_tools: relatedToolsData,
      };

      // Add to blogs manifest for the blogs listing page
      blogsManifest.push({
        title: blog.title,
        description: blog.description,
        slug: cleanSlug,
        featured_image: blog.featured_image,
        created_at: blog.created_at,
      });

      // Build TOC
      if (blogData.content) {
        const $ = cheerio.load(blogData.content);
        const toc: { id: string; text: string; level: string }[] = [];

        $('h2, h3, h4, h5').each((_, el) => {
          const text = $(el).text();
          if (!text) return;

          let id = slugify(text);
          let newId = id;
          let n = 1;

          while ($(`[id="${newId}"]`).length > 0 && $(`[id="${newId}"]`).get(0) !== el) {
            newId = `${id}-${n}`;
            n++;
          }

          $(el).attr('id', newId);
          toc.push({
            id: newId,
            text,
            level: el.tagName.toLowerCase()
          });
        });

        blogData.content = $.html();
        blogData.toc = toc;
      }

      const slugParts = cleanSlug.split('/');
      const fileName = 'page.json';
      const dirPath = path.join(blogsDir, ...slugParts);
      console.log('DEBUG blogs: dirPath=', dirPath);
      fs.mkdirSync(dirPath, { recursive: true });
      const writePath = path.join(dirPath, fileName);
      console.log('DEBUG blogs write:', writePath);
      fs.writeFileSync(writePath, JSON.stringify(blogData, null, 2));

      manifest.push({
        slug: cleanSlug,
        path: `blogs/${slugParts.join('/')}/${fileName}`,
        type: 'blog'
      });
    }

    // Write blogs.json
    fs.writeFileSync(
      path.join(contentDir, '..', "blogs.json"),
      JSON.stringify(blogsManifest, null, 2)
    );


    // -----------------------------
    // PAGES
    // -----------------------------
    const customPages = await CustomPage.query().where('is_active', true);

    for (const page of customPages) {
      const cleanSlug = (page.page_slug || '').trim().replace(/^\/+|\/+$/g, '');
      if (!cleanSlug) continue;

      if (page.page_slug !== cleanSlug) {
        try {
          await CustomPage.query().patch({ page_slug: cleanSlug }).where('id', page.id);
          console.log(`[Auto-Heal] Fixed page #${page.id} slug: "${page.page_slug}" -> "${cleanSlug}"`);
        } catch (e: any) {}
        page.page_slug = cleanSlug;
      }

      const slugParts = cleanSlug.split('/');
      const fileName = 'page.json';
      const dirPath = path.join(pagesDir, ...slugParts);
      console.log('DEBUG pages: dirPath=', dirPath);
      fs.mkdirSync(dirPath, { recursive: true });
      fs.writeFileSync(path.join(dirPath, fileName), JSON.stringify({ ...page, page_slug: cleanSlug }, null, 2));

      manifest.push({
        slug: cleanSlug,
        path: `pages/${slugParts.join('/')}/${fileName}`,
        type: 'page'
      });
    }

    // -----------------------------
    // TOOLS
    // -----------------------------
    // -----------------------------
// TOOLS (support nested slugs fully)
// -----------------------------
const toolPages = await ToolPage.query().where('is_active', true);

for (const tool of toolPages) {
  const cleanSlug = (tool.page_slug || '').trim().replace(/^\/+|\/+$/g, '');
  if (!cleanSlug) continue;

  if (tool.page_slug !== cleanSlug) {
    try {
      await ToolPage.query().patch({ page_slug: cleanSlug }).where('id', tool.id);
      console.log(`[Auto-Heal] Fixed tool #${tool.id} slug: "${tool.page_slug}" -> "${cleanSlug}"`);
    } catch (e: any) {}
    tool.page_slug = cleanSlug;
  }

  const slugParts = cleanSlug.split('/');
  const fileName = 'page.json';
  const dirPath = path.join(toolsDir, ...slugParts);
  console.log('DEBUG tools: dirPath=', dirPath);
  fs.mkdirSync(dirPath, { recursive: true });
  fs.writeFileSync(path.join(dirPath, fileName), JSON.stringify({ ...tool, page_slug: cleanSlug }, null, 2));

  manifest.push({
    slug: cleanSlug,
    path: `tools/${slugParts.join('/')}/${fileName}`,
    type: 'tool'
  });
}


    // -----------------------------
    // MANIFEST WRITE
    // -----------------------------
    fs.writeFileSync(
      path.join(contentDir, "page-list.json"),
      JSON.stringify(manifest, null, 2)
    );

    console.log("Content generated and page-list.json created with paths!");

    // Sync generated content & blogs.json to all active client/public content targets
    try {
      const resolvedPrimary = path.resolve(contentDir);
      const blogsJsonSrc = path.resolve(contentDir, '..', 'blogs.json');
      const targetDirs = getTargetContentDirs().filter((dir) => dir !== resolvedPrimary);

      for (const targetDir of targetDirs) {
        try {
          fs.mkdirSync(targetDir, { recursive: true });
          fs.cpSync(resolvedPrimary, targetDir, { recursive: true, force: true });
          if (fs.existsSync(blogsJsonSrc)) {
            const targetBlogsJson = path.resolve(targetDir, '..', 'blogs.json');
            fs.copyFileSync(blogsJsonSrc, targetBlogsJson);
          }
          console.log(`[ContentSync] Synced content to ${targetDir}`);
        } catch (syncErr: any) {
          console.warn(`[ContentSync] Failed syncing to ${targetDir}:`, syncErr?.message || syncErr);
        }
      }
    } catch (syncOuterErr: any) {
      console.warn('[ContentSync] Sync loop error:', syncOuterErr?.message || syncOuterErr);
    }

    // -----------------------------
    // SITEMAP SYNC
    // -----------------------------
    try {
      
    let manualUrls: string[] = [];
    try {
      const setting = await Setting.query().first();
      let manualRaw = setting?.manual_sitemap_urls || "";
      if (!manualRaw && setting?.sites_settings) {
        try {
          const parsed = typeof setting.sites_settings === 'string' ? JSON.parse(setting.sites_settings) : setting.sites_settings;
          manualRaw = parsed?.manual_sitemap_urls || "";
        } catch (e) {}
      }
      if (manualRaw) {
        manualUrls = manualRaw.split('\n').map((u: string) => u.trim()).filter((u: string) => u !== '');
      }
    } catch (e) {}

    const sitemapXml = generateSitemapXmlString(blogs, toolPages, customPages, manualUrls);

      const targetSitemapPaths = [
        path.join(baseDir, 'project', 'public', 'sitemap.xml'),
        path.join(baseDir, 'project', 'dist', 'client', 'sitemap.xml'),
        path.join(baseDir, 'nytsolvers', 'dist', 'client', 'sitemap.xml'),
        '/var/www/nytsolvers/dist/client/sitemap.xml',
      ];
      for (const sPath of targetSitemapPaths) {
        try {
          if (fs.existsSync(path.dirname(sPath))) {
            fs.writeFileSync(sPath, sitemapXml, 'utf-8');
            console.log(`[Sitemap] Synced sitemap to ${sPath}`);
          }
        } catch (e) {}
      }
    } catch (e) {
      console.error("Error syncing sitemap during generateContent:", e);
    }

  } catch (error) {
    console.error("Error generating content:", error);
  }
}
