@Tags(['analyzer'])
library;

import 'dart:convert';

import 'package:drift/backends.dart';
import 'package:drift_dev/src/analysis/options.dart';
import 'package:drift_dev/src/analysis/results/file_results.dart';
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:drift_dev/src/services/schema/schema_files.dart';
import 'package:drift_dev/src/utils/string_escaper.dart';
import 'package:drift_dev/src/writer/database_writer.dart';
import 'package:drift_dev/src/writer/import_manager.dart';
import 'package:drift_dev/src/writer/writer.dart';
import 'package:test/test.dart';

import '../../analysis/test_utils.dart';

void main() {
  test('writer integration test', () async {
    const options = DriftOptions.defaults(
      dialect: DialectOptions(
        null,
        [SqlDialect.sqlite, SqlDialect.postgres],
        SqliteAnalysisOptions(
          modules: [SqlModule.fts5],
        ),
      ),
    );
    final state = await TestBackend.inTest(
      {
        'a|lib/a.drift': '''
import 'main.dart';

CREATE TABLE "groups" (
  id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,

  UNIQUE(name)
);

CREATE VIRTUAL TABLE email USING fts5(sender, title, body);

CREATE TABLE group_members (
  "group" INT NOT NULL REFERENCES "groups"(id),
  user INT NOT NULL REFERENCES users(id),
  is_admin BOOLEAN NOT NULL DEFAULT FALSE,

  PRIMARY KEY ("group", user) ON CONFLICT REPLACE
);

CREATE TRIGGER delete_empty_groups AFTER DELETE ON group_members BEGIN
  DELETE FROM "groups"
    WHERE NOT EXISTS (SELECT * FROM group_members WHERE "group" = "groups".id);
END;

CREATE INDEX groups_name ON "groups"(name, upper(name));

CREATE VIEW my_view WITH MyViewRow AS SELECT id FROM "groups";

CREATE TRIGGER my_view_trigger INSTEAD OF UPDATE ON my_view BEGIN
  UPDATE "groups" SET id = old.id;
END;

simple_query: SELECT * FROM my_view; -- not part of the schema
      ''',
        'a|lib/main.dart': '''
import 'package:drift/drift.dart';

class Users extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
  TextColumn get settings => text()
    .check(settings.length.isBiggerThanValue(10))
    .named('setting')
    .withDefault(Constant('foo' + 'bar'))
    .map(const SettingsConverter())();

  @override
  List<Set<Column>> get uniqueKeys => [{name, settings}];
}

class Settings {}

class SettingsConverter extends TypeConverter<Settings, String> {
  const SettingsConverter();

  String toSql(Settings s) => '';
  Settings fromSql(String db) => Settings();
}

class MyViewRow {
  final int id;
  MyViewRow(this.id);
}

@DriftDatabase(include: {'a.drift'}, tables: [Users])
class Database {}
      ''',
      },
      options: options,
    );

    final file = await state.analyze('package:a/main.dart');
    await state.analyze('package:a/a.drift');
    state.expectNoErrors();

    final db = file.fileAnalysis!.resolvedDatabases.values.single;

    final schemaJson =
        await SchemaWriter(db.availableElements, options: options)
            .createSchemaJson();

    expect(schemaJson, json.decode(expected));

    final schemaWithOptions = await SchemaWriter(
      db.availableElements,
      options: const DriftOptions.defaults(storeDateTimeValuesAsText: true),
    ).createSchemaJson();
    expect(
        schemaWithOptions['options'], {'store_date_time_values_as_text': true});
  });

  test('can generate code from schema json', () {
    final serializedSchema = json.decode(
            // Column types used to be serialized under a different format, test
            // reading that as well.
            expected.replaceAll('"int"', '"ColumnType.integer"'))
        as Map<String, dynamic>;
    final reader = SchemaReader.readJson(serializedSchema);

    final writer = Writer(
      const DriftOptions.defaults(),
      generationOptions: GenerationOptions(
        forSchema: 1,
        writeCompanions: true,
        writeDataClasses: true,
        imports: NullImportManager(),
      ),
    );

    final database = DriftDatabase(
      id: DriftElementId(SchemaReader.elementUri, 'database'),
      declaration: DriftDeclaration(SchemaReader.elementUri, 0, 'database'),
      declaredIncludes: const [],
      declaredQueries: const [],
      declaredTables: const [],
      declaredViews: const [],
      hasConstructorArgumentForConnection: false,
    );
    final resolved =
        ResolvedDatabaseAccessor(const {}, const [], reader.entities.toList());
    final input = DatabaseGenerationInput(database, resolved, const {}, null);

    DatabaseWriter(input, writer.child()).write();
    final generated = writer.writeGenerated();
    expect(generated,
        contains('ComparableExpr(settings.length).isBiggerThanValue(10)'));
  });

  test('can export Dart-defined views', () async {
    final backend = await TestBackend.inTest({
      'a|lib/main.dart': '''
import 'package:drift/drift.dart';

class MyTable extends Table {
  IntColumn get id => integer()();
}

class MyView extends View {
  MyTable get a;
  MyTable get b;
  MyTable get c;

  @override
  Query as() => select([
    a.id,
    b.id,
    c.id,
  ]).from(a).join([
    innerJoin(b, b.id.equalsExp(a.id)),
    innerJoin(c, c.id.equalsExp(a.id)),
  ]);
}

@DriftDatabase(tables: [MyTable], views: [MyView])
class Database {}
''',
    });

    final file = await backend.analyze('package:a/main.dart');
    backend.expectNoErrors();

    final db = file.fileAnalysis!.resolvedDatabases.values.single;

    final schemaJson =
        await SchemaWriter(db.availableElements).createSchemaJson();
    final serializedView = (schemaJson['entities'] as List)[1];

    expect(serializedView['data'], {
      'name': 'my_view',
      'sql':
          'CREATE VIEW IF NOT EXISTS "my_view" ("id", "id1", "id2") AS SELECT "t0"."id" AS "id", "t1"."id" AS "id1", "t2"."id" AS "id2" FROM "my_table" "t0" INNER JOIN "my_table" "t1" ON "t1"."id" = "t0"."id" INNER JOIN "my_table" "t2" ON "t2"."id" = "t0"."id"',
      'dart_info_name': r'$MyViewView',
      'columns': anything,
    });
  });

  group('generates correct datetime mode', () {
    Future<void> runTest(bool storeAsText, String expectedDefault) async {
      final options =
          DriftOptions.defaults(storeDateTimeValuesAsText: storeAsText);
      final backend = await TestBackend.inTest(
        {
          'a|lib/main.dart': '''
import 'package:drift/drift.dart';

class MyTable extends Table {
  DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}

@DriftDatabase(tables: [MyTable])
class Database {}
''',
        },
        options: options,
      );

      final file = await backend.analyze('package:a/main.dart');
      backend.expectNoErrors();

      final db = file.fileAnalysis!.resolvedDatabases.values.single;

      final schemaJson =
          await SchemaWriter(db.availableElements, options: options)
              .createSchemaJson();
      final serializedTable = (schemaJson['entities'] as List)[0];
      final data = serializedTable['data'];
      expect(data, {
        'name': 'my_table',
        'was_declared_in_moor': false,
        'is_virtual': false,
        'without_rowid': false,
        'columns': hasLength(1),
        'constraints': [],
      });

      expect(
        data['columns'][0]['default_dart'],
        'const CustomExpression(${asDartLiteral(expectedDefault)})',
      );
    }

    test('with integer times', () async {
      await runTest(
          false, "CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)");
    });

    test('with text times', () async {
      await runTest(true, 'CURRENT_TIMESTAMP');
    });
  });
}

