/**
 * ImportExecutionEngine End-to-End Tests
 *
 * Comprehensive tests that parse a real WXR file and verify the complete import process.
 * Uses tests/assets/import-test-cases.wxr as the source test data.
 *
 * Test Categories:
 * 1. HTML to Markdown Conversion - verifies proper transformation of all HTML elements
 * 2. Shortcode/Macro Conversion - verifies [shortcode] → [[shortcode]] transformation
 * 3. Tag/Category Mapping - verifies taxonomy resolution and creation
 * 4. Conflict Resolution - verifies ignore/overwrite/import behaviors
 * 5. Media Import - verifies media file handling with post linkage
 * 6. Page Import - verifies pages become posts with "page" category
 * 7. Other Post Types - verifies nav_menu_item, revision, wp_template are analyzed but not imported
 */

import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import * as path from 'path';
import * as fs from 'fs';
import { WxrParser, type WxrData } from '../../src/main/engine/WxrParser';
import type {
  ImportAnalysisReport,
  AnalyzedPost,
  AnalyzedMedia,
  AnalyzedCategory,
  AnalyzedTag,
  PostAnalysisStatus,
  MediaAnalysisStatus,
} from '../../src/main/engine/ImportAnalysisEngine';
import type { WxrPost, WxrMedia } from '../../src/main/engine/WxrParser';

// Read the WXR file SYNCHRONOUSLY at module load time (before mocks apply)
const wxrFilePath = path.join(__dirname, '../assets/import-test-cases.wxr');
const wxrFileContent = fs.readFileSync(wxrFilePath, 'utf-8');

// Track all database inserts
const insertedPosts: Array<{
  id: string;
  projectId: string;
  title: string;
  slug: string;
  content: string | null;
  status: string;
  tags: string;
  categories: string;
  createdAt: Date;
  updatedAt: Date;
  publishedAt?: Date;
  author?: string;
}> = [];

// Track all database updates
const updatedPosts: Array<{
  id: string;
  data: any;
}> = [];

const insertedMedia: Array<{
  id: string;
  linkedPostIds: string[];
  title?: string;
}> = [];

const createdTags: string[] = [];

// Track files written
const writtenFiles: Array<{
  path: string;
  content: string;
}> = [];

// Mock database that tracks inserts and updates
const mockDb = {
  insert: vi.fn().mockImplementation((table: any) => ({
    values: vi.fn().mockImplementation(async (data: any) => {
      // Track based on data structure
      if (data && typeof data === 'object') {
        if ('slug' in data && 'title' in data) {
          insertedPosts.push(data);
        }
      }
      return data;
    }),
  })),
  update: vi.fn().mockImplementation((table: any) => ({
    set: vi.fn().mockImplementation((data: any) => ({
      where: vi.fn().mockImplementation(async () => {
        // Track updates
        updatedPosts.push({ id: 'updated-id', data });
        return data;
      }),
    })),
  })),
  select: vi.fn().mockReturnValue({
    from: vi.fn().mockReturnValue({
      where: vi.fn().mockResolvedValue([]),
    }),
  }),
};

const mockClient = {
  execute: vi.fn().mockResolvedValue({ rows: [] }),
};

// Mock modules
vi.mock('../../src/main/database', () => ({
  getDatabase: vi.fn(() => ({
    getLocal: vi.fn(() => mockDb),
    getLocalClient: vi.fn(() => mockClient),
  })),
}));

vi.mock('fs/promises', () => ({
  mkdir: vi.fn().mockResolvedValue(undefined),
  writeFile: vi.fn().mockImplementation(async (filePath: string, content: string) => {
    writtenFiles.push({ path: filePath, content });
  }),
  copyFile: vi.fn().mockResolvedValue(undefined),
  access: vi.fn().mockResolvedValue(undefined),
  stat: vi.fn().mockResolvedValue({ size: 1024 }),
  readFile: vi.fn().mockImplementation(async (filePath: string) => {
    // Return the pre-loaded WXR content for the test file
    if (filePath.endsWith('import-test-cases.wxr')) {
      return wxrFileContent;
    }
    return Buffer.from('test data');
  }),
}));

vi.mock('electron', () => ({
  app: {
    getPath: vi.fn(() => '/mock/user/data'),
  },
}));

let uuidCounter = 0;
vi.mock('uuid', () => ({
  v4: vi.fn(() => `test-uuid-${++uuidCounter}`),
}));

// Mock TagEngine
const mockTagEngine = {
  setProjectContext: vi.fn(),
  createTag: vi.fn().mockImplementation(async (input: { name: string }) => {
    createdTags.push(input.name.toLowerCase());
    return {
      id: `tag-${input.name}`,
      projectId: 'test-project',
      name: input.name.toLowerCase(),
      createdAt: new Date(),
      updatedAt: new Date(),
    };
  }),
  getAllTags: vi.fn().mockResolvedValue([]),
};

vi.mock('../../src/main/engine/TagEngine', () => ({
  getTagEngine: vi.fn(() => mockTagEngine),
}));

// Mock PostEngine
const mockPostEngine = {
  setProjectContext: vi.fn(),
  createPost: vi.fn(),
  publishPost: vi.fn(),
  isSlugAvailable: vi.fn().mockResolvedValue(true),
  generateUniqueSlug: vi.fn().mockImplementation(async (title: string) => {
    return `${title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}-new`;
  }),
  updateFTSIndex: vi.fn().mockResolvedValue(undefined),
};

vi.mock('../../src/main/engine/PostEngine', () => ({
  getPostEngine: vi.fn(() => mockPostEngine),
}));

// Mock MediaEngine
const mockMediaEngine = {
  setProjectContext: vi.fn(),
  importMedia: vi.fn().mockImplementation(async (sourcePath: string, metadata?: any) => {
    const result = {
      id: `media-${Math.random().toString(36).substr(2, 9)}`,
      filename: path.basename(sourcePath),
      originalName: metadata?.originalName || path.basename(sourcePath),
      title: metadata?.title,
      linkedPostIds: metadata?.linkedPostIds || [],
    };
    insertedMedia.push(result);
    return result;
  }),
};

