import { describe, it, expect } from "vitest";
import { CharStreams, CommonTokenStream } from "antlr4ts";
import { TSQLLexer } from "./TSQLLexer.js";
import { TSQLParser } from "./TSQLParser.js";

describe("TSQLParser", () => {
  function parse(input: string) {
    const inputStream = CharStreams.fromString(input);
    const lexer = new TSQLLexer(inputStream);
    const tokenStream = new CommonTokenStream(lexer);
    const parser = new TSQLParser(tokenStream);
    return parser;
  }

  describe("select statements", () => {
    it("should parse a simple SELECT statement", () => {
      const parser = parse("SELECT * FROM users;");
      const tree = parser.select();

      expect(tree).toBeDefined();
      // The select rule can return selectStmt, selectSetStmt, or tSQLxTagElement
      // Most SELECT statements are wrapped in selectSetStmt
      const selectSetStmt = tree.selectSetStmt();
      expect(selectSetStmt).toBeDefined();

      // Get the underlying selectStmt from selectSetStmt -> selectStmtWithParens
      const selectStmtWithParens = selectSetStmt!.selectStmtWithParens();
      const selectStmt = selectStmtWithParens.selectStmt();

      expect(selectStmt).toBeDefined();
      expect(selectStmt!.SELECT()).toBeDefined();
      expect(selectStmt!.columnExprList()).toBeDefined();
      expect(selectStmt!.fromClause()).toBeDefined();
    });

    it("should parse SELECT with WHERE clause", () => {
      const parser = parse("SELECT id, name FROM users WHERE id = 1;");
      const tree = parser.select();

      expect(tree).toBeDefined();
      const selectSetStmt = tree.selectSetStmt();
      const selectStmt = selectSetStmt!.selectStmtWithParens().selectStmt()!;

      expect(selectStmt.whereClause()).toBeDefined();
      expect(selectStmt.whereClause()!.WHERE()).toBeDefined();
      expect(selectStmt.whereClause()!.columnExpr()).toBeDefined();
    });

    it("should parse SELECT with multiple columns", () => {
      const parser = parse("SELECT id, name, email FROM users;");
      const tree = parser.select();

      expect(tree).toBeDefined();
      const selectSetStmt = tree.selectSetStmt();
      const selectStmt = selectSetStmt!.selectStmtWithParens().selectStmt()!;

      const columnList = selectStmt.columnExprList();
      expect(columnList).toBeDefined();
      // columnExprList contains comma-separated expressions
      expect(columnList.columnExpr().length).toBeGreaterThanOrEqual(1);
    });

    it("should parse SELECT with DISTINCT", () => {
      const parser = parse("SELECT DISTINCT id FROM users;");
      const tree = parser.select();

      expect(tree).toBeDefined();
      const selectSetStmt = tree.selectSetStmt();
      const selectStmt = selectSetStmt!.selectStmtWithParens().selectStmt()!;

      expect(selectStmt.DISTINCT()).toBeDefined();
    });
  });

  describe("expressions", () => {
    it("should parse a simple addition expression", () => {
      const parser = parse("1 + 2");
      const tree = parser.expr();

      expect(tree).toBeDefined();
      const columnExpr = tree.columnExpr();
      expect(columnExpr).toBeDefined();
      // Check that the expression has children (the operands and operator)
      expect(columnExpr.text).toBeDefined();
      const text = columnExpr.text;
      expect(text).toContain("1");
      expect(text).toContain("2");
      expect(text).toContain("+");
    });

    it("should parse arithmetic expressions with parentheses", () => {
      const parser = parse("(1 + 2) * 3");
      const tree = parser.expr();

      expect(tree).toBeDefined();
      const columnExpr = tree.columnExpr();
      expect(columnExpr).toBeDefined();
      // The expression should contain the operators
      const text = columnExpr.text;
      expect(text).toContain("+");
      expect(text).toContain("*");
      expect(text).toContain("1");
      expect(text).toContain("2");
      expect(text).toContain("3");
    });

    it("should parse string literals", () => {
      const parser = parse("'hello world'");
      const tree = parser.expr();

      expect(tree).toBeDefined();
      const columnExpr = tree.columnExpr();
      expect(columnExpr).toBeDefined();
      const text = columnExpr.text;
      expect(text).toContain("hello");
      expect(text).toContain("world");
    });

    it("should parse numeric literals", () => {
      const parser = parse("42");
      const tree = parser.expr();

      expect(tree).toBeDefined();
      const columnExpr = tree.columnExpr();
      expect(columnExpr).toBeDefined();
      expect(columnExpr.text).toContain("42");
    });
  });

  describe("program", () => {
    it("should parse an empty program", () => {
      const parser = parse("");
      const tree = parser.program();

      expect(tree).toBeDefined();
      expect(tree.EOF()).toBeDefined();
      expect(tree.declaration().length).toBe(0);
    });

    it("should parse variable declarations", () => {
      const parser = parse("let x := 1");
      const tree = parser.program();

      expect(tree).toBeDefined();
      expect(tree.declaration().length).toBe(1);
      const declaration = tree.declaration(0);

      // Check if it's a varDecl
      const varDecl = declaration.varDecl();
      expect(varDecl).toBeDefined();
      expect(varDecl!.LET()).toBeDefined();
      expect(varDecl!.identifier()).toBeDefined();
      expect(varDecl!.expression()).toBeDefined();
    });

    it("should parse multiple declarations", () => {
      const parser = parse("let x := 1; let y := 2");
      const tree = parser.program();

      expect(tree).toBeDefined();
      // Count only non-empty declarations (varDecl or non-empty statements)
      const varDecls = tree.declaration().filter((d) => d.varDecl() !== undefined);
      expect(varDecls.length).toBe(2);
    });
  });
});