const expected = r'''
{
    "_meta": {
        "description": "This file contains a serialized version of schema entities for drift.",
        "version": "1.2.0"
    },
    "options": {
        "store_date_time_values_as_text": false
    },
    "entities": [
        {
            "id": 0,
            "references": [],
            "type": "table",
            "data": {
                "name": "groups",
                "was_declared_in_moor": true,
                "columns": [
                    {
                        "name": "id",
                        "getter_name": "id",
                        "moor_type": "int",
                        "nullable": false,
                        "customConstraints": "NOT NULL PRIMARY KEY AUTOINCREMENT",
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": [
                            "auto-increment"
                        ]
                    },
                    {
                        "name": "name",
                        "getter_name": "name",
                        "moor_type": "string",
                        "nullable": false,
                        "customConstraints": "NOT NULL",
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": []
                    }
                ],
                "is_virtual": false,
                "without_rowid": false,
                "constraints": [
                    "UNIQUE(name)"
                ],
                "unique_keys": [
                    [
                        "name"
                    ]
                ]
            }
        },
        {
            "id": 1,
            "references": [],
            "type": "table",
            "data": {
                "name": "email",
                "was_declared_in_moor": true,
                "columns": [
                    {
                        "name": "sender",
                        "getter_name": "sender",
                        "moor_type": "string",
                        "nullable": false,
                        "customConstraints": "",
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": []
                    },
                    {
                        "name": "title",
                        "getter_name": "title",
                        "moor_type": "string",
                        "nullable": false,
                        "customConstraints": "",
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": []
                    },
                    {
                        "name": "body",
                        "getter_name": "body",
                        "moor_type": "string",
                        "nullable": false,
                        "customConstraints": "",
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": []
                    }
                ],
                "is_virtual": true,
                "create_virtual_stmt": "CREATE VIRTUAL TABLE \"email\" USING fts5(sender, title, body)",
                "without_rowid": false,
                "constraints": []
            }
        },
        {
            "id": 2,
            "references": [],
            "type": "table",
            "data": {
                "name": "users",
                "was_declared_in_moor": false,
                "columns": [
                    {
                        "name": "id",
                        "getter_name": "id",
                        "moor_type": "int",
                        "nullable": false,
                        "customConstraints": null,
                        "defaultConstraints": "PRIMARY KEY AUTOINCREMENT",
                        "dialectAwareDefaultConstraints": {
                          "sqlite": "PRIMARY KEY AUTOINCREMENT",
                          "postgres": "PRIMARY KEY AUTOINCREMENT"
                        },
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": [
                            "auto-increment"
                        ]
                    },
                    {
                        "name": "name",
                        "getter_name": "name",
                        "moor_type": "string",
                        "nullable": false,
                        "customConstraints": null,
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": []
                    },
                    {
                        "name": "setting",
                        "getter_name": "settings",
                        "moor_type": "string",
                        "nullable": false,
                        "customConstraints": null,
                        "default_dart": "const CustomExpression('\\'foobar\\'')",
                        "default_client_dart": null,
                        "dsl_features": [
                          {
                            "check": {
                              "dart_expression": {
                                "elements": [
                                  {
                                    "lexeme": "ComparableExpr",
                                    "import_uri": "package:drift/src/runtime/query_builder/query_builder.dart"
                                  },
                                  "(",
                                  {"lexeme": "settings", "tag": "settings"},
                                  ".length).isBiggerThanValue(10)"
                                ]
                              }
                            }
                          }
                        ],
                        "type_converter": {
                            "dart_expr": "const SettingsConverter()",
                            "dart_type_name": "Settings"
                        }
                    }
                ],
                "is_virtual": false,
                "without_rowid": false,
                "constraints": [],
                "unique_keys": [
                    [
                        "name",
                        "setting"
                    ]
                ]
            }
        },
        {
            "id": 3,
            "references": [
                0,
                2
            ],
            "type": "table",
            "data": {
                "name": "group_members",
                "was_declared_in_moor": true,
                "columns": [
                    {
                        "name": "group",
                        "getter_name": "group",
                        "moor_type": "int",
                        "nullable": false,
                        "customConstraints": "NOT NULL REFERENCES \"groups\"(id)",
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": [
                            "unknown"
                        ]
                    },
                    {
                        "name": "user",
                        "getter_name": "user",
                        "moor_type": "int",
                        "nullable": false,
                        "customConstraints": "NOT NULL REFERENCES users(id)",
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": [
                            "unknown"
                        ]
                    },
                    {
                        "name": "is_admin",
                        "getter_name": "isAdmin",
                        "moor_type": "bool",
                        "nullable": false,
                        "customConstraints": "NOT NULL DEFAULT FALSE",
                        "default_dart": "const CustomExpression('FALSE')",
                        "default_client_dart": null,
                        "dsl_features": []
                    }
                ],
                "is_virtual": false,
                "without_rowid": false,
                "constraints": [
                    "PRIMARY KEY(\"group\", user)ON CONFLICT REPLACE"
                ],
                "explicit_pk": [
                    "group",
                    "user"
                ]
            }
        },
        {
            "id": 4,
            "references": [
                3,
                0
            ],
            "type": "trigger",
            "data": {
                "on": 3,
                "references_in_body": [
                    3,
                    0
                ],
                "name": "delete_empty_groups",
                "sql": "CREATE TRIGGER delete_empty_groups AFTER DELETE ON group_members BEGIN\n  DELETE FROM \"groups\"\n    WHERE NOT EXISTS (SELECT * FROM group_members WHERE \"group\" = \"groups\".id);\nEND;"
            }
        },
        {
            "id": 5,
            "references": [
                0
            ],
            "type": "index",
            "data": {
                "on": 0,
                "name": "groups_name",
                "sql": "CREATE INDEX groups_name ON \"groups\"(name, upper(name));",
                "unique": false,
                "columns": []
            }
        },
        {
            "id": 6,
            "references": [
                0
            ],
            "type": "view",
            "data": {
                "name": "my_view",
                "sql": "CREATE VIEW my_view AS SELECT id FROM \"groups\";",
                "dart_info_name": "MyView",
                "columns": [
                    {
                        "name": "id",
                        "getter_name": "id",
                        "moor_type": "int",
                        "nullable": false,
                        "customConstraints": null,
                        "default_dart": null,
                        "default_client_dart": null,
                        "dsl_features": []
                    }
                ]
            }
        },
        {
            "id": 7,
            "references": [6, 0],
            "type": "trigger",
            "data": {
                "on": 6,
                "references_in_body": [6, 0],
                "name": "my_view_trigger",
                "sql": "CREATE TRIGGER my_view_trigger INSTEAD OF UPDATE ON my_view BEGIN\n  UPDATE \"groups\" SET id = old.id;\nEND;"
            }
        }
    ]
}
''';
