import fs from "node:fs";
import path from "node:path";

// Load environment variables from .env.local
const envPath = path.join(process.cwd(), ".env.local");
if (fs.existsSync(envPath)) {
  const content = fs.readFileSync(envPath, "utf-8");
  for (const line of content.split("\n")) {
    const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
    if (!m) continue;
    let v = m[2].trim();
    if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
      v = v.slice(1, -1);
    }
    if (!(m[1] in process.env)) process.env[m[1]] = v;
  }
}

async function runE2ETest() {
  console.log("============================================================");
  console.log("QUTO AI CMS — FULL FORMATTING, MEDIA, SEO & E2E TEST SUITE");
  console.log("============================================================");

  const { db } = await import("../lib/admin/db");
  const { blogService, validatePostPreflight } = await import("../lib/blog-service");
  const { calculateReadingStats } = await import("../lib/reading-time");
  const { safeCleanupUnreferencedMedia } = await import("../lib/media-storage");

  // 1. Resolve an Admin user as author
  console.log("\n[PHASE 1] Resolving Admin/Sub-Admin User for Author Attribution...");
  const adminUsers = await db.getUsers();
  if (adminUsers.length === 0) throw new Error("No admin users found in MongoDB!");
  const testAdmin = adminUsers[0];
  console.log(`[PASS] Using Admin: "${testAdmin.name}" (${testAdmin.email}, Role: ${testAdmin.role})`);

  // 2. Resolve or create a Category from MongoDB
  console.log("\n[PHASE 2] Resolving Category from MongoDB Module...");
  let categories = await db.getCategories();
  let testCat = categories.find((c) => c.slug === "voice-ai-guides") || categories[0];
  if (!testCat) {
    testCat = await db.createCategory({
      name: "Voice AI Guides",
      slug: "voice-ai-guides",
      description: "Technical architectures for enterprise voice AI",
    });
  }
  console.log(`[PASS] Using Category: "${testCat.name}" (Slug: ${testCat.slug})`);

  // 3. Create persistent test media assets in storage and MongoDB
  console.log("\n[PHASE 3] Creating Real Stored Media Assets in Media Library...");
  const now = new Date();
  const year = String(now.getFullYear());
  const month = String(now.getMonth() + 1).padStart(2, "0");
  const uploadsDir = path.join(process.cwd(), "public", "uploads", "blog", year, month);
  fs.mkdirSync(uploadsDir, { recursive: true });

  // Create 1x1 transparent PNG dummy buffer with authentic magic bytes
  const dummyPng = Buffer.from([
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
    0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
    0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00,
    0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x00, 0x01, 0x00, 0x00,
    0x05, 0x00, 0x01, 0x0d, 0x0a, 0x2d, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x49,
    0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
  ]);

  async function createTestMedia(name: string, alt: string) {
    const filename = `qa-${name}-${Date.now()}.png`;
    const filePath = path.join(uploadsDir, filename);
    fs.writeFileSync(filePath, dummyPng);
    const mediaItem = await db.createMedia({
      id: crypto.randomUUID(),
      filename,
      originalName: `${name}.png`,
      url: `/uploads/blog/${year}/${month}/${filename}`,
      mimeType: "image/png",
      size: dummyPng.length,
      alt,
      caption: `Caption for ${name}`,
      usedInPosts: [],
      uploadedBy: testAdmin.email,
      uploadedAt: now.toISOString(),
    });
    return mediaItem;
  }

  const featuredMedia = await createTestMedia("featured", "Quto AI Architecture Hero Banner");
  const inlineMedia1 = await createTestMedia("inline-top", "Pipeline Diagram Top");
  const inlineMedia2 = await createTestMedia("inline-middle", "Latency Benchmarks Middle");
  const inlineMedia3 = await createTestMedia("inline-bottom", "Compliance Matrix Bottom");
  const replacementMedia = await createTestMedia("inline-replaced", "New Updated Pipeline Diagram");
  console.log(`[PASS] Created 5 Media Library Assets (IDs: ${featuredMedia.id.slice(0, 8)}..., ${inlineMedia1.id.slice(0, 8)}...)`);

  // 4. Build comprehensive rich text body testing ALL supported editor formats (NO H1!)
  console.log("\n[PHASE 4] Building Comprehensive Rich Text Body Content...");
  const richTextBodyHtml = `
    <h2>1. Executive Architecture Overview</h2>
    <p>Modern enterprise voice AI requires sub-500ms audio turnaround to maintain natural conversational pacing. This benchmark report analyzes pipeline bottlenecks.</p>
    <img src="${inlineMedia1.url}" alt="${inlineMedia1.alt}" />

    <h3>1.1 Critical Latency Milestones</h3>
    <p>Speech processing involves four synchronous stages: <strong>automatic speech recognition (ASR)</strong>, <em>large language model inference</em>, <u>neural text-to-speech synthesis (TTS)</u>, and <del>legacy telephony transcoding</del>.</p>
    <p>Key latency formula: <code>T_total = T_asr + T_ttft + T_tts</code>.</p>

    <blockquote>Enterprise SLA requires P95 voice turn-completion below 480ms under carrier SIP trunking.</blockquote>

    <hr />

    <h2>2. Supported Engineering Formats & Benchmarks</h2>
    <p>The following parameters have been verified across production clusters:</p>
    <ul>
      <li>WebRTC Direct Media Stream with jitter buffer stabilization</li>
      <li>Speculative token pre-buffering for immediate neural vocalization</li>
      <li>Zero-copy audio packet routing on dedicated carrier trunks</li>
    </ul>

    <ol>
      <li>Initialize SIP trunk connection with TLS encryption</li>
      <li>Establish bidirectional RTP audio stream</li>
      <li>Pipe audio frames directly to high-throughput streaming ASR</li>
    </ol>

    <img src="${inlineMedia2.url}" alt="${inlineMedia2.alt}" />

    <h3>2.1 Low-Latency Streaming Code Implementation</h3>
    <pre><code>async function streamAudioBuffer(chunk: ArrayBuffer): Promise&lt;void&gt; {
  const socket = await getVoicePipelineSocket();
  socket.send(chunk);
}</code></pre>

    <p>For more architectural details, refer to our <a href="/about">About Us page</a> and the <a href="https://example.com/voice-specs" target="_blank" rel="noopener noreferrer">official specifications</a>.</p>

    <hr />

    <h2>3. Compliance & Security Standards</h2>
    <p>Voice data is processed strictly in accordance with SOC 2 Type II and HIPAA regulatory boundaries.</p>
    <img src="${inlineMedia3.url}" alt="${inlineMedia3.alt}" />
  `;

  // Verify that NO <h1> exists in body
  if (/<h1[\s>]/i.test(richTextBodyHtml)) {
    throw new Error("FAIL: Body content must NOT contain H1 tags! H1 is reserved exclusively for the article title.");
  }
  console.log("[PASS] Verified body content strictly adheres to heading hierarchy: H2, H3, H4 (NO H1 present).");

  // 5. Test Mandatory SEO Preflight Blocking
  console.log("\n[PHASE 5] Testing Mandatory SEO Preflight Validation Blocking...");
  const dummyTitle = `Quto AI Editor QA — Full Formatting Test ${Date.now()}`;
  const preflightMissingSeoTitle = validatePostPreflight({
    title: dummyTitle,
    slug: "quto-ai-editor-qa-formatting-test",
    contentHtml: richTextBodyHtml,
    metaDescription: "A comprehensive test of enterprise voice AI pipelines.",
    seoTitle: "", // Missing
  });
  if (preflightMissingSeoTitle.valid) throw new Error("FAIL: Preflight must block publication when SEO title is empty!");
  console.log("[PASS] Correctly blocked publication when SEO title is empty:", preflightMissingSeoTitle.errors[0]);

  const preflightMissingMetaDesc = validatePostPreflight({
    title: dummyTitle,
    slug: "quto-ai-editor-qa-formatting-test",
    contentHtml: richTextBodyHtml,
    seoTitle: "Quto AI Editor QA — Comprehensive Latency Analysis",
    metaDescription: "", // Missing
  });
  if (preflightMissingMetaDesc.valid) throw new Error("FAIL: Preflight must block publication when Meta description is empty!");
  console.log("[PASS] Correctly blocked publication when Meta description is empty:", preflightMissingMetaDesc.errors[0]);

  // 6. Create Draft Article in MongoDB
  console.log("\n[PHASE 6] Creating Draft Article in MongoDB...");
  const createdPost = await blogService.createDraft(
    {
      title: dummyTitle,
      category: testCat.name,
      authorId: testAdmin.id,
    },
    testAdmin
  );
  console.log(`[PASS] Draft created with ID: ${createdPost.id}, initial slug: ${createdPost.slug}`);

  // 7. Update Draft with all rich content, featured image, SEO, and social metadata
  console.log("\n[PHASE 7] Updating Draft with Full Content, Featured Image & SEO Metadata...");
  const initialSeoTitle = "Quto AI Editor QA — Sub-500ms Latency Report";
  const initialMetaDesc = "A thorough benchmark analysis of enterprise sub-500ms voice AI pipeline turnaround and telephony compliance.";

  const updatedDraft = await blogService.updateDraft(
    createdPost.id,
    {
      contentHtml: richTextBodyHtml,
      excerpt: "Deep technical benchmark and formatting test of enterprise sub-500ms voice AI pipelines.",
      featuredImage: featuredMedia.url,
      featuredImageId: featuredMedia.id,
      featuredImageAlt: featuredMedia.alt,
      seoTitle: initialSeoTitle,
      metaDescription: initialMetaDesc,
      ogTitle: "Quto AI Voice Architecture Report",
      ogDescription: "Enterprise voice AI pipeline analysis and benchmarks.",
      ogImage: featuredMedia.url,
      ogImageId: featuredMedia.id,
      twitterTitle: "Quto AI Latency Guide",
      twitterDescription: "Technical voice AI pipeline turnaround benchmarks.",
      twitterImage: featuredMedia.url,
      twitterImageId: featuredMedia.id,
    },
    testAdmin
  );

  if (!updatedDraft.featuredImage || updatedDraft.featuredImage !== featuredMedia.url) {
    throw new Error("FAIL: Featured image failed to persist to draft post!");
  }
  if (!updatedDraft.tableOfContents || updatedDraft.tableOfContents.length === 0) {
    throw new Error("FAIL: Table of contents was not automatically generated from headings!");
  }
  console.log(`[PASS] Featured Image persisted: ${updatedDraft.featuredImage}`);
  console.log(`[PASS] Auto-generated ${updatedDraft.tableOfContents.length} TOC entries:`, updatedDraft.tableOfContents.map((t) => t.title));
  console.log(`[PASS] Reading Stats computed: ${updatedDraft.readTime} (${updatedDraft.wordCount} words)`);

  // 8. Publish Article (Making it Live on the Public Site)
  console.log("\n[PHASE 8] Publishing Article to Public Website...");
  const publishedPost = await blogService.publish(createdPost.id, testAdmin);
  if (publishedPost.status !== "PUBLISHED") throw new Error("FAIL: Article status did not transition to PUBLISHED!");
  console.log(`[PASS] Article published! Status: ${publishedPost.status}, Version: ${publishedPost.version}`);

  // Verify public retrieval by slug
  const publicQuery1 = await blogService.getPostBySlug(publishedPost.slug);
  if (!publicQuery1) throw new Error("FAIL: Published article not found via getPostBySlug!");
  if (publicQuery1.featuredImage !== featuredMedia.url) {
    throw new Error(`FAIL: Public post featuredImage mismatch: got ${publicQuery1.featuredImage}`);
  }
  console.log(`[PASS] Public article resolved from MongoDB with canonical featuredImage: ${publicQuery1.featuredImage}`);

  // 9. Modify Published Post — Working Draft vs Live Isolation Test
  console.log("\n[PHASE 9] Modifying Published Article — Testing Working Draft Isolation...");
  const workingTitle = `${dummyTitle} [Updated Working Title]`;
  const workingSeoTitle = "Updated Working SEO Title";
  const workingMetaDesc = "Updated Working Meta Description for Preview Only";

  // In the working draft, replace inline image 1 with replacementMedia and remove inline image 3
  const workingBodyHtml = richTextBodyHtml
    .replace(inlineMedia1.url, replacementMedia.url)
    .replace(`<img src="${inlineMedia3.url}" alt="${inlineMedia3.alt}" />`, "<!-- inline image 3 removed -->");

  const workingDraftResult = await blogService.updateDraft(
    publishedPost.id,
    {
      title: workingTitle,
      seoTitle: workingSeoTitle,
      metaDescription: workingMetaDesc,
      contentHtml: workingBodyHtml,
    },
    testAdmin
  );

  if (!workingDraftResult.hasWorkingDraft) throw new Error("FAIL: hasWorkingDraft should be true!");

  // Verify Public site remains 100% UNTOUCHED
  const publicQuery2 = await blogService.getPostBySlug(publishedPost.slug);
  if (!publicQuery2) throw new Error("FAIL: Public query failed during working draft editing!");
  if (publicQuery2.title !== dummyTitle) {
    throw new Error("FAIL: Public live article title was mutated before Update Live!");
  }
  if (publicQuery2.contentHtml?.includes(replacementMedia.url)) {
    throw new Error("FAIL: Public live article content was mutated with replacement image before Update Live!");
  }
  console.log("[PASS] Public site remains 100% UNTOUCHED: shows original title and original media!");

  // Verify Preview endpoint displays working draft updates
  const previewPost = await blogService.getPostForPreview(publishedPost.id);
  if (!previewPost) throw new Error("FAIL: Preview endpoint returned null!");
  if (previewPost.title !== workingTitle) {
    throw new Error(`FAIL: Preview does not show working title: got "${previewPost.title}"`);
  }
  if (!previewPost.contentHtml?.includes(replacementMedia.url)) {
    throw new Error("FAIL: Preview does not show replacement image!");
  }
  console.log("[PASS] Preview endpoint resolves working draft perfectly with new replacement image!");

  // 10. Admin Clicks "Update Live"
  console.log("\n[PHASE 10] Admin Clicks 'Update Live' (Committing Working Draft to Public)...");
  const liveCommitted = await blogService.updateLive(publishedPost.id, testAdmin);
  if (liveCommitted.hasWorkingDraft) throw new Error("FAIL: hasWorkingDraft should be false after Update Live!");
  if (liveCommitted.version <= publishedPost.version) throw new Error("FAIL: Version number was not incremented!");
  console.log(`[PASS] Update Live completed! Version incremented to: ${liveCommitted.version}`);

  // Verify public article now reflects all committed changes
  const publicQuery3 = await blogService.getPostBySlug(liveCommitted.slug);
  if (!publicQuery3) throw new Error("FAIL: Public query failed after Update Live!");
  if (publicQuery3.title !== workingTitle) {
    throw new Error(`FAIL: Public article does not reflect updated title: got "${publicQuery3.title}"`);
  }
  if (!publicQuery3.contentHtml?.includes(replacementMedia.url)) {
    throw new Error("FAIL: Public article does not reflect replacement image after Update Live!");
  }
  console.log("[PASS] Public site now reflects updated title, replacement image, and new version!");

  // 11. Safe Media Cleanup Test
  console.log("\n[PHASE 11] Testing Safe Media Reference Cleanup...");
  // inlineMedia3 was removed from the article. Let's verify safe cleanup cleans it ONLY if unreferenced
  const cleanupRes = await safeCleanupUnreferencedMedia(inlineMedia3.id);
  console.log(`[PASS] safeCleanupUnreferencedMedia result for removed image: cleaned=${cleanupRes.cleaned}`);

  // Verify that an in-use media (featuredMedia) is NOT deleted if cleanup is attempted
  const inUseCleanup = await safeCleanupUnreferencedMedia(featuredMedia.id);
  if (inUseCleanup.cleaned) throw new Error("FAIL: Active in-use media asset was deleted!");
  console.log(`[PASS] Active media preserved safely: inUse=${inUseCleanup.inUse}, references=${inUseCleanup.references?.length}`);

  // 12. Teardown Test Post and Clean Up Test Files
  console.log("\n[PHASE 12] Cleaning up QA Test Post and Temporary Media...");
  await db.deletePost(publishedPost.id, true);
  await safeCleanupUnreferencedMedia(featuredMedia.id);
  await safeCleanupUnreferencedMedia(inlineMedia1.id);
  await safeCleanupUnreferencedMedia(inlineMedia2.id);
  await safeCleanupUnreferencedMedia(replacementMedia.id);
  console.log("[PASS] Cleaned up test post and temporary test media from MongoDB and disk.");

  console.log("\n============================================================");
  console.log("ALL 12 E2E PHASES PASSED WITH 100% SUCCESS!");
  console.log("============================================================");
}

runE2ETest()
  .then(() => process.exit(0))
  .catch((err) => {
    console.error("\n[TEST FAILED]:", err);
    process.exit(1);
  });
