import { describe, expect, test } from "bun:test";

import type { AskResult, SearchResult } from "../../src/pipeline/types";

import { formatAsk } from "../../src/cli/commands/ask";

// Helper to create a minimal search result
function createSearchResult(
  overrides: Partial<SearchResult> = {}
): SearchResult {
  return {
    docid: "#a1b2c3d4",
    score: 0.85,
    uri: "gno://work/doc.md",
    title: "Test Document",
    snippet: "This is test content for the document.",
    snippetRange: { startLine: 1, endLine: 10 },
    source: {
      relPath: "doc.md",
      mime: "text/markdown",
      ext: ".md",
    },
    ...overrides,
  };
}

// Helper to create an ask result
function createAskResult(overrides: Partial<AskResult> = {}): AskResult {
  return {
    query: "test question",
    mode: "hybrid",
    queryLanguage: "en",
    results: [createSearchResult()],
    meta: {
      expanded: true,
      reranked: true,
      vectorsUsed: true,
      answerGenerated: false,
      totalResults: 1,
    },
    ...overrides,
  };
}

describe("ask command", () => {
  describe("formatAsk", () => {
    test("formats JSON output with all fields", () => {
      const result = { success: true as const, data: createAskResult() };
      const output = formatAsk(result, { json: true });

      const parsed = JSON.parse(output);
      expect(parsed.query).toBe("test question");
      expect(parsed.mode).toBe("hybrid");
      expect(parsed.results).toHaveLength(1);
      expect(parsed.meta.expanded).toBe(true);
    });

    test("formats JSON with query mode summary when present", () => {
      const result = {
        success: true as const,
        data: createAskResult({
          meta: {
            expanded: true,
            reranked: true,
            vectorsUsed: true,
            answerGenerated: false,
            totalResults: 1,
            queryModes: {
              term: 1,
              intent: 1,
              hyde: false,
            },
          },
        }),
      };

      const output = formatAsk(result, { json: true });
      const parsed = JSON.parse(output);

      expect(parsed.meta.queryModes).toEqual({
        term: 1,
        intent: 1,
        hyde: false,
      });
    });

    test("formats JSON with answer and citations", () => {
      const result = {
        success: true as const,
        data: createAskResult({
          answer: "This is the generated answer [1].",
          citations: [
            {
              docid: "#a1b2c3d4",
              uri: "gno://work/doc.md",
              startLine: 1,
              endLine: 10,
            },
          ],
          meta: {
            expanded: true,
            reranked: true,
            vectorsUsed: true,
            answerGenerated: true,
            totalResults: 1,
            answerContext: {
              strategy: "adaptive_coverage_v1",
              targetSources: 2,
              facets: ["auth", "session"],
              selected: [
                {
                  docid: "#a1b2c3d4",
                  uri: "gno://work/doc.md",
                  score: 0.85,
                  queryTokenHits: 2,
                  facetHits: 1,
                  reason: "new_facet_coverage",
                },
              ],
              dropped: [],
            },
          },
        }),
      };
      const output = formatAsk(result, { json: true });

      const parsed = JSON.parse(output);
      expect(parsed.answer).toBe("This is the generated answer [1].");
      expect(parsed.citations).toHaveLength(1);
      expect(parsed.citations[0].docid).toBe("#a1b2c3d4");
      expect(parsed.meta.answerGenerated).toBe(true);
      expect(parsed.meta.answerContext?.strategy).toBe("adaptive_coverage_v1");
    });

    test("formats terminal output with sources", () => {
      const result = { success: true as const, data: createAskResult() };
      const output = formatAsk(result, { json: false, md: false });

      expect(output).toContain("Sources");
      expect(output).toContain("#a1b2c3d4");
      expect(output).toContain("gno://work/doc.md");
    });

    test("formats terminal output with answer when present", () => {
      const result = {
        success: true as const,
        data: createAskResult({
          answer: "Here is the answer to your question.",
        }),
      };
      const output = formatAsk(result, { json: false, md: false });

      expect(output).toContain("Answer");
      expect(output).toContain("Here is the answer to your question.");
    });

    test("formats markdown output correctly", () => {
      const result = { success: true as const, data: createAskResult() };
      const output = formatAsk(result, { md: true });

      expect(output).toContain("# ");
      expect(output).toContain("## Sources");
    });

    test("handles empty results gracefully", () => {
      const result = {
        success: true as const,
        data: createAskResult({ results: [] }),
      };

      const terminal = formatAsk(result, { json: false, md: false });
      expect(terminal).toContain("No relevant sources");

      const json = formatAsk(result, { json: true });
      const parsed = JSON.parse(json);
      expect(parsed.results).toHaveLength(0);
    });

    test("formats error correctly in JSON", () => {
      const result = { success: false as const, error: "Generation failed" };
      const output = formatAsk(result, { json: true });

      const parsed = JSON.parse(output);
      expect(parsed.error.code).toBe("ASK_FAILED");
      expect(parsed.error.message).toBe("Generation failed");
    });

    test("formats error correctly in terminal", () => {
      const result = { success: false as const, error: "Generation failed" };
      const output = formatAsk(result, { json: false, md: false });

      expect(output).toContain("Error:");
      expect(output).toContain("Generation failed");
    });
  });

  describe("answer generation requirements", () => {
    test("answer field is optional", () => {
      const result = createAskResult();
      expect(result.answer).toBeUndefined();
      expect(result.meta.answerGenerated).toBe(false);
    });

    test("citations align with context blocks", () => {
      const result = createAskResult({
        answer: "Answer referencing [1] and [2].",
        citations: [
          { docid: "#doc1", uri: "gno://work/a.md", startLine: 1, endLine: 5 },
          { docid: "#doc2", uri: "gno://work/b.md", startLine: 1, endLine: 5 },
        ],
      });

      // Citations array should match the numbered references
      expect(result.citations).toHaveLength(2);
      expect(result.citations?.[0]?.docid).toBe("#doc1");
      expect(result.citations?.[1]?.docid).toBe("#doc2");
    });
  });

  describe("context size bounds", () => {
    test("long snippets should be truncated in actual usage", () => {
      // This tests that the MAX_SNIPPET_CHARS constant is applied
      // The actual truncation happens in generateGroundedAnswer
      const longSnippet = "x".repeat(2000);
      const result = createSearchResult({ snippet: longSnippet });

      // The raw result can have a long snippet
      expect(result.snippet.length).toBe(2000);

      // When passed to generateGroundedAnswer, it will be truncated
      // This is tested implicitly by the implementation
    });

    test("empty snippets are skipped", () => {
      const result = createSearchResult({ snippet: "" });
      expect(result.snippet).toBe("");
    });
  });
});
