import 'package:sqlparser/sqlparser.dart';
import 'package:sqlparser/src/utils/ast_equality.dart';
import 'package:sqlparser/utils/node_to_text.dart';
import 'package:test/test.dart';

enum _ParseKind { statement, moorFile }

void main() {
  final engine = SqlEngine(EngineOptions(useMoorExtensions: true));

  void testFormat(String input, {_ParseKind kind = _ParseKind.statement}) {
    AstNode parse(String input) {
      ParseResult result;

      switch (kind) {
        case _ParseKind.statement:
          result = engine.parse(input);
          break;
        case _ParseKind.moorFile:
          result = engine.parseMoorFile(input);
          break;
      }

      if (result.errors.isNotEmpty) {
        fail('Error parsing $input: ${result.errors.join('\n')}');
      }

      return result.rootNode;
    }

    final originalAst = parse(input);
    final formatted = originalAst.toSql();
    final newAst = parse(formatted);

    try {
      enforceEqual(originalAst, newAst);
    } catch (e) {
      fail('Not equal after formatting: $input to $formatted: $e');
    }
  }

  group('create', () {
    group('trigger', () {
      test('before delete', () {
        testFormat('''
CREATE TRIGGER IF NOT EXISTS my_trigger BEFORE DELETE ON t1 BEGIN
  SELECT * FROM t2;
END;
      ''');
      });

      test('instead of insert', () {
        testFormat('''
CREATE TRIGGER IF NOT EXISTS my_trigger INSTEAD OF INSERT ON t1 BEGIN
  SELECT * FROM t2;
END;
      ''');
      });

      test('after update', () {
        testFormat('''
CREATE TRIGGER IF NOT EXISTS my_trigger AFTER UPDATE ON t1 BEGIN
  SELECT * FROM t2;
END;
      ''');
      });

      test('after update of when', () {
        testFormat('''
CREATE TRIGGER IF NOT EXISTS my_trigger AFTER UPDATE OF c1, c2 ON t1 
  WHEN foo = bar
BEGIN
  SELECT * FROM t2;
END;
      ''');
      });
    });

    test('view', () {
      testFormat('''
CREATE VIEW my_view (foo, bar) AS SELECT * FROM t1;
      ''');

      testFormat('''
CREATE VIEW my_view AS SELECT * FROM t1;
      ''');
    });

    test('table', () {
      testFormat('''
CREATE TABLE IF NOT EXISTS my_table(
  foo TEXT NOT NULL PRIMARY KEY DEFAULT (3 * 4),
  baz INT CONSTRAINT not_null NOT NULL PRIMARY KEY AUTOINCREMENT,
  bar TEXT
    UNIQUE ON CONFLICT IGNORE
    CHECK (3 * 4 = 12)
    DEFAULT 'some string here'
    COLLATE c
    REFERENCES t2 (c) ON DELETE RESTRICT ON UPDATE NO ACTION,

  PRIMARY KEY (foo, bar) ON CONFLICT ABORT,
  UNIQUE (baz) ON CONFLICT REPLACE,
  CONSTRAINT my_constraint CHECK(baz < 3),
  FOREIGN KEY (foo, baz) REFERENCES t2 ON DELETE SET NULL ON UPDATE CASCADE
    DEFERRABLE INITIALLY IMMEDIATE,
  FOREIGN KEY (bar) REFERENCES t2 (bax) ON DELETE SET DEFAULT NOT DEFERRABLE
);
      ''');

      testFormat('''
CREATE TABLE IF NOT EXISTS my_table(
  foo INTEGER NOT NULL PRIMARY KEY ASC
) WITHOUT ROWID;
      ''');
    });

    test('virtual table', () {
      testFormat('CREATE VIRTUAL TABLE foo USING bar(a, b, c);');
    });

    test('index', () {
      testFormat('''
CREATE INDEX my_idx ON t1 (c1, c2, c3);
      ''');

      testFormat('''
CREATE UNIQUE INDEX my_idx ON t1 (c1, c2, c3) WHERE c1 < c3;
      ''');
    });
  });

  group('escapes identifiers', () {
    test("when they're keywords", () {
      testFormat('SELECT * FROM "create";');
    });

    test('when they contain whitespace', () {
      testFormat('SELECT * FROM "my fancy table"');
    });
  });

  group('query statements', () {
    group('select', () {
      test('with common table expressions', () {
        testFormat('''
          WITH RECURSIVE foo (id) AS (VALUES(1) UNION ALL SELECT id + 1 FROM foo)
            SELECT * FROM foo;
        ''');
      });

      test('compound', () {
        testFormat('''
        SELECT * FROM foo
          UNION ALL SELECT * FROM bar
          UNION SELECT * FROM baz
          INTERSECT SELECT * FROM bar2
          EXCEPT SELECT * FROM bar3
          LIMIT 5;
        ''');
      });

      test('values', () {
        testFormat('VALUES (1,2,3), (4,5,6);');
      });

      test('group by', () {
        testFormat('''
          SELECT * FROM foo
            GROUP BY a, b, c HAVING COUNT(id) > 10
        ''');
      });

      test('with windows', () {
        testFormat('''
          SELECT * FROM foo
            WINDOW my_window AS (
              PARTITION BY bar GROUPS
              BETWEEN 3 PRECEDING AND CURRENT ROW
              EXCLUDE NO OTHERS
            ),
            other AS (my_window
              ORDER BY foo DESC, bar ASC NULLS FIRST
              RANGE UNBOUNDED PRECEDING EXCLUDE CURRENT ROW
            ),
            yet_another AS (other RANGE CURRENT ROW EXCLUDE GROUP),
            finally AS (other
              RANGE BETWEEN CURRENT ROW AND 4 FOLLOWING
              EXCLUDE TIES
            )
        ''');
      });

      test('joins', () {
        testFormat('''
          SELECT * FROM
            foo AS f,
            bar
            NATURAL INNER JOIN j1 USING (foo)
            LEFT JOIN j2 ON j2.id = bar.c
            LEFT OUTER JOIN j3 ON j3.id = bar.c
            CROSS JOIN j4 ON j4.a = j3.b
            INNER JOIN (SELECT * FROM bar) AS b
            INNER JOIN table_valued_function(foo)
        ''');
      });

      test('limit', () {
        testFormat('SELECT * FROM foo LIMIT 3, 4');
        testFormat('SELECT * FROM foo LIMIT 4 OFFSET 3');
      });

      test('order by', () {
        testFormat('SELECT foo.* FROM foo ORDER BY foo NULLS FIRST');
      });
    });

    test('delete', () {
      testFormat(
          'WITH foo (id) AS (SELECT * FROM bar) DELETE FROM bar WHERE x;');
    });

    group('insert', () {
      test('replace', () {
        testFormat('WITH foo (id) AS (SELECT * FROM bar) '
            'REPLACE INTO foo DEFAULT VALUES');
      });

      test('insert into select', () {
        testFormat('INSERT INTO foo SELECT * FROM bar');
      });

      test('upsert - do nothing', () {
        testFormat(
            'INSERT OR REPLACE INTO foo DEFAULT VALUES ON CONFLICT DO NOTHING');
      });

      test('upsert - update', () {
        testFormat('INSERT INTO foo VALUES (1, 2, 3) '
            'ON CONFLICT DO UPDATE SET a = b, c = d WHERE d < a;');
      });
    });
  });

  group('expressions', () {
    test('between', () {
      testFormat('SELECT x BETWEEN a AND b');
      testFormat('SELECT x NOT BETWEEN a AND b');
    });

    test('binary', () {
      testFormat('SELECT x OR y');
      testFormat('SELECT x AND y');
    });

    test('boolean literals', () {
      testFormat('SELECT TRUE OR FALSE');
    });

    test('case', () {
      testFormat('SELECT CASE WHEN a THEN b ELSE C END');
      testFormat('SELECT CASE x WHEN a THEN b WHEN c THEN d END');
    });

    test('cast', () {
      testFormat('SELECT CAST(X AS INTEGER)');
    });

    test('collate', () {
      testFormat('SELECT x COLLATE y AS xc');
    });

    test('exists', () {
      testFormat('SELECT x FROM foo WHERE EXISTS (SELECT * FROM bar)');
    });

    test('function', () {
      testFormat('SELECT my_function(*) FROM foo');
      testFormat('SELECT my_function(DISTINCT a, b) FROM foo');
      testFormat('SELECT my_function(a, b) FROM foo');
    });

    test('is', () {
      testFormat('SELECT foo IS bar');
      testFormat('SELECT foo IS NOT bar');
    });

    test('is null', () {
      testFormat('SELECT foo ISNULL');
      testFormat('SELECT foo NOTNULL');
    });

    test('null literal', () {
      testFormat('SELECT NULL;');
    });

    test('parentheses', () {
      testFormat('SELECT (3 + 4) * 5');
    });

    test('reference with table', () {
      testFormat('SELECT foo.bar FROM foo');
    });

    test('string comparison', () {
      testFormat('SELECT x LIKE y FROM foo');
    });

    test('time literals', () {
      testFormat('SELECT CURRENT_TIME;');
      testFormat('SELECT CURRENT_DATE;');
      testFormat('SELECT CURRENT_TIMESTAMP;');
    });

    test('unary expression', () {
      testFormat('SELECT -(+(~3));');
    });
  });

  group('moor', () {
    test('dart placeholders', () {
      testFormat(r'SELECT $placeholder FROM foo');
    });

    test('imports', () {
      testFormat('import \'foo.bar\';', kind: _ParseKind.moorFile);
    });

    test('declared statements', () {
      testFormat('foo (?1 AS INT): SELECT * FROM bar WHERE ? < 10;',
          kind: _ParseKind.moorFile);
      testFormat('foo: SELECT * FROM bar WHERE :id < 10;',
          kind: _ParseKind.moorFile);
      testFormat(r'foo ($pred = FALSE): SELECT * FROM bar WHERE $pred;',
          kind: _ParseKind.moorFile);
    });

    test('nested star', () {
      testFormat('q: SELECT foo.** FROM foo;', kind: _ParseKind.moorFile);
    });
  });

  test('does not format invalid statements', () {
    expect(InvalidStatement().toSql, throwsUnsupportedError);
  });
}
