import 'package:drift/drift.dart';

import 'enums.dart';
import 'tables.dart';

part 'database.g.dart';

/// The encrypted local inventory database (SQLCipher via an injected executor).
///
/// The executor is passed in so production wires SQLCipher while tests can use
/// an in-memory database — the schema and queries are identical either way.
@DriftDatabase(
  tables: [
    Varieties,
    VarietyVernacularNames,
    Species,
    SpeciesCommonNames,
    Lots,
    GerminationTests,
    ConditionChecks,
    Movements,
    Parties,
    Attachments,
    ExternalLinks,
    Plantares,
  ],
)
class AppDatabase extends _$AppDatabase {
  AppDatabase(super.e);

  /// Current schema version; also stamped into interchange exports so an
  /// importer knows which app generation wrote the file (data-model §7).
  static const int currentSchemaVersion = 9;

  @override
  int get schemaVersion => currentSchemaVersion;

  @override
  MigrationStrategy get migration => MigrationStrategy(
    onCreate: (m) async => m.createAll(),
    onUpgrade: (m, from, to) async {
      // v2: lots can hold seeds or living plant material.
      if (from < 2) {
        await m.addColumn(lots, lots.type);
      }
      // v3: optional harvest month alongside the harvest year.
      if (from < 3) {
        await m.addColumn(lots, lots.harvestMonth);
      }
      // v4: optional packaging/supply form for living lots.
      if (from < 4) {
        await m.addColumn(lots, lots.presentation);
      }
      // v5: display order for attachments (cover photo = lowest sortOrder).
      if (from < 5) {
        await m.addColumn(attachments, attachments.sortOrder);
      }
      // v6: photo-first draft varieties awaiting a name ("to catalogue" tray).
      if (from < 6) {
        await m.addColumn(varieties, varieties.isDraft);
      }
      // v7: organic ("eco") self-declaration on varieties; per-species seed
      // viability (years) reference data for expiry warnings. Guarded by a
      // column-existence check so a dev database left half-migrated (column
      // added but user_version not bumped) re-runs cleanly instead of failing
      // on "duplicate column".
      if (from < 7) {
        if (!await _hasColumn('varieties', 'is_organic')) {
          await m.addColumn(varieties, varieties.isOrganic);
        }
        if (!await _hasColumn('species', 'viability_years')) {
          await m.addColumn(species, species.viabilityYears);
        }
      }
      // v8: absorbs fields from a real seed-bank inventory. Varieties gain a
      // "needs reproducing" intent and an advisory crop calendar; Lots gain
      // provenance (origin name/place), a qualitative abundance level and a
      // preservation format; a new ConditionChecks table logs container count +
      // drying-agent state. Every step is guarded so a half-migrated dev
      // database re-runs cleanly (see the v7 note above).
      if (from < 8) {
        Future<void> addIfMissing(
          String table,
          String column,
          GeneratedColumn<Object> definition,
          TableInfo<Table, dynamic> tableInfo,
        ) async {
          if (!await _hasColumn(table, column)) {
            await m.addColumn(tableInfo, definition);
          }
        }

        await addIfMissing(
          'varieties',
          'needs_reproduction',
          varieties.needsReproduction,
          varieties,
        );
        await addIfMissing(
          'varieties',
          'sow_months',
          varieties.sowMonths,
          varieties,
        );
        await addIfMissing(
          'varieties',
          'transplant_months',
          varieties.transplantMonths,
          varieties,
        );
        await addIfMissing(
          'varieties',
          'flowering_months',
          varieties.floweringMonths,
          varieties,
        );
        await addIfMissing(
          'varieties',
          'fruiting_months',
          varieties.fruitingMonths,
          varieties,
        );
        await addIfMissing(
          'varieties',
          'seed_harvest_months',
          varieties.seedHarvestMonths,
          varieties,
        );
        await addIfMissing('lots', 'origin_name', lots.originName, lots);
        await addIfMissing('lots', 'origin_place', lots.originPlace, lots);
        await addIfMissing('lots', 'abundance', lots.abundance, lots);
        await addIfMissing(
          'lots',
          'preservation_format',
          lots.preservationFormat,
          lots,
        );
        if (!await _hasTable('condition_checks')) {
          await m.createTable(conditionChecks);
        }
      }
      // v9: Plantares — reproduction commitments (data-model §2.7). Guarded so a
      // half-migrated dev database re-runs cleanly (see the v7 note above).
      if (from < 9) {
        if (!await _hasTable('plantares')) {
          await m.createTable(plantares);
        }
      }
    },
  );

  /// Whether a table named [table] already exists. Keeps the additive v8
  /// table creation idempotent against partially-migrated databases.
  Future<bool> _hasTable(String table) async {
    final rows = await customSelect(
      "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
      variables: [Variable.withString(table)],
    ).get();
    return rows.isNotEmpty;
  }

  /// Whether [table] already has a column named [column]. Used to keep additive
  /// migrations idempotent against partially-migrated databases.
  Future<bool> _hasColumn(String table, String column) async {
    final rows = await customSelect(
      'SELECT 1 FROM pragma_table_info(?) WHERE name = ?',
      variables: [Variable.withString(table), Variable.withString(column)],
    ).get();
    return rows.isNotEmpty;
  }
}
