/**
 * Unit tests for cli/configLoader.js
 * Tests parseConfig, matchesGlob, and resolveFailOn independently of the CLI process.
 */

import { parseConfig, matchesGlob, resolveFailOn } from "../../../cli/configLoader.js";

// ---------------------------------------------------------------------------
// parseConfig
// ---------------------------------------------------------------------------

describe("parseConfig", () => {
    describe("valid configs", () => {
        test("accepts empty overrides array", () => {
            const config = parseConfig('{"overrides":[]}');
            expect(config.overrides).toEqual([]);
        });

        test("accepts config with one override", () => {
            const json = JSON.stringify({
                overrides: [{ paths: ["src/legacy/**"], failOn: "none" }]
            });
            const config = parseConfig(json);
            expect(config.overrides[0].failOn).toBe("none");
        });

        test("accepts config with multiple overrides", () => {
            const json = JSON.stringify({
                overrides: [
                    { paths: ["src/legacy/**"], failOn: "none" },
                    { paths: ["src/new/**"],    failOn: "warning" }
                ]
            });
            const config = parseConfig(json);
            expect(config.overrides).toHaveLength(2);
        });

        test("accepts all valid failOn values", () => {
            for (const failOn of ["error", "warning", "information", "hint", "none"]) {
                const json = JSON.stringify({ overrides: [{ paths: ["**"], failOn }] });
                expect(() => parseConfig(json)).not.toThrow();
            }
        });

        test("accepts override with multiple paths", () => {
            const json = JSON.stringify({
                overrides: [{ paths: ["src/old/**", "src/legacy/**"], failOn: "none" }]
            });
            const config = parseConfig(json);
            expect(config.overrides[0].paths).toHaveLength(2);
        });

        test("accepts naming section with charToTypeMap and scopeToCharMap", () => {
            const json = JSON.stringify({
                naming: {
                    charToTypeMap:  { z: "STRING", s: "SHORT" },
                    scopeToCharMap: { global: "g", parameter: "p" }
                }
            });
            const config = parseConfig(json);
            expect(config.naming.charToTypeMap.z).toBe("STRING");
            expect(config.naming.scopeToCharMap.global).toBe("g");
        });

        test("accepts naming section without overrides", () => {
            const json = JSON.stringify({
                naming: { charToTypeMap: { z: "STRING" }, scopeToCharMap: { global: "g" } }
            });
            const config = parseConfig(json);
            expect(config.naming).toBeDefined();
            expect(config.overrides).toBeUndefined();
        });

        test("accepts combined naming and overrides", () => {
            const json = JSON.stringify({
                naming: { charToTypeMap: { z: "STRING" }, scopeToCharMap: { global: "g" } },
                overrides: [{ paths: ["legacy/**"], failOn: "none" }]
            });
            const config = parseConfig(json);
            expect(config.naming).toBeDefined();
            expect(config.overrides).toHaveLength(1);
        });
    });

    describe("malformed JSON", () => {
        test("throws on invalid JSON", () => {
            expect(() => parseConfig("not json")).toThrow(/Invalid JSON/);
        });

        test("throws when root is an array", () => {
            expect(() => parseConfig("[]")).toThrow(/root must be a JSON object/);
        });

        test("throws when root is a string", () => {
            expect(() => parseConfig('"hello"')).toThrow(/root must be a JSON object/);
        });

        test("throws when root is null", () => {
            expect(() => parseConfig("null")).toThrow(/root must be a JSON object/);
        });
    });

    describe("invalid overrides", () => {
        test("throws when overrides is not an array", () => {
            expect(() => parseConfig('{"overrides":{}}')).toThrow(/"overrides" must be an array/);
        });

        test("throws when override is missing paths", () => {
            const json = JSON.stringify({ overrides: [{ failOn: "none" }] });
            expect(() => parseConfig(json)).toThrow(/paths must be an array/);
        });

        test("throws when override.paths is not an array", () => {
            const json = JSON.stringify({ overrides: [{ paths: "src/**", failOn: "none" }] });
            expect(() => parseConfig(json)).toThrow(/paths must be an array/);
        });

        test("throws when a path entry is not a string", () => {
            const json = JSON.stringify({ overrides: [{ paths: [123], failOn: "none" }] });
            expect(() => parseConfig(json)).toThrow(/must be a string/);
        });

        test("throws when override.failOn is an invalid value", () => {
            const json = JSON.stringify({ overrides: [{ paths: ["**"], failOn: "maybe" }] });
            expect(() => parseConfig(json)).toThrow(/failOn must be one of/);
        });

        test("throws when an override entry is not an object", () => {
            const json = JSON.stringify({ overrides: ["src/**"] });
            expect(() => parseConfig(json)).toThrow(/must be an object/);
        });
    });

    describe("invalid naming", () => {
        test("throws when naming is an array", () => {
            expect(() => parseConfig('{"naming":[]}')).toThrow(/"naming" must be an object/);
        });

        test("throws when naming is a string", () => {
            expect(() => parseConfig('{"naming":"x"}')).toThrow(/"naming" must be an object/);
        });

        test("throws when naming.charToTypeMap is an array", () => {
            const json = JSON.stringify({ naming: { charToTypeMap: [] } });
            expect(() => parseConfig(json)).toThrow(/"naming.charToTypeMap" must be an object/);
        });

        test("throws when naming.scopeToCharMap value is not a single char", () => {
            const json = JSON.stringify({ naming: { scopeToCharMap: { global: "global" } } });
            expect(() => parseConfig(json)).toThrow(/must be a single character string/);
        });
    });
});