vi.mock('../../src/main/engine/MediaEngine', () => ({
  getMediaEngine: vi.fn(() => mockMediaEngine),
}));

// Mock PostMediaEngine
const mockPostMediaEngine = {
  setProjectContext: vi.fn(),
  linkMediaToPost: vi.fn().mockImplementation(async (postId: string, mediaId: string) => {
    return {
      id: `link-${postId}-${mediaId}`,
      projectId: 'test-project',
      postId,
      mediaId,
      sortOrder: 0,
      createdAt: new Date(),
    };
  }),
};

vi.mock('../../src/main/engine/PostMediaEngine', () => ({
  getPostMediaEngine: vi.fn(() => mockPostMediaEngine),
}));

// Import after mocks are set up
import { ImportExecutionEngine } from '../../src/main/engine/ImportExecutionEngine';

describe('ImportExecutionEngine E2E Tests', () => {
  let engine: ImportExecutionEngine;
  let wxrData: WxrData;

  beforeEach(async () => {
    // Reset all tracking arrays
    insertedPosts.length = 0;
    insertedMedia.length = 0;
    updatedPosts.length = 0;
    createdTags.length = 0;
    writtenFiles.length = 0;
    uuidCounter = 0;

    // Clear all mocks
    vi.clearAllMocks();

    // Create engine instance
    engine = new ImportExecutionEngine({
      tagEngine: mockTagEngine as any,
      postEngine: mockPostEngine as any,
      mediaEngine: mockMediaEngine as any,
      postMediaEngine: mockPostMediaEngine as any,
    });
    engine.setProjectContext('test-project', '/mock/test/data');

    // Parse the WXR content (mocked readFile will return our pre-loaded content)
    const parser = new WxrParser();
    wxrData = await parser.parseFile(wxrFilePath);
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  // ==========================================================================
  // SECTION 1: HTML TO MARKDOWN CONVERSION
  // ==========================================================================

  describe('HTML to Markdown Conversion', () => {
    /**
     * Creates a minimal analysis report for a single post for testing conversion
     */
    function createSinglePostReport(wxrPost: WxrPost): ImportAnalysisReport {
      return {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };
    }

    it('should convert basic text formatting (bold, italic, strikethrough)', async () => {
      // Post 101: Basic Text Formatting
      const post = wxrData.posts.find(p => p.wpId === 101);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      expect(insertedPosts.length).toBe(1);

      // Find the written file for this post
      const writtenFile = writtenFiles.find(f => f.path.includes('html-formatting-basic'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Verify bold conversion: <strong> and <b> → **text**
      expect(content).toContain('**bold text**');
      expect(content).toContain('**another bold**');

      // Verify italic conversion: <em> and <i> → _text_ or *text*
      expect(content).toMatch(/_italic text_|\*italic text\*/);
      expect(content).toMatch(/_italic using i tag_|\*italic using i tag\*/);

      // Verify combined bold+italic (TurndownService outputs **_text_** or ***text***)
      expect(content).toMatch(/\*\*_bold and italic together_\*\*|\*\*\*bold and italic together\*\*\*/);

      // Note: TurndownService does NOT convert <del> and <s> to ~~ by default
      // The strikethrough text will appear as plain text
      expect(content).toContain('strikethrough text');
      expect(content).toContain('also this');
    });

    it('should convert headings (h1-h6) to ATX style', async () => {
      // Post 102: Headings
      const post = wxrData.posts.find(p => p.wpId === 102);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('html-formatting-headings'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Verify ATX-style headings
      expect(content).toContain('# Heading Level 1');
      expect(content).toContain('## Heading Level 2');
      expect(content).toContain('### Heading Level 3');
      expect(content).toContain('#### Heading Level 4');
      expect(content).toContain('##### Heading Level 5');
      expect(content).toContain('###### Heading Level 6');

      // Verify paragraphs between headings
      expect(content).toContain('Paragraph after h1');
      expect(content).toContain('Paragraph after h2');
    });

    it('should convert lists (ordered, unordered, nested)', async () => {
      // Post 103: Lists
      const post = wxrData.posts.find(p => p.wpId === 103);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('html-formatting-lists'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Verify unordered list items (- marker with possible spaces)
      expect(content).toMatch(/-\s+First item/);
      expect(content).toMatch(/-\s+Second item/);
      expect(content).toMatch(/-\s+Third item/);

      // Verify ordered list items
      expect(content).toMatch(/1\.\s+Step one/);
      expect(content).toMatch(/2\.\s+Step two/);
      expect(content).toMatch(/3\.\s+Step three/);

      // Verify nested list structure (indent varies)
      expect(content).toMatch(/-\s+Parent item/);
      expect(content).toMatch(/-\s+Another parent/);
      // Nested items should contain Child items somewhere in content
      expect(content).toContain('Child item 1');
      expect(content).toContain('Child item 2');
    });

    it('should convert links and images', async () => {
      // Post 104: Links and Images
      const post = wxrData.posts.find(p => p.wpId === 104);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('html-formatting-links'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Verify link conversion
      expect(content).toContain('[simple link](https://example.com)');
      expect(content).toMatch(/\[titled link\]\(https:\/\/example\.com.*\)/);

      // Verify image conversion
      expect(content).toContain('![Test image](https://example.com/image.jpg)');
      expect(content).toContain('![Photo](https://example.com/photo.png');

      // Verify linked image - should become a plain image (link is unwrapped)
      // The link href is not an image URL, so the image src is used
      expect(content).toContain('![Banner](https://example.com/banner.jpg)');
    });

    it('should convert code blocks (inline and fenced)', async () => {
      // Post 105: Code Blocks
      const post = wxrData.posts.find(p => p.wpId === 105);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('html-formatting-code'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Verify inline code
      expect(content).toContain('`const x = 10;`');

      // Verify fenced code block
      expect(content).toContain('```');
      expect(content).toContain('function hello()');
      expect(content).toContain('console.log("Hello World")');

      // Verify <pre> only block
      expect(content).toContain('Plain preformatted text');
    });

    it('should convert blockquotes', async () => {
      // Post 106: Blockquotes
      const post = wxrData.posts.find(p => p.wpId === 106);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('html-formatting-quotes'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Verify blockquote conversion
      expect(content).toContain('> The only way to do great work is to love what you do.');
      // Note: TurndownService escapes the dash, so it becomes \- or just text
      expect(content).toContain('Steve Jobs');

      // Verify nested blockquote (should have > > for inner quote)
      expect(content).toContain('> Outer quote');
      expect(content).toContain('> > Inner quote');
    });

    it('should convert linked images with empty alt to plain images with derived alt', async () => {
      // Post 107: Linked Images with empty/missing alt
      const post = wxrData.posts.find(p => p.wpId === 107);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('html-formatting-linked-images'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Linked image with empty alt should become a plain image with filename-derived alt
      // The link target is the full-size image, so use that for the image src
      // Note: extension is stripped from alt text for cleaner output
      expect(content).toContain('![full-size](http://example.com/wp-content/uploads/2020/03/full-size.png)');

      // Linked image with no alt attribute (link and image different)
      expect(content).toContain('![photo](http://example.com/gallery/photo.jpg)');

      // Linked image where link and src are the same
      expect(content).toContain('![photo](http://example.com/photo.jpg)');

      // Image with proper alt inside link should preserve the alt text
      expect(content).toContain('![Company Logo](http://example.com/logo.png)');

      // Image with title but empty alt should use title as alt text (title takes precedence over filename)
      expect(content).toContain('![Delicious Piroggen](http://example.com/wp-content/uploads/2020/03/dish.jpg');

      // Should NOT have empty image alt text (the broken pattern we're fixing)
      expect(content).not.toMatch(/!\[\]\([^)]+\)/);
    });

    it('should preserve line breaks in paragraph text', async () => {
      // Post 108: Line Breaks Preservation
      const post = wxrData.posts.find(p => p.wpId === 108);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('html-formatting-line-breaks'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Line breaks within paragraphs should be preserved as markdown line breaks
      // (either as two trailing spaces + newline, or as actual newlines)
      // The key is that "inside the text that should" appears on a separate line from 
      // "This paragraph has line breaks"
      expect(content).toMatch(/has line breaks\s*\n.*inside the text/);
      expect(content).toMatch(/inside the text that should\s*\n.*be preserved/);

      // Second paragraph should also preserve line breaks
      expect(content).toMatch(/another paragraph\s*\n.*with different content/);

      // Single line paragraph should remain intact
      expect(content).toContain('Single line paragraph for comparison.');
    });
  });

  // ==========================================================================
  // SECTION 2: SHORTCODE/MACRO CONVERSION
  // ==========================================================================

  describe('Shortcode to Macro Conversion', () => {
    function createSinglePostReport(wxrPost: WxrPost): ImportAnalysisReport {
      return {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };
    }

    it('should convert [gallery] shortcode to [[gallery]] macro', async () => {
      // Post 201: Gallery Shortcode
      const post = wxrData.posts.find(p => p.wpId === 201);
      expect(post).toBeDefined();
      expect(post!.content).toContain('[gallery ids="1,2,3" columns="3"]');

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('shortcode-gallery'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // MUST be converted to double brackets
      expect(content).toContain('[[gallery ids="1,2,3" columns="3"]]');
      // MUST NOT contain single bracket version
      expect(content).not.toMatch(/(?<!\[)\[gallery/);
    });

    it('should convert [video] shortcode with attributes to [[video]] macro', async () => {
      // Post 202: Video Shortcode
      const post = wxrData.posts.find(p => p.wpId === 202);
      expect(post).toBeDefined();
      expect(post!.content).toContain('[video src="https://example.com/video.mp4"');

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('shortcode-video'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // MUST preserve all attributes in double-bracket format
      expect(content).toContain('[[video src="https://example.com/video.mp4" width="640" height="360"]]');
    });

    it('should convert multiple shortcodes in a single post', async () => {
      // Post 203: Multiple Shortcodes
      const post = wxrData.posts.find(p => p.wpId === 203);
      expect(post).toBeDefined();

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('shortcode-multiple'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // All shortcodes must be converted
      expect(content).toContain('[[audio src="https://example.com/podcast.mp3"]]');
      expect(content).toContain('[[gallery ids="10,20,30"]]');
      expect(content).toContain('[[embed]]');

      // None should remain as single brackets
      expect(content).not.toMatch(/(?<!\[)\[audio /);
      expect(content).not.toMatch(/(?<!\[)\[gallery /);
      expect(content).not.toMatch(/(?<!\[)\[embed\]/);
    });

    it('should convert self-closing shortcodes [shortcode /]', async () => {
      // Post 204: Self-Closing Shortcodes
      const post = wxrData.posts.find(p => p.wpId === 204);
      expect(post).toBeDefined();
      expect(post!.content).toContain('[divider /]');
      expect(post!.content).toContain('[spacer height="20" /]');

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('shortcode-selfclose'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Self-closing shortcodes should be converted (removing the /)
      expect(content).toContain('[[divider]]');
      expect(content).toContain('[[spacer height="20"]]');
    });

    it('should NOT double-convert already bracketed [[macro]] content', async () => {
      // Post 205: Already Double-Bracketed
      const post = wxrData.posts.find(p => p.wpId === 205);
      expect(post).toBeDefined();
      // Original content has both formats
      expect(post!.content).toContain('[[gallery ids="1,2,3"]]');
      expect(post!.content).toContain('[video src="new.mp4"]');
      expect(post!.content).toContain('[[audio src="podcast.mp3"]]');

      const report = createSinglePostReport(post!);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('shortcode-already'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // Already-converted macros MUST remain as double brackets (not become [[[[...)
      expect(content).toContain('[[gallery ids="1,2,3"]]');
      expect(content).not.toContain('[[[');
      expect(content).not.toContain(']]]');

      // Single bracket shortcode MUST be converted
      expect(content).toContain('[[video src="new.mp4"]]');

      // Pre-existing double bracket MUST remain unchanged
      expect(content).toContain('[[audio src="podcast.mp3"]]');
    });
  });

  // ==========================================================================
  // SECTION 3: TAG AND CATEGORY MAPPING
  // ==========================================================================

  describe('Tag and Category Mapping', () => {
    it('should create new tags that do not exist in the project', async () => {
      // Post 201 has tag "React" which we'll mark as new
      const post = wxrData.posts.find(p => p.wpId === 201);
      expect(post).toBeDefined();
      expect(post!.tags).toContain('React');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: post!,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [
          { name: 'React', slug: 'react', existsInProject: false },  // New tag
        ],
        categories: [
          { name: 'Technology', slug: 'technology', existsInProject: true },  // Existing
        ],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      // Tag "react" should have been created
      expect(createdTags).toContain('react');
      expect(result.tags.created).toBe(1);

      // The imported post should have the tag
      expect(insertedPosts.length).toBe(1);
      const postTags = JSON.parse(insertedPosts[0].tags);
      expect(postTags).toContain('react');
    });

    it('should map tags to existing project tags when mappedTo is set', async () => {
      // Post 203 has tag "nodejs" which we'll map to existing "node"
      const post = wxrData.posts.find(p => p.wpId === 203);
      expect(post).toBeDefined();
      expect(post!.tags).toContain('nodejs');
      expect(post!.tags).toContain('JavaScript');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: post!,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [
          { name: 'nodejs', slug: 'nodejs', existsInProject: false, mappedTo: 'node' },  // Mapped
          { name: 'JavaScript', slug: 'javascript', existsInProject: true },  // Existing
        ],
        categories: [
          { name: 'Web Dev', slug: 'web-dev', existsInProject: false, mappedTo: 'web-development' },  // Mapped
        ],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      // Should NOT create "nodejs" tag (it's mapped)
      expect(createdTags).not.toContain('nodejs');
      // Should NOT create "node" either (it exists)
      expect(createdTags).not.toContain('node');

      // The imported post should use the mapped tag name
      expect(insertedPosts.length).toBe(1);
      const postTags = JSON.parse(insertedPosts[0].tags);
      expect(postTags).toContain('node');  // Mapped from "nodejs"
      expect(postTags).toContain('javascript');  // Existing

      // Category should also be mapped
      const postCategories = JSON.parse(insertedPosts[0].categories);
      expect(postCategories).toContain('web-development');  // Mapped from "Web Dev"
    });

    it('should skip existing tags and not try to create them', async () => {
      const post = wxrData.posts.find(p => p.wpId === 105);
      expect(post).toBeDefined();
      expect(post!.tags).toContain('JavaScript');
      expect(post!.tags).toContain('TypeScript');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: post!,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [
          { name: 'JavaScript', slug: 'javascript', existsInProject: true },
          { name: 'TypeScript', slug: 'typescript', existsInProject: true },
        ],
        categories: [
          { name: 'Programming', slug: 'programming', existsInProject: true },
        ],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      // No tags should be created (all exist)
      expect(createdTags.length).toBe(0);
      expect(result.tags.created).toBe(0);
      expect(result.tags.skipped).toBe(3);  // 2 tags + 1 category

      // Post should still have the tags
      const postTags = JSON.parse(insertedPosts[0].tags);
      expect(postTags).toContain('javascript');
      expect(postTags).toContain('typescript');
    });
  });

  // ==========================================================================
  // SECTION 4: CONFLICT RESOLUTION
  // ==========================================================================

  describe('Conflict Resolution', () => {
    it('should SKIP import when conflict resolution is "ignore"', async () => {
      // Post 301: Conflict → Ignore
      const post = wxrData.posts.find(p => p.wpId === 301);
      expect(post).toBeDefined();
      expect(post!.slug).toBe('existing-post');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 0,
          update: 0,
          conflict: 1,
          items: [{
            wxrPost: post!,
            status: 'conflict' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
            conflictResolution: 'ignore',
            existingPost: {
              id: 'existing-post-id',
              title: 'Existing Post',
              slug: 'existing-post',
              checksum: 'old-hash',
              pubDate: '2023-01-01T00:00:00Z',
              excerpt: null,
              author: null,
              tags: [],
              categories: [],
            },
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      // Post should be SKIPPED
      expect(result.posts.skipped).toBe(1);
      expect(result.posts.imported).toBe(0);

      // No post should be inserted
      expect(insertedPosts.length).toBe(0);

      // No file should be written for this post
      const writtenFile = writtenFiles.find(f => f.path.includes('existing-post'));
      expect(writtenFile).toBeUndefined();
    });

    it('should import as DRAFT when conflict resolution is "overwrite"', async () => {
      // Post 302: Conflict → Overwrite
      const post = wxrData.posts.find(p => p.wpId === 302);
      expect(post).toBeDefined();
      expect(post!.slug).toBe('overwrite-me');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 0,
          update: 0,
          conflict: 1,
          items: [{
            wxrPost: post!,
            status: 'conflict' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
            conflictResolution: 'overwrite',
            existingPost: {
              id: 'existing-overwrite-id',
              title: 'Original Post',
              slug: 'overwrite-me',
              checksum: 'old-hash',
              pubDate: '2023-01-01T00:00:00Z',
              excerpt: null,
              author: null,
              tags: [],
              categories: [],
            },
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [
          { name: 'TypeScript', slug: 'typescript', existsInProject: true },
        ],
        categories: [
          { name: 'Programming', slug: 'programming', existsInProject: true },
        ],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      // Post should be IMPORTED (via update)
      expect(result.posts.imported).toBe(1);
      expect(result.posts.skipped).toBe(0);

      // Should UPDATE existing post, not insert new one
      expect(insertedPosts.length).toBe(0);
      expect(updatedPosts.length).toBeGreaterThan(0);

      // The updated post MUST be a DRAFT
      expect(updatedPosts[0].data.status).toBe('draft');

      // Draft posts store content in DB, not in file
      expect(updatedPosts[0].data.content).not.toBeNull();
      expect(updatedPosts[0].data.content).toContain('conflict resolution is "overwrite"');

      // No file should be written (draft = content in DB)
      const writtenFile = writtenFiles.find(f => f.path.includes('overwrite-me'));
      expect(writtenFile).toBeUndefined();
    });

    it('should import with NEW SLUG when conflict resolution is "import"', async () => {
      // Post 303: Conflict → Import (new slug)
      const post = wxrData.posts.find(p => p.wpId === 303);
      expect(post).toBeDefined();
      expect(post!.slug).toBe('duplicate-slug');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 0,
          update: 0,
          conflict: 1,
          items: [{
            wxrPost: post!,
            status: 'conflict' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
            conflictResolution: 'import',
            existingPost: {
              id: 'existing-duplicate-id',
              title: 'Duplicate Post',
              slug: 'duplicate-slug',
              checksum: 'old-hash',
              pubDate: '2023-01-01T00:00:00Z',
              excerpt: null,
              author: null,
              tags: [],
              categories: [],
            },
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [
          { name: 'Web Dev', slug: 'web-dev', existsInProject: true },
        ],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      // Post should be IMPORTED
      expect(result.posts.imported).toBe(1);

      // Should insert exactly one post
      expect(insertedPosts.length).toBe(1);

      // The inserted post should be PUBLISHED (not draft)
      expect(insertedPosts[0].status).toBe('published');

      // The slug must be DIFFERENT from the original (new unique slug)
      expect(insertedPosts[0].slug).not.toBe('duplicate-slug');
      // The mock generates slug from title with "-new" suffix
      expect(insertedPosts[0].slug).toBe('conflict-test-import-as-new-new');

      // Published post should have file written
      const writtenFile = writtenFiles.find(f => f.path.includes('conflict-test-import-as-new-new'));
      expect(writtenFile).toBeDefined();
    });

    it('should preserve WordPress dates when importing via update (overwrite)', async () => {
      // Post 302 has specific dates we want to preserve
      const post = wxrData.posts.find(p => p.wpId === 302);
      expect(post).toBeDefined();

      // Verify the WXR dates are parsed correctly
      expect(post!.postDate).toBeInstanceOf(Date);
      expect(post!.postModified).toBeInstanceOf(Date);
      expect(post!.postDate!.toISOString()).toContain('2024-01-23');
      expect(post!.postModified!.toISOString()).toContain('2024-01-23');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 0,
          update: 0,
          conflict: 1,
          items: [{
            wxrPost: post!,
            status: 'conflict' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
            conflictResolution: 'overwrite',
            existingPost: {
              id: 'existing-id',
              title: 'Original',
              slug: 'overwrite-me',
              checksum: 'old',
              pubDate: null,
              excerpt: null,
              author: null,
              tags: [],
              categories: [],
            },
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      await engine.executeImport(report, {});

      // Overwrite now updates existing post, not inserts
      expect(insertedPosts.length).toBe(0);
      expect(updatedPosts.length).toBeGreaterThan(0);

      // For updates, the updatedAt is set to now (not the WXR date)
      // since we're updating an existing post
      const updateData = updatedPosts[0].data;
      expect(updateData.updatedAt).toBeInstanceOf(Date);
    });
  });

  // ==========================================================================
  // SECTION 5: MEDIA IMPORT
  // ==========================================================================

  describe('Media Import', () => {
    it('should import media and link to parent post via wpId mapping', async () => {
      // First import Post 201 (the parent of Media 401)
      const post = wxrData.posts.find(p => p.wpId === 201);
      const media = wxrData.media.find(m => m.wpId === 401);

      expect(post).toBeDefined();
      expect(media).toBeDefined();
      expect(media!.parentId).toBe(201);  // Media is attached to post 201

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: post!,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          missing: 0,
          items: [{
            wxrMedia: media!,
            status: 'new' as MediaAnalysisStatus,
            fileHash: 'media-hash',
          }],
        },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, { uploadsFolder: '/mock/wp-content/uploads' });

      // Post should be imported
      expect(result.posts.imported).toBe(1);

      // Media should be imported
      expect(result.media.imported).toBe(1);

      // Verify wpId to postId mapping was created
      expect(result.wpIdToPostId.has(201)).toBe(true);

      // Media should be linked to the imported post
      expect(insertedMedia.length).toBe(1);
      expect(insertedMedia[0].linkedPostIds.length).toBe(1);
      expect(insertedMedia[0].linkedPostIds[0]).toBe(result.wpIdToPostId.get(201));

      // CRITICAL: Verify PostMediaEngine.linkMediaToPost was called to create the DB link
      expect(mockPostMediaEngine.linkMediaToPost).toHaveBeenCalledWith(
        result.wpIdToPostId.get(201),  // postId
        insertedMedia[0].id             // mediaId
      );
    });

    it('should import standalone media without parent link', async () => {
      // Media 402 has no parent (parentId = 0)
      const media = wxrData.media.find(m => m.wpId === 402);
      expect(media).toBeDefined();
      expect(media!.parentId).toBe(0);

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          missing: 0,
          items: [{
            wxrMedia: media!,
            status: 'new' as MediaAnalysisStatus,
            fileHash: 'media-hash',
          }],
        },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, { uploadsFolder: '/mock/wp-content/uploads' });

      expect(result.media.imported).toBe(1);

      // Should be imported with title from WXR title
      expect(insertedMedia.length).toBe(1);
      expect(insertedMedia[0].title).toBe('standalone-logo');

      // No linked posts (standalone)
      expect(insertedMedia[0].linkedPostIds.length).toBe(0);
    });

    it('should skip media when conflict resolution is "ignore"', async () => {
      // Media 403: Conflict ignore
      const media = wxrData.media.find(m => m.wpId === 403);
      expect(media).toBeDefined();

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: {
          total: 1,
          new: 0,
          update: 0,
          conflict: 1,
          missing: 0,
          items: [{
            wxrMedia: media!,
            status: 'conflict' as MediaAnalysisStatus,
            fileHash: 'media-hash',
            conflictResolution: 'ignore',
            existingMedia: {
              id: 'existing-media-id',
              originalName: 'existing.jpg',
              checksum: 'existing-hash',
            },
          } as any],
        },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, { uploadsFolder: '/mock/wp-content/uploads' });

      // Media should be SKIPPED
      expect(result.media.skipped).toBe(1);
      expect(result.media.imported).toBe(0);

      // No media should be imported
      expect(insertedMedia.length).toBe(0);
    });

    it('should skip media when file is missing in uploads folder', async () => {
      // Media with status 'missing'
      const media = wxrData.media.find(m => m.wpId === 401);
      expect(media).toBeDefined();

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: {
          total: 1,
          new: 0,
          update: 0,
          conflict: 0,
          missing: 1,
          items: [{
            wxrMedia: media!,
            status: 'missing' as MediaAnalysisStatus,
            fileHash: null,
          }],
        },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, { uploadsFolder: '/mock/wp-content/uploads' });

      // Media should be SKIPPED (missing)
      expect(result.media.skipped).toBe(1);
      expect(result.media.imported).toBe(0);
    });
  });

  // ==========================================================================
  // SECTION 6: PAGE IMPORT
  // ==========================================================================

  describe('Page Import', () => {
    it('should import pages as posts with "page" category', async () => {
      // Page 501: Standard page
      const page = wxrData.pages.find(p => p.wpId === 501);
      expect(page).toBeDefined();
      expect(page!.postType).toBe('page');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        pages: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: page!,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      expect(result.pages.imported).toBe(1);

      // Page should be inserted as a post
      expect(insertedPosts.length).toBe(1);
      expect(insertedPosts[0].title).toBe('About This Blog');

      // MUST have "page" category
      const categories = JSON.parse(insertedPosts[0].categories);
      expect(categories).toContain('page');

      // Verify content is converted to Markdown
      const writtenFile = writtenFiles.find(f => f.path.includes('about'));
      expect(writtenFile).toBeDefined();
      expect(writtenFile!.content).toContain('## About');
      expect(writtenFile!.content).toContain('Welcome to my blog. This is a page, not a post.');
    });

    it('should convert page HTML content and shortcodes', async () => {
      // Page 502: Page with complex HTML and shortcode
      const page = wxrData.pages.find(p => p.wpId === 502);
      expect(page).toBeDefined();
      expect(page!.content).toContain('[contact_form id="1"]');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        pages: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: page!,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('contact'));
      expect(writtenFile).toBeDefined();

      const content = writtenFile!.content;

      // HTML should be converted to Markdown
      expect(content).toContain('## Contact Us');
      expect(content).toContain('### Office Hours');

      // Links should be converted
      expect(content).toContain('[test@example.com](mailto:test@example.com)');
      expect(content).toContain('[@test](https://twitter.com/test)');

      // Lists should be converted (TurndownService uses multiple spaces after -)
      expect(content).toMatch(/-\s+Email:/);
      expect(content).toMatch(/-\s+Twitter:/);

      // Shortcode should be converted to macro (underscore may be escaped by TurndownService)
      expect(content).toMatch(/\[\[contact_?\\?_?form id="1"\]\]/);
    });

    it('should preserve page author information', async () => {
      // Page 502 has author "admin"
      const page = wxrData.pages.find(p => p.wpId === 502);
      expect(page).toBeDefined();
      expect(page!.creator).toBe('admin');

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        pages: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: page!,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      await engine.executeImport(report, {});

      expect(insertedPosts.length).toBe(1);
      expect(insertedPosts[0].author).toBe('admin');
    });
  });

  // ==========================================================================
  // SECTION 7: RESULT SUMMARY VERIFICATION
  // ==========================================================================

  describe('Result Summary Accuracy', () => {
    it('should return accurate counts for a mixed import', async () => {
      // Import multiple posts with different statuses
      const post1 = wxrData.posts.find(p => p.wpId === 101);  // New
      const post2 = wxrData.posts.find(p => p.wpId === 301);  // Conflict-ignore
      const post3 = wxrData.posts.find(p => p.wpId === 302);  // Conflict-overwrite
      const page = wxrData.pages.find(p => p.wpId === 501);   // New page
      const media = wxrData.media.find(m => m.wpId === 402);  // New media

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 3,
          new: 1,
          update: 0,
          conflict: 2,
          items: [
            {
              wxrPost: post1!,
              status: 'new' as PostAnalysisStatus,
              contentHash: 'hash1',
              markdownPreview: '',
            },
            {
              wxrPost: post2!,
              status: 'conflict' as PostAnalysisStatus,
              contentHash: 'hash2',
              markdownPreview: '',
              conflictResolution: 'ignore',
              existingPost: { id: 'e1', title: 'E1', slug: 'existing-post', checksum: null, pubDate: null, excerpt: null, author: null, tags: [], categories: [] },
            },
            {
              wxrPost: post3!,
              status: 'conflict' as PostAnalysisStatus,
              contentHash: 'hash3',
              markdownPreview: '',
              conflictResolution: 'overwrite',
              existingPost: { id: 'e2', title: 'E2', slug: 'overwrite-me', checksum: null, pubDate: null, excerpt: null, author: null, tags: [], categories: [] },
            },
          ],
        },
        pages: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: page!,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'hash4',
            markdownPreview: '',
          }],
        },
        media: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          missing: 0,
          items: [{
            wxrMedia: media!,
            status: 'new' as MediaAnalysisStatus,
            fileHash: 'media-hash',
          }],
        },
        tags: [
          { name: 'NewTag', slug: 'newtag', existsInProject: false },
          { name: 'ExistingTag', slug: 'existingtag', existsInProject: true },
        ],
        categories: [
          { name: 'Technology', slug: 'technology', existsInProject: true },
        ],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, { uploadsFolder: '/mock/wp-content/uploads' });

      // Verify result accuracy
      expect(result.success).toBe(true);

      // Posts: 1 new inserted, 1 ignore skipped, 1 overwrite updated (counts as imported)
      expect(result.posts.imported).toBe(2);  // post1 (inserted) + post3 (updated)
      expect(result.posts.skipped).toBe(1);   // post2 (ignore)
      expect(result.posts.errors).toBe(0);

      // Verify that post1 was inserted and post3 was updated
      // Note: insertedPosts may include the page as well (pages are stored as posts)
      const postInserts = insertedPosts.filter(p => !JSON.parse(p.categories || '[]').includes('page'));
      const pageInserts = insertedPosts.filter(p => JSON.parse(p.categories || '[]').includes('page'));
      expect(postInserts.length).toBe(1);   // post1 only (new)
      expect(updatedPosts.length).toBeGreaterThan(0);  // post3 (overwrite)

      // Pages: 1 imported (as insert since it's new)
      expect(result.pages.imported).toBe(1);
      expect(result.pages.skipped).toBe(0);
      expect(result.pages.errors).toBe(0);
      expect(pageInserts.length).toBe(1);

      // Media: 1 imported
      expect(result.media.imported).toBe(1);
      expect(result.media.skipped).toBe(0);
      expect(result.media.errors).toBe(0);

      // Tags: 1 created (NewTag), 2 skipped (ExistingTag + Technology category)
      expect(result.tags.created).toBe(1);
      expect(result.tags.skipped).toBe(2);

      // WpId mapping should have entries for imported posts
      expect(result.wpIdToPostId.size).toBeGreaterThanOrEqual(2);  // post1, post3
    });
  });

  // ==========================================================================
  // SECTION 7: OTHER POST TYPES (analyzed but not imported)
  // ==========================================================================

  describe('Other Post Types (analyzed but not imported)', () => {
    it('should include nav_menu_item, revision, and wp_template in WXR parsed data', () => {
      // Verify the WXR parser includes these in the posts array
      const navMenuItem = wxrData.posts.find(p => p.wpId === 601);
      const revision = wxrData.posts.find(p => p.wpId === 602);
      const wpTemplate = wxrData.posts.find(p => p.wpId === 603);

      expect(navMenuItem).toBeDefined();
      expect(navMenuItem!.postType).toBe('nav_menu_item');
      expect(navMenuItem!.title).toBe('Home Menu Link');

      expect(revision).toBeDefined();
      expect(revision!.postType).toBe('revision');
      expect(revision!.slug).toBe('101-revision-v1');

      expect(wpTemplate).toBeDefined();
      expect(wpTemplate!.postType).toBe('wp_template');
      expect(wpTemplate!.title).toBe('Single Post Template');
    });

    it('should include other post types in analysis report but skip them during import', async () => {
      // Find the "other" post types from parsed WXR
      const navMenuItem = wxrData.posts.find(p => p.wpId === 601)!;
      const revision = wxrData.posts.find(p => p.wpId === 602)!;
      const wpTemplate = wxrData.posts.find(p => p.wpId === 603)!;
      
      // Also include a regular post to verify it gets imported
      const regularPost = wxrData.posts.find(p => p.wpId === 101)!;

      // Create analysis report that includes both regular posts and "other" post types
      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 4,
          new: 4,
          update: 0,
          conflict: 0,
          items: [
            {
              wxrPost: regularPost,
              status: 'new' as PostAnalysisStatus,
              contentHash: 'hash1',
              markdownPreview: '',
            },
            {
              wxrPost: navMenuItem,
              status: 'new' as PostAnalysisStatus,
              contentHash: 'hash2',
              markdownPreview: '',
            },
            {
              wxrPost: revision,
              status: 'new' as PostAnalysisStatus,
              contentHash: 'hash3',
              markdownPreview: '',
            },
            {
              wxrPost: wpTemplate,
              status: 'new' as PostAnalysisStatus,
              contentHash: 'hash4',
              markdownPreview: '',
            },
          ],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      // Verify only the regular post was imported
      expect(result.posts.imported).toBe(1);
      
      // The 3 "other" post types should be skipped
      expect(result.posts.skipped).toBe(3);
      expect(result.posts.errors).toBe(0);

      // Verify only one post was actually written to database/filesystem
      expect(insertedPosts.length).toBe(1);
      expect(insertedPosts[0].slug).toBe('html-formatting-basic');

      // Verify no files were written for nav_menu_item, revision, or wp_template
      const writtenSlugs = writtenFiles.map(f => f.path);
      expect(writtenSlugs.some(p => p.includes('home-menu-link'))).toBe(false);
      expect(writtenSlugs.some(p => p.includes('101-revision-v1'))).toBe(false);
      expect(writtenSlugs.some(p => p.includes('single'))).toBe(false);
    });

    it('should correctly count skipped "other" types in result summary', async () => {
      // Test with only "other" post types to verify counting
      const navMenuItem = wxrData.posts.find(p => p.wpId === 601)!;
      const revision = wxrData.posts.find(p => p.wpId === 602)!;

      const report: ImportAnalysisReport = {
        wxrData: wxrData,
        posts: {
          total: 2,
          new: 2,
          update: 0,
          conflict: 0,
          items: [
            {
              wxrPost: navMenuItem,
              status: 'new' as PostAnalysisStatus,
              contentHash: 'hash1',
              markdownPreview: '',
            },
            {
              wxrPost: revision,
              status: 'new' as PostAnalysisStatus,
              contentHash: 'hash2',
              markdownPreview: '',
            },
          ],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: wxrData.site,
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };

      const result = await engine.executeImport(report, {});

      // All should be skipped, none imported
      expect(result.posts.imported).toBe(0);
      expect(result.posts.skipped).toBe(2);
      expect(result.posts.errors).toBe(0);
      expect(insertedPosts.length).toBe(0);
    });
  });

  // ==========================================================================
  // SECTION 8: MEDIA URL CONVERSION TO RELATIVE PATHS
  // ==========================================================================

  describe('Media URL Conversion to Relative Paths', () => {
    /**
     * Creates a custom post with specific content for URL conversion testing
     */
    function createPostWithContent(content: string, siteUrl: string = 'https://testblog'): ImportAnalysisReport {
      const customPost: WxrPost = {
        wpId: 9001,
        title: 'URL Conversion Test Post',
        slug: 'url-conversion-test',
        content: content,
        excerpt: '',
        pubDate: new Date('2024-01-15T10:00:00Z'),
        postDate: new Date('2024-01-15T10:00:00Z'),
        postModified: new Date('2024-01-15T10:00:00Z'),
        creator: 'testauthor',
        status: 'publish',
        postType: 'post',
        categories: [],
        tags: [],
      };

      return {
        wxrData: wxrData,
        posts: {
          total: 1,
          new: 1,
          update: 0,
          conflict: 0,
          items: [{
            wxrPost: customPost,
            status: 'new' as PostAnalysisStatus,
            contentHash: 'test-hash',
            markdownPreview: '',
          }],
        },
        pages: { total: 0, new: 0, update: 0, conflict: 0, items: [] },
        media: { total: 0, new: 0, update: 0, conflict: 0, missing: 0, items: [] },
        tags: [],
        categories: [],
        site: { ...wxrData.site, link: siteUrl },
        macros: { totalUniqueMacros: 0, totalMacroUsages: 0, allMapped: true, macros: [] },
      };
    }

    it('should convert absolute media URLs from site domain to relative paths', async () => {
      // Post with image URL pointing to the site's own media
      const content = `<p>Check out this image:</p>
    <img src="https://testblog/wp-content/uploads/2022/11/P1010853_01.jpg" alt="My Photo" />
<p>Nice, right?</p>`;

      const report = createPostWithContent(content);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;

      // Should convert to relative media URL
      expect(fileContent).toContain('![My Photo](media/2022/11/P1010853_01.jpg)');
      // Should NOT contain the absolute URL
      expect(fileContent).not.toContain('https://testblog/wp-content/uploads');
    });

    it('should convert linked images with absolute media URLs to relative paths', async () => {
      // Linked image pattern common in WordPress - thumbnail links to full-size
      const content = `<a href="https://testblog/wp-content/uploads/2022/11/full-size.jpg">
    <img src="https://testblog/wp-content/uploads/2022/11/thumb.jpg" alt="Gallery Image" />
</a>`;

      const report = createPostWithContent(content);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;

      // The linked image rule uses the href (full-size) as the image URL
      expect(fileContent).toContain('media/2022/11/full-size.jpg');
      // Should NOT contain absolute URLs
      expect(fileContent).not.toContain('https://testblog/wp-content/uploads');
    });

    it('should preserve external image URLs that are not from the site', async () => {
      // Mix of site-owned and external images
      const content = `<p>Own image:</p>
    <img src="https://testblog/wp-content/uploads/2024/01/local.jpg" alt="Local" />
<p>External image:</p>
<img src="https://external-site.com/images/photo.jpg" alt="External" />`;

      const report = createPostWithContent(content);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;

      // Local image should become relative
      expect(fileContent).toContain('![Local](media/2024/01/local.jpg)');
      // External image should remain absolute
      expect(fileContent).toContain('![External](https://external-site.com/images/photo.jpg)');
    });

    it('should handle site URLs with trailing slash', async () => {
      const content = `<img src="https://hugo.rfc1437.de/wp-content/uploads/2022/11/image.jpg" alt="Test" />`;

      const report = createPostWithContent(content, 'https://hugo.rfc1437.de/');
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;
      expect(fileContent).toContain('![Test](media/2022/11/image.jpg)');
    });

    it('should handle site URLs without trailing slash', async () => {
      const content = `<img src="https://hugo.rfc1437.de/wp-content/uploads/2022/11/image.jpg" alt="Test" />`;

      const report = createPostWithContent(content, 'https://hugo.rfc1437.de');
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;
      expect(fileContent).toContain('![Test](media/2022/11/image.jpg)');
    });

    it('should convert media URLs in markdown image syntax after HTML conversion', async () => {
      // Sometimes WordPress content already has markdown-like syntax in HTML
      const content = `<p>Image with title:</p>
    <img src="https://testblog/wp-content/uploads/2024/02/sunset.png" alt="Sunset" title="Beautiful Sunset" />`;

      const report = createPostWithContent(content);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;
      // Image with title should still get relative URL
      expect(fileContent).toContain('media/2024/02/sunset.png');
      expect(fileContent).toContain('Beautiful Sunset');
    });

    it('should handle multiple images in same post', async () => {
      const content = `<p>Gallery:</p>
    <img src="https://testblog/wp-content/uploads/2024/01/img1.jpg" alt="Image 1" />
    <img src="https://testblog/wp-content/uploads/2024/01/img2.jpg" alt="Image 2" />
    <img src="https://testblog/wp-content/uploads/2024/02/img3.jpg" alt="Image 3" />`;

      const report = createPostWithContent(content);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;
      expect(fileContent).toContain('![Image 1](media/2024/01/img1.jpg)');
      expect(fileContent).toContain('![Image 2](media/2024/01/img2.jpg)');
      expect(fileContent).toContain('![Image 3](media/2024/02/img3.jpg)');
    });

    it('should handle deep nested upload paths', async () => {
      const content = `<img src="https://testblog/wp-content/uploads/sites/2/2024/03/nested/deep/image.jpg" alt="Deep" />`;

      const report = createPostWithContent(content);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;
      // Even complex paths should work, preserving path after wp-content/uploads/
      expect(fileContent).toContain('media/sites/2/2024/03/nested/deep/image.jpg');
    });

    it('should NOT convert wp-content/themes or wp-content/plugins URLs', async () => {
      // Assets from themes/plugins should stay absolute (they're not imported media)
      const content = `<img src="https://testblog/wp-content/themes/mytheme/images/logo.png" alt="Theme Logo" />
    <img src="https://testblog/wp-content/plugins/myplugin/assets/icon.png" alt="Plugin Icon" />`;

      const report = createPostWithContent(content);
      await engine.executeImport(report, {});

      const writtenFile = writtenFiles.find(f => f.path.includes('url-conversion-test'));
      expect(writtenFile).toBeDefined();

      const fileContent = writtenFile!.content;
      // Theme assets should remain absolute
      expect(fileContent).toContain('https://testblog/wp-content/themes/');
      // Plugin assets should remain absolute  
      expect(fileContent).toContain('https://testblog/wp-content/plugins/');
    });
  });
});
