import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:code_builder/dart/core.dart';
import 'package:code_builder/code_builder.dart';
import 'package:console/console.dart';
import 'package:inflection/inflection.dart';
import 'package:pubspec/pubspec.dart';
import 'package:recase/recase.dart';
import 'maker.dart';

class ModelCommand extends Command {
  final TextPen _pen = new TextPen();

  @override
  String get name => 'model';

  @override
  String get description => 'Generates a model class.';

  ModelCommand() {
    argParser
      ..addFlag('migration',
          abbr: 'm',
          help: 'Generate an angel_orm migration file.',
          negatable: false)
      ..addFlag('orm', help: 'Generate angel_orm code.', negatable: false)
      ..addFlag('serializable',
          help: 'Generate angel_serialize annotations.', defaultsTo: true)
      ..addOption('name',
          abbr: 'n', help: 'Specifies a name for the model class.')
      ..addOption('output-dir',
          help: 'Specifies a directory to create the model class in.',
          defaultsTo: 'lib/src/models')
      ..addOption('migration-dir',
          help: 'Specifies a directory to create the migration class in.',
          defaultsTo: 'tool/migrations');
  }

  @override
  run() async {
    var pubspec = await PubSpec.load(Directory.current);
    String name;
    if (argResults.wasParsed('name')) name = argResults['name'];

    if (name?.isNotEmpty != true) {
      var p = new Prompter('Name of Model class: ');
      name = await p.prompt(checker: (s) => s.isNotEmpty);
    }

    List<MakerDependency> deps = [
      const MakerDependency('angel_framework', '^1.0.0'),
      const MakerDependency('angel_model', '^1.0.0'),
    ];

    var rc = new ReCase(name);
    var modelLib =
        new LibraryBuilder('${pubspec.name}.src.models.${rc.snakeCase}');
    modelLib.addDirective(
        new ImportBuilder('package:angel_model/angel_model.dart'));

    var needsSerialize = argResults['serializable'] || argResults['orm'];

    if (needsSerialize) {
      modelLib.addDirective(
          new ImportBuilder('package:angel_serialize/angel_serialize.dart'));
      deps.add(const MakerDependency('angel_serialize', '^1.0.0-alpha'));
    }

    if (argResults['orm']) {
      modelLib
          .addDirective(new ImportBuilder('package:angel_orm/angel_orm.dart'));
      deps.add(const MakerDependency('angel_orm', '^1.0.0-alpha'));
    }

    var modelClazz = new ClassBuilder(
        needsSerialize ? '_${rc.pascalCase}' : rc.pascalCase,
        asExtends: new TypeBuilder('Model'));
    modelLib.addMember(modelClazz);

    if (needsSerialize) {
      modelLib.addDirective(new PartBuilder('${rc.snakeCase}.g.dart'));
      modelClazz.addAnnotation(reference('serializable'));
    }

    if (argResults['orm']) {
      modelClazz.addAnnotation(reference('orm'));
    }

    // Save model file
    var outputDir = new Directory.fromUri(
        Directory.current.uri.resolve(argResults['output-dir']));
    var modelFile =
        new File.fromUri(outputDir.uri.resolve('${rc.snakeCase}.dart'));
    if (!await modelFile.exists()) await modelFile.create(recursive: true);
    await modelFile.writeAsString(prettyToSource(modelLib.buildAst()));
    _pen
      ..green()
      ..call(
          '${Icon.CHECKMARK} Created model file "${modelFile.absolute.path}".')
      ..call()
      ..reset();

    if (argResults['migration']) {
      deps.add(
          const MakerDependency('angel_migration', '^1.0.0-alpha', dev: true));

      var migrationLib = new LibraryBuilder()
        ..addDirective(
            new ImportBuilder('package:angel_migration/angel_migration.dart'));
      var migrationClazz = new ClassBuilder('${rc.pascalCase}Migration',
          asExtends: new TypeBuilder('Migration'));
      migrationLib.addMember(migrationClazz);
      var tableName = pluralize(rc.snakeCase);

      // up()
      var up = new MethodBuilder('up', returnType: lib$core.$void);
      migrationClazz.addMethod(up);
      up.addAnnotation(lib$core.override);
      up.addPositional(parameter('schema', [new TypeBuilder('Schema')]));

      // (table) { ... }
      var callback = new MethodBuilder.closure();
      callback.addPositional(parameter('table'));

      var cascade = reference('table').cascade((table) => [
            table.invoke('serial', [literal('id')]).invoke('primaryKey', []),
            table.invoke('date', [literal('created_at')]),
            table.invoke('date', [literal('updated_at')])
          ]);
      callback.addStatement(cascade);

      up.addStatement(reference('schema').invoke('create', [callback]));

      // down()
      var down = new MethodBuilder('down', returnType: lib$core.$void);
      migrationClazz.addMethod(down);
      down.addAnnotation(lib$core.override);
      down.addPositional(parameter('schema', [new TypeBuilder('Schema')]));
      down.addStatement(
          reference('schema').invoke('drop', [literal(tableName)]));

      // Save migration file
      var migrationDir = new Directory.fromUri(
          Directory.current.uri.resolve(argResults['migration-dir']));
      var migrationFile =
          new File.fromUri(migrationDir.uri.resolve('${rc.snakeCase}.dart'));
      if (!await migrationFile.exists())
        await migrationFile.create(recursive: true);
      await migrationFile
          .writeAsString(prettyToSource(migrationLib.buildAst()));
    _pen
      ..green()
      ..call(
          '${Icon.CHECKMARK} Created migration file "${migrationFile.absolute.path}".')
      ..call()
      ..reset();
    }

    if (deps.isNotEmpty) await depend(deps);
  }
}