import * as vscode from "../__mocks__/vscode.js";
import { KeywordCapitalizationCodeActionProvider } from "../../vscode-extension/src/codeActionProvider.js";

describe("CodeActionProvider - Keyword Capitalization", () => {
    let provider;
    let mockDocument;
    let mockDiagnostic;

    beforeEach(() => {
        provider = new KeywordCapitalizationCodeActionProvider();
    });

    describe("provideCodeActions", () => {
        it("should return empty array when no keyword capitalization diagnostics", () => {
            const context = {
                diagnostics: []
            };

            const actions = provider.provideCodeActions(mockDocument, null, context);
            expect(actions).toEqual([]);
        });

        it("should return a code action for keyword capitalization diagnostic", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "if x = 5"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-KWD-001",
                data: { ruleId: "keywordCapitalization" },
                message: "Keyword 'if' should be capitalized as 'IF'.",
                range: new vscode.Range(0, 0, 0, 2),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const context = {
                diagnostics: [mockDiagnostic]
            };

            const actions = provider.provideCodeActions(mockDocument, null, context);
            
            // The quick fix action + a suppression action for the suppressable diagnostic
            expect(actions).toHaveLength(2);
            expect(actions[0].title).toContain("Capitalize 'if' to 'IF'");
            expect(actions[0].kind).toBe(vscode.CodeActionKind.QuickFix);
        });

        it("should return a code action for type keyword capitalization diagnostic", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "VAR zMyString : string"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-TYPE-001",
                data: { ruleId: "typeKeywordCapitalization" },
                message: "Type keyword 'string' should be capitalized as 'STRING'.",
                range: new vscode.Range(0, 16, 0, 22),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const context = {
                diagnostics: [mockDiagnostic]
            };

            const actions = provider.provideCodeActions(mockDocument, null, context);
            
            // The quick fix action + a suppression action for the suppressable diagnostic
            expect(actions).toHaveLength(2);
            expect(actions[0].title).toContain("Capitalize 'string' to 'STRING'");
            expect(actions[0].kind).toBe(vscode.CodeActionKind.QuickFix);
        });

        it("should ignore diagnostics with other codes", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "undefined_var = 5"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-VAR-001",
                data: { ruleId: "undefinedVariable" },
                message: "undefined_var is not defined.",
                range: new vscode.Range(0, 0, 0, 12),
                severity: vscode.DiagnosticSeverity.Error
            };

            const context = {
                diagnostics: [mockDiagnostic]
            };

            const actions = provider.provideCodeActions(mockDocument, null, context);
            
            // No transformation quick fix, but a suppression action is still offered
            // for suppressable diagnostics even when no specific fix handler exists.
            expect(actions).toHaveLength(1);
            expect(actions[0].title).toContain("Suppress ACU-VAR-001");
        });
    });

    describe("createKeywordCapitalizationFix", () => {
        beforeEach(() => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "set variable = 10"
                }),
                uri: { fsPath: "test.txt" }
            };
        });

        it("should create a WorkspaceEdit with correct replacement", () => {
            mockDiagnostic = {
                code: "ACU-KWD-001",
                message: "Keyword 'set' should be capitalized as 'SET'.",
                range: new vscode.Range(0, 0, 0, 3),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createKeywordCapitalizationFix(mockDocument, mockDiagnostic);

            expect(action).not.toBeNull();
            expect(action.edit).toBeDefined();
            expect(action.diagnostics).toContain(mockDiagnostic);
        });

        it("should handle multiple-character keywords", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "enddo"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-KWD-001",
                message: "Keyword 'enddo' should be capitalized as 'ENDDO'.",
                range: new vscode.Range(0, 0, 0, 5),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createKeywordCapitalizationFix(mockDocument, mockDiagnostic);

            expect(action).not.toBeNull();
            expect(action.title).toContain("ENDDO");
        });

        it("should return null for malformed messages", () => {
            mockDiagnostic = {
                code: "ACU-KWD-001",
                message: "Malformed message with no keyword",
                range: new vscode.Range(0, 0, 0, 3)
            };

            const action = provider.createKeywordCapitalizationFix(mockDocument, mockDiagnostic);

            expect(action).toBeNull();
        });

        it("should return null when diagnostic position is stale (text doesn't match)", () => {
            // Simulate a scenario where the document has been edited since diagnostics were computed
            // For example, 'set' was already capitalized to 'SET', but diagnostic still points to old position
            mockDocument = {
                lineAt: (line) => ({
                    text: "SET variable = 10"  // Already capitalized
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-KWD-001",
                message: "Keyword 'set' should be capitalized as 'SET'.",
                range: new vscode.Range(0, 0, 0, 3),  // Points to 'SET' but expects 'set'
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createKeywordCapitalizationFix(mockDocument, mockDiagnostic);

            // Should return null because the text at the range ('SET') doesn't match expected ('set')
            expect(action).toBeNull();
        });
    });

    describe("createTypeKeywordCapitalizationFix", () => {
        beforeEach(() => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "VAR zMyString : string"
                }),
                uri: { fsPath: "test.txt" }
            };
        });

        it("should create a WorkspaceEdit with correct replacement for type keyword", () => {
            mockDiagnostic = {
                code: "ACU-TYPE-001",
                message: "Type keyword 'string' should be capitalized as 'STRING'.",
                range: new vscode.Range(0, 16, 0, 22),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createTypeKeywordCapitalizationFix(mockDocument, mockDiagnostic);

            expect(action).not.toBeNull();
            expect(action.edit).toBeDefined();
            expect(action.diagnostics).toContain(mockDiagnostic);
        });

        it("should handle multiple-character type keywords", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "FUNCTION test(): boolean"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-TYPE-001",
                message: "Type keyword 'boolean' should be capitalized as 'BOOLEAN'.",
                range: new vscode.Range(0, 17, 0, 24),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createTypeKeywordCapitalizationFix(mockDocument, mockDiagnostic);

            expect(action).not.toBeNull();
            expect(action.title).toContain("BOOLEAN");
        });

        it("should return null for malformed type messages", () => {
            mockDiagnostic = {
                code: "ACU-TYPE-001",
                message: "Malformed message with no type",
                range: new vscode.Range(0, 16, 0, 22)
            };

            const action = provider.createTypeKeywordCapitalizationFix(mockDocument, mockDiagnostic);

            expect(action).toBeNull();
        });

        it("should return null when diagnostic position is stale (text doesn't match)", () => {
            // Simulate a scenario where the document has been edited since diagnostics were computed
            // For example, 'string' was already capitalized to 'STRING', but diagnostic still points to old position
            mockDocument = {
                lineAt: (line) => ({
                    text: "VAR zMyString : STRING"  // Already capitalized
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-TYPE-001",
                message: "Type keyword 'string' should be capitalized as 'STRING'.",
                range: new vscode.Range(0, 16, 0, 22),  // Points to 'STRING' but expects 'string'
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createTypeKeywordCapitalizationFix(mockDocument, mockDiagnostic);

            // Should return null because the text at the range ('STRING') doesn't match expected ('string')
            expect(action).toBeNull();
        });
    });

    describe("createUnclosedStringFix", () => {
        it("should create a fix to close a double-quoted string", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: 'SET zName = "unclosed string'
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-VAR-003",
                message: "String is unclosed.",
                range: new vscode.Range(0, 12, 0, 29),
                severity: vscode.DiagnosticSeverity.Error
            };

            const action = provider.createUnclosedStringFix(mockDocument, mockDiagnostic);

            expect(action).not.toBeNull();
            expect(action.title).toContain("Add closing");
            expect(action.edit).toBeDefined();
        });

        it("should create a fix to close a single-quoted string", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "SET zName = 'unclosed string"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-VAR-003",
                message: "String is unclosed.",
                range: new vscode.Range(0, 12, 0, 28),
                severity: vscode.DiagnosticSeverity.Error
            };

            const action = provider.createUnclosedStringFix(mockDocument, mockDiagnostic);

            expect(action).not.toBeNull();
            expect(action.title).toContain("Add closing '");
        });
    });

    describe("createUnclosedInterpolationFix", () => {
        it("should create a fix to close unclosed interpolation", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "OUTPUT ^zName"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-INT-003",
                message: "Unclosed interpolation ^variable^",
                range: new vscode.Range(0, 7, 0, 8),
                severity: vscode.DiagnosticSeverity.Error
            };

            const action = provider.createUnclosedInterpolationFix(mockDocument, mockDiagnostic);

            expect(action).not.toBeNull();
            expect(action.title).toContain("Add closing ^");
            expect(action.edit).toBeDefined();
        });
    });

    describe("createRemoveDatabaseFieldPrefixFix", () => {
        it("should create a fix to remove h prefix from variable name", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "VAR hMyField : STRING"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-NAM-001",
                message: "ERROR: Declaring variables with an 'h' prefix is strongly discouraged...",
                range: new vscode.Range(0, 0, 0, 20),
                severity: vscode.DiagnosticSeverity.Error
            };

            const action = provider.createRemoveDatabaseFieldPrefixFix(mockDocument, mockDiagnostic);

            expect(action).not.toBeNull();
            expect(action.title).toContain("Rename 'hMyField' to 'MyField'");
            expect(action.edit).toBeDefined();
        });

        it("should return null if no VAR declaration found", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "SET x = 5"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-NAM-001",
                message: "ERROR: Declaring variables with an 'h' prefix is strongly discouraged...",
                range: new vscode.Range(0, 0, 0, 9),
                severity: vscode.DiagnosticSeverity.Error
            };

            const action = provider.createRemoveDatabaseFieldPrefixFix(mockDocument, mockDiagnostic);

            expect(action).toBeNull();
        });
    });

    describe("createVariableRenameFix", () => {
        it("should create a fix to rename variable with type prefix correction", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "VAR zCount : SHORT"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-NAM-003",
                message: "Naming convention (type): 'zCount' has type prefix suggesting INTEGER but is declared as SHORT. Consider 'sCount'.",
                range: new vscode.Range(0, 0, 0, 18),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createVariableRenameFix(mockDocument, mockDiagnostic, "namingPrefixType");

            expect(action).not.toBeNull();
            expect(action.title).toContain("Rename 'zCount' to 'sCount'");
        });

        it("should create a fix to rename variable with scope prefix correction", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "VAR gzName : STRING"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-NAM-002",
                message: "Naming convention (scope): 'gzName' has scope prefix 'g' (global) but is function-scoped. Consider 'fzName'.",
                range: new vscode.Range(0, 0, 0, 19),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createVariableRenameFix(mockDocument, mockDiagnostic, "namingPrefixScope");

            expect(action).not.toBeNull();
            expect(action.title).toContain("Rename 'gzName' to 'fzName'");
        });

        it("should return null if diagnostic message has no suggested name", () => {
            mockDocument = {
                lineAt: (line) => ({
                    text: "VAR zCount : SHORT"
                }),
                uri: { fsPath: "test.txt" }
            };

            mockDiagnostic = {
                code: "ACU-NAM-003",
                message: "Malformed message with no suggested name",
                range: new vscode.Range(0, 0, 0, 18),
                severity: vscode.DiagnosticSeverity.Warning
            };

            const action = provider.createVariableRenameFix(mockDocument, mockDiagnostic, "namingPrefixType");

            expect(action).toBeNull();
        });
    });

    describe("isInQuotedString", () => {
        it("should return true if position is inside double quotes", () => {
            const result = provider.isInQuotedString('SET zName = "hello');
            expect(result).toBe(true);
        });

        it("should return true if position is inside single quotes", () => {
            const result = provider.isInQuotedString("SET zName = 'hello");
            expect(result).toBe(true);
        });

        it("should return false if position is outside quotes", () => {
            const result = provider.isInQuotedString('SET zName = "hello" ');
            expect(result).toBe(false);
        });

        it("should handle mixed quotes correctly", () => {
            const result = provider.isInQuotedString('SET zName = "hello\' ');
            expect(result).toBe(true);
        });
    });
});
