// @ts-ignore
import { registerSubmitReportTool, additionalRecommendationSchema } from "./submitReportTool.js";
import { TestType } from "../types/TestTypes.js";
import * as fs from "fs/promises";
import * as path from "path";
import * as os from "os";
jest.mock("../utils/logger.js", () => ({
    logger: { info: jest.fn(), error: jest.fn() },
}));
jest.mock("../services/AnalyticsService.js", () => ({
    AnalyticsService: { pushMCPToolEvent: jest.fn().mockResolvedValue(undefined) },
}));
function captureToolHandler() {
    let handler;
    const fakeServer = {
        registerTool: (_name, _opts, fn) => {
            handler = fn;
        },
    };
    registerSubmitReportTool(fakeServer);
    return handler;
}
function sampleReportParams(outputFile) {
    return {
        summaryOutputFile: outputFile,
        businessCaseAnalysis: "This PR adds product search. Tests needed for filtering.",
        newTestsCreated: [
            { testId: "smoke-get-products-search", testType: TestType.SMOKE, category: "workflow", endpoint: "GET /api/v1/products/search", fileName: "search_smoke_test.py", reasoning: "Validates product search returns correct results for category filter" },
        ],
        testMaintenance: [{ action: "UPDATE", testType: TestType.CONTRACT, endpoint: "GET /api/v1/products", fileName: "products_contract_test.py", description: "Updated auth flow in existing tests", beforeStatus: "Fail", beforeDetails: "401 Unauthorized (1.5s)", afterStatus: "Pass", afterDetails: "All assertions passed (2.3s)" }],
        testResults: [
            { testType: TestType.SMOKE, endpoint: "GET /api/v1/products", status: "Pass", details: "2.1s, products_smoke_test.py" },
            { testType: TestType.SMOKE, endpoint: "GET /api/v1/products/search", status: "Fail", details: "3.4s, search_smoke_test.py" },
        ],
        issuesFound: [{ description: "Search endpoint returns 500 with category filter" }],
    };
}
describe("registerSubmitReportTool", () => {
    let handler;
    let tmpDirs = [];
    beforeAll(() => {
        handler = captureToolHandler();
    });
    afterEach(async () => {
        for (const dir of tmpDirs) {
            await fs.rm(dir, { recursive: true, force: true });
        }
        tmpDirs = [];
    });
    it("should register the tool on the server with nextSteps in schema", () => {
        const registerToolMock = jest.fn();
        const fakeServer = { registerTool: registerToolMock };
        registerSubmitReportTool(fakeServer);
        expect(registerToolMock).toHaveBeenCalledWith("skyramp_submit_report", expect.objectContaining({
            description: expect.any(String),
            inputSchema: expect.objectContaining({
                nextSteps: expect.any(Object),
            }),
        }), expect.any(Function));
    });
    it("should write JSON report to the specified file", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler(sampleReportParams(outputFile));
        expect(result.isError).toBeUndefined();
        expect(result.content[0].text).toContain("Report submitted successfully");
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.businessCaseAnalysis).toBe("This PR adds product search. Tests needed for filtering.");
        expect(written.newTestsCreated).toHaveLength(1);
        expect(written.testResults).toHaveLength(2);
        expect(written.issuesFound).toHaveLength(1);
        expect(written.testMaintenance).toHaveLength(1);
    });
    it("should create parent directories if they don't exist", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "nested", "dirs", "report.json");
        const result = await handler(sampleReportParams(outputFile));
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.businessCaseAnalysis).toBeDefined();
    });
    it("should handle empty arrays for optional sections", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler({
            summaryOutputFile: outputFile,
            businessCaseAnalysis: "Simple config change.",
            newTestsCreated: [],
            testMaintenance: [],
            testResults: [
                { testType: TestType.SMOKE, endpoint: "GET /api/v1/products", status: "Pass", details: "1.0s, test.py" },
            ],
            issuesFound: [],
        });
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.newTestsCreated).toEqual([]);
        expect(written.testMaintenance).toEqual([]);
        expect(written.issuesFound).toEqual([]);
        expect(written.testResults).toHaveLength(1);
    });
    it("should return error for invalid file path", async () => {
        const result = await handler(sampleReportParams("/nonexistent/readonly/path/report.json"));
        expect(result.isError).toBe(true);
        expect(result.content[0].text).toContain("Failed to write report");
    });
    it("should write commitMessage when provided", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler({
            ...sampleReportParams(outputFile),
            commitMessage: "add contract tests for /products endpoint",
        });
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.commitMessage).toBe("add contract tests for /products endpoint");
    });
    it("should use default commitMessage when omitted", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler(sampleReportParams(outputFile));
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.commitMessage).toBe("Added recommendations by Skyramp Testbot.");
    });
    it("should sanitize commitMessage (collapse newlines, trim whitespace)", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler({
            ...sampleReportParams(outputFile),
            commitMessage: "  line one\nline two\r\nline three  ",
        });
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.commitMessage).toBe("line one line two line three");
        // No length cap — truncation is the consumer's responsibility (SKYR-3757).
    });
    it("should preserve commitMessage longer than 72 chars without truncation", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const longMessage = "add contract and integration tests for the new PATCH /orders/{order_id} endpoint including discount recalculation and line-item edits";
        const result = await handler({
            ...sampleReportParams(outputFile),
            commitMessage: longMessage,
        });
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.commitMessage).toBe(longMessage);
        expect(written.commitMessage.length).toBeGreaterThan(72);
    });
    it("should use default commitMessage when provided as empty string", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler({
            ...sampleReportParams(outputFile),
            commitMessage: "",
        });
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.commitMessage).toBe("Added recommendations by Skyramp Testbot.");
    });
    it("should use default commitMessage when whitespace-only", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler({
            ...sampleReportParams(outputFile),
            commitMessage: "   \n\r\n   ",
        });
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.commitMessage).toBe("Added recommendations by Skyramp Testbot.");
    });
    it("includes nextSteps array in written JSON", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler({
            ...sampleReportParams(outputFile),
            nextSteps: ["Check your targetSetupCommand — endpoints returned 404"],
        });
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.nextSteps).toEqual([
            "Check your targetSetupCommand — endpoints returned 404",
        ]);
    });
    it("defaults nextSteps to empty array when omitted", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const result = await handler(sampleReportParams(outputFile));
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.nextSteps).toEqual([]);
    });
    it("should produce valid JSON with pretty formatting", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        await handler(sampleReportParams(outputFile));
        const raw = await fs.readFile(outputFile, "utf-8");
        // Pretty-printed JSON should have indentation
        expect(raw).toContain("  ");
        // Should not contain summaryOutputFile in the output
        expect(raw).not.toContain("summaryOutputFile");
    });
    it("additionalRecommendations serialises primaryEndpoint into the report JSON", async () => {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "submit-report-test-"));
        tmpDirs.push(tmpDir);
        const outputFile = path.join(tmpDir, "report.json");
        const params = {
            ...sampleReportParams(outputFile),
            additionalRecommendations: [
                {
                    testId: "integration-orders-patch-status",
                    testType: TestType.INTEGRATION,
                    category: "business_rule",
                    primaryEndpoint: "PATCH /api/v1/orders/{order_id}",
                    scenarioName: "orders-patch-status-transition",
                    steps: [{ method: "PATCH", path: "/api/v1/orders/1", description: "Patch order status" }],
                    description: "Verifies status transitions persist across PATCH calls",
                    priority: "high",
                    reasoning: "Catches broken state machine transitions on the new PATCH endpoint",
                },
            ],
        };
        const result = await handler(params);
        expect(result.isError).toBeUndefined();
        const written = JSON.parse(await fs.readFile(outputFile, "utf-8"));
        expect(written.additionalRecommendations[0].primaryEndpoint).toBe("PATCH /api/v1/orders/{order_id}");
    });
    it("additionalRecommendationSchema rejects a contract test missing primaryEndpoint", () => {
        const result = additionalRecommendationSchema.safeParse({
            testId: "contract-orders-patch-404",
            testType: TestType.CONTRACT,
            category: "error_handling",
            // primaryEndpoint intentionally omitted — required for contract tests
            scenarioName: "orders-patch-not-found",
            steps: [{ method: "PATCH", path: "/api/v1/orders/99999", description: "Patch non-existent order" }],
            description: "Verifies 404 for unknown order_id",
            priority: "high",
            reasoning: "Validates the 404 guard on the new PATCH endpoint",
        });
        expect(result.success).toBe(false);
        const issue = result.error?.issues.find(i => i.path.includes("primaryEndpoint"));
        expect(issue).toBeDefined();
    });
    it("additionalRecommendationSchema accepts a contract test with primaryEndpoint", () => {
        const result = additionalRecommendationSchema.safeParse({
            testId: "contract-orders-patch-404",
            testType: TestType.CONTRACT,
            category: "error_handling",
            primaryEndpoint: "PATCH /api/v1/orders/{order_id}",
            scenarioName: "orders-patch-not-found",
            steps: [{ method: "PATCH", path: "/api/v1/orders/99999", description: "Patch non-existent order" }],
            description: "Verifies 404 for unknown order_id",
            priority: "high",
            reasoning: "Validates the 404 guard on the new PATCH endpoint",
        });
        expect(result.success).toBe(true);
    });
    it("additionalRecommendationSchema accepts a multi-step integration test without primaryEndpoint", () => {
        // For multi-step scenarios, primaryEndpoint is optional — steps is the authoritative source
        const result = additionalRecommendationSchema.safeParse({
            testId: "integration-orders-patch-mutation",
            testType: TestType.INTEGRATION,
            category: "business_rule",
            // primaryEndpoint intentionally omitted for multi-step scenario
            scenarioName: "orders-patch-mutation-verify",
            steps: [
                { method: "POST", path: "/api/v1/products", description: "Create product" },
                { method: "POST", path: "/api/v1/orders", description: "Create order" },
                { method: "PATCH", path: "/api/v1/orders/1", description: "Patch order" },
            ],
            description: "Verifies total_amount recalculates after PATCH",
            priority: "high",
            reasoning: "Catches item accumulation bug",
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.primaryEndpoint).toBeUndefined();
        }
    });
});
