/**
 * formatter.test.ts - Unit tests verifying context is shown in all output formats
 *
 * Run with: bun test formatter.test.ts
 */

import { describe, test, expect } from "vitest";
import {
  // Search result formatters
  searchResultsToJson,
  searchResultsToCsv,
  searchResultsToFiles,
  searchResultsToMarkdown,
  searchResultsToXml,
  searchResultsToMcpCsv,
  formatSearchResults,
  // Document (multi-get) formatters
  documentsToJson,
  documentsToCsv,
  documentsToFiles,
  documentsToMarkdown,
  documentsToXml,
  formatDocuments,
  // Single document formatters
  documentToJson,
  documentToMarkdown,
  documentToXml,
  formatDocument,
  type MultiGetFile,
} from "../src/cli/formatter.js";
import type { SearchResult, DocumentResult } from "../src/store.js";

// =============================================================================
// Test Fixtures
// =============================================================================

const TEST_CONTEXT = "Internal engineering keynotes from company summit events";

function makeSearchResult(overrides: Partial<SearchResult> = {}): SearchResult {
  return {
    filepath: "qmd://archive/summit/keynote.md",
    displayPath: "qmd://archive/summit/keynote.md",
    title: "Summit Keynote",
    context: TEST_CONTEXT,
    hash: "dc5590abcdef",
    docid: "dc5590",
    collectionName: "archive",
    modifiedAt: "2024-01-01T00:00:00Z",
    bodyLength: 100,
    body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.",
    score: 0.84,
    source: "fts",
    ...overrides,
  };
}

function makeDocumentResult(overrides: Partial<DocumentResult> = {}): DocumentResult {
  return {
    filepath: "qmd://archive/summit/keynote.md",
    displayPath: "qmd://archive/summit/keynote.md",
    title: "Summit Keynote",
    context: TEST_CONTEXT,
    hash: "dc5590abcdef",
    docid: "dc5590",
    collectionName: "archive",
    modifiedAt: "2024-01-01T00:00:00Z",
    bodyLength: 100,
    body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.",
    ...overrides,
  };
}

function makeMultiGetFile(overrides: Partial<MultiGetFile & { skipped: false }> = {}): MultiGetFile {
  return {
    filepath: "qmd://archive/summit/keynote.md",
    displayPath: "qmd://archive/summit/keynote.md",
    title: "Summit Keynote",
    context: TEST_CONTEXT,
    body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.",
    skipped: false,
    ...overrides,
  };
}

// =============================================================================
// Search Results: Context in Every Format
// =============================================================================

describe("search results include context in all formats", () => {
  const results = [makeSearchResult()];

  test("JSON format includes context", () => {
    const output = searchResultsToJson(results, { query: "keynote" });
    const parsed = JSON.parse(output);
    expect(parsed[0].context).toBe(TEST_CONTEXT);
  });

  test("JSON format includes line", () => {
    const output = searchResultsToJson(results, { query: "keynote" });
    const parsed = JSON.parse(output);
    expect(parsed[0].line).toBeTypeOf("number");
    expect(parsed[0].line).toBeGreaterThan(0);
  });

  test("JSON format includes line with --full", () => {
    const output = searchResultsToJson(results, { query: "keynote", full: true });
    const parsed = JSON.parse(output);
    expect(parsed[0].line).toBeTypeOf("number");
    expect(parsed[0].line).toBeGreaterThan(0);
  });

  test("CSV format includes context", () => {
    const output = searchResultsToCsv(results, { query: "keynote" });
    // Header should have context column
    const lines = output.split("\n");
    expect(lines[0]).toContain("context");
    // Data row should contain the context text
    expect(output).toContain(TEST_CONTEXT);
  });

  test("files format includes context", () => {
    const output = searchResultsToFiles(results);
    expect(output).toContain(TEST_CONTEXT);
  });

  test("Markdown format includes context", () => {
    const output = searchResultsToMarkdown(results, { query: "keynote" });
    expect(output).toContain(TEST_CONTEXT);
  });

  test("XML format includes context", () => {
    const output = searchResultsToXml(results, { query: "keynote" });
    expect(output).toContain(TEST_CONTEXT);
  });

  test("MCP CSV format includes context", () => {
    const mcpResults = [{
      docid: "dc5590",
      file: "qmd://archive/summit/keynote.md",
      title: "Summit Keynote",
      score: 0.84,
      context: TEST_CONTEXT,
      snippet: "This is the keynote content.",
    }];
    const output = searchResultsToMcpCsv(mcpResults);
    expect(output).toContain(TEST_CONTEXT);
  });

  test("formatSearchResults (JSON) includes context", () => {
    const output = formatSearchResults(results, "json", { query: "keynote" });
    const parsed = JSON.parse(output);
    expect(parsed[0].context).toBe(TEST_CONTEXT);
  });

  test("formatSearchResults (CSV) includes context", () => {
    const output = formatSearchResults(results, "csv", { query: "keynote" });
    expect(output).toContain(TEST_CONTEXT);
  });

  test("formatSearchResults (files) includes context", () => {
    const output = formatSearchResults(results, "files");
    expect(output).toContain(TEST_CONTEXT);
  });

  test("formatSearchResults (md) includes context", () => {
    const output = formatSearchResults(results, "md", { query: "keynote" });
    expect(output).toContain(TEST_CONTEXT);
  });

  test("formatSearchResults (xml) includes context", () => {
    const output = formatSearchResults(results, "xml", { query: "keynote" });
    expect(output).toContain(TEST_CONTEXT);
  });
});