// ---------------------------------------------------------------------------
// matchesGlob
// ---------------------------------------------------------------------------

describe("matchesGlob", () => {
    test("matches exact path", () => {
        expect(matchesGlob("src/reports/AC14.TXT", "src/reports/AC14.TXT")).toBe(true);
    });

    test("does not match a different exact path", () => {
        expect(matchesGlob("src/reports/AC14.TXT", "src/reports/AC15.TXT")).toBe(false);
    });

    test("** at end matches files directly in that directory", () => {
        expect(matchesGlob("src/legacy/**", "src/legacy/report.TXT")).toBe(true);
    });

    test("** at end matches files in nested subdirectories", () => {
        expect(matchesGlob("src/legacy/**", "src/legacy/sub/report.TXT")).toBe(true);
    });

    test("** at end does not match a sibling directory", () => {
        expect(matchesGlob("src/legacy/**", "src/new/report.TXT")).toBe(false);
    });

    test("** in middle matches zero segments", () => {
        // src/**/report.TXT should match src/report.TXT (zero intermediate segments)
        expect(matchesGlob("src/**/report.TXT", "src/report.TXT")).toBe(true);
    });

    test("** in middle matches one segment", () => {
        expect(matchesGlob("src/**/report.TXT", "src/a/report.TXT")).toBe(true);
    });

    test("** in middle matches multiple segments", () => {
        expect(matchesGlob("src/**/report.TXT", "src/a/b/report.TXT")).toBe(true);
    });

    test("** in middle does not match when filename differs", () => {
        expect(matchesGlob("src/**/report.TXT", "src/a/other.TXT")).toBe(false);
    });

    test("* within a segment matches any chars in that segment", () => {
        expect(matchesGlob("src/*.TXT", "src/AC14.TXT")).toBe(true);
    });

    test("* within a segment does not match a path separator", () => {
        expect(matchesGlob("src/*.TXT", "src/sub/AC14.TXT")).toBe(false);
    });

    test("normalises backslash separators", () => {
        expect(matchesGlob("src/legacy/**", "src\\legacy\\report.TXT")).toBe(true);
    });
});

// ---------------------------------------------------------------------------
// resolveFailOn
// ---------------------------------------------------------------------------

describe("resolveFailOn", () => {
    test("returns default when config is null", () => {
        expect(resolveFailOn(null, "src/file.TXT", "error")).toBe("error");
    });

    test("returns default when config has no overrides array", () => {
        expect(resolveFailOn({}, "src/file.TXT", "error")).toBe("error");
    });

    test("returns default when overrides array is empty", () => {
        expect(resolveFailOn({ overrides: [] }, "src/file.TXT", "error")).toBe("error");
    });

    test("returns default when no override matches the file", () => {
        const config = { overrides: [{ paths: ["src/legacy/**"], failOn: "none" }] };
        expect(resolveFailOn(config, "src/new/file.TXT", "error")).toBe("error");
    });

    test("returns override failOn when path matches", () => {
        const config = { overrides: [{ paths: ["src/legacy/**"], failOn: "none" }] };
        expect(resolveFailOn(config, "src/legacy/report.TXT", "error")).toBe("none");
    });

    test("first match wins when multiple overrides apply", () => {
        const config = {
            overrides: [
                { paths: ["src/**"],        failOn: "warning" },
                { paths: ["src/legacy/**"], failOn: "none" }
            ]
        };
        expect(resolveFailOn(config, "src/legacy/report.TXT", "error")).toBe("warning");
    });

    test("matches file listed in a multi-path override", () => {
        const config = {
            overrides: [{ paths: ["src/old/**", "src/legacy/**"], failOn: "none" }]
        };
        expect(resolveFailOn(config, "src/legacy/file.TXT", "error")).toBe("none");
    });

    test("normalises backslashes in relPath", () => {
        const config = { overrides: [{ paths: ["src/legacy/**"], failOn: "none" }] };
        expect(resolveFailOn(config, "src\\legacy\\report.TXT", "error")).toBe("none");
    });
});