// =============================================================================
// Search Results: No Context When Absent
// =============================================================================

describe("search results omit context when null", () => {
  const results = [makeSearchResult({ context: null })];

  test("JSON format omits context field when null", () => {
    const output = searchResultsToJson(results, { query: "keynote" });
    const parsed = JSON.parse(output);
    expect(parsed[0].context).toBeUndefined();
  });

  test("files format does not include trailing context when null", () => {
    const output = searchResultsToFiles(results);
    // Should just be docid,score,path - no trailing comma/context
    expect(output).not.toContain(",\"");
  });
});

// =============================================================================
// Multi-Get Documents: Context in Every Format
// =============================================================================

describe("multi-get documents include context in all formats", () => {
  const docs = [makeMultiGetFile()];

  test("JSON format includes context", () => {
    const output = documentsToJson(docs);
    const parsed = JSON.parse(output);
    expect(parsed[0].context).toBe(TEST_CONTEXT);
  });

  test("CSV format includes context", () => {
    const output = documentsToCsv(docs);
    const lines = output.split("\n");
    expect(lines[0]).toContain("context");
    expect(output).toContain(TEST_CONTEXT);
  });

  test("files format includes context", () => {
    const output = documentsToFiles(docs);
    expect(output).toContain(TEST_CONTEXT);
  });

  test("Markdown format includes context", () => {
    const output = documentsToMarkdown(docs);
    expect(output).toContain(TEST_CONTEXT);
  });

  test("XML format includes context", () => {
    const output = documentsToXml(docs);
    expect(output).toContain(TEST_CONTEXT);
  });

  test("formatDocuments (JSON) includes context", () => {
    const output = formatDocuments(docs, "json");
    const parsed = JSON.parse(output);
    expect(parsed[0].context).toBe(TEST_CONTEXT);
  });

  test("formatDocuments (md) includes context", () => {
    const output = formatDocuments(docs, "md");
    expect(output).toContain(TEST_CONTEXT);
  });

  test("formatDocuments (xml) includes context", () => {
    const output = formatDocuments(docs, "xml");
    expect(output).toContain(TEST_CONTEXT);
  });
});

// =============================================================================
// Single Document: Context in Every Format
// =============================================================================

describe("single document includes context in all formats", () => {
  const doc = makeDocumentResult();

  test("JSON format includes context", () => {
    const output = documentToJson(doc);
    const parsed = JSON.parse(output);
    expect(parsed.context).toBe(TEST_CONTEXT);
  });

  test("Markdown format includes context", () => {
    const output = documentToMarkdown(doc);
    expect(output).toContain(TEST_CONTEXT);
  });

  test("XML format includes context", () => {
    const output = documentToXml(doc);
    expect(output).toContain(TEST_CONTEXT);
  });

  test("formatDocument (JSON) includes context", () => {
    const output = formatDocument(doc, "json");
    const parsed = JSON.parse(output);
    expect(parsed.context).toBe(TEST_CONTEXT);
  });

  test("formatDocument (md) includes context", () => {
    const output = formatDocument(doc, "md");
    expect(output).toContain(TEST_CONTEXT);
  });

  test("formatDocument (xml) includes context", () => {
    const output = formatDocument(doc, "xml");
    expect(output).toContain(TEST_CONTEXT);
  });
});

// =============================================================================
// Single Document: No Context When Absent
// =============================================================================

describe("single document omits context when null", () => {
  const doc = makeDocumentResult({ context: null });

  test("JSON format omits context field when null", () => {
    const output = documentToJson(doc);
    const parsed = JSON.parse(output);
    expect(parsed.context).toBeUndefined();
  });

  test("Markdown format does not show Context line when null", () => {
    const output = documentToMarkdown(doc);
    expect(output).not.toContain("Context:");
  });

  test("XML format does not show context element when null", () => {
    const output = documentToXml(doc);
    expect(output).not.toContain("<context>");
  });
});
