import 'dart:convert';
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:tane/data/species_catalog.dart';

/// Guards the committed, generated catalog asset (produced by
/// `tool/gen_species_catalog.dart`). Reads the file directly — no Flutter asset
/// bundle needed — so it runs as a plain VM test.
void main() {
  // `flutter test` runs with the package root as the working directory.
  final file = File('assets/catalog/species.json');
  final json = jsonDecode(file.readAsStringSync()) as Map<String, dynamic>;
  final species = (json['species'] as List).cast<Map<String, dynamic>>();

  test('is the current (version 3) generated catalog', () {
    expect(json['version'], 3);
  });

  test('speciesCatalogVersion mirrors the asset version', () {
    // The startup seed guard skips the reseed by comparing this constant to the
    // version it last recorded; it must track the asset or a bumped catalog is
    // never re-seeded.
    expect(
      speciesCatalogVersion,
      parseSpeciesCatalogVersion(file.readAsStringSync()),
    );
  });

  test('is a broad catalog, not the old starter set', () {
    // The expanded catalog holds ~1200 species (was 14 by hand). The floor
    // leaves headroom for Wikidata drift across regenerations.
    expect(species.length, greaterThan(900));
  });

  test('every entry has a scientific name and at least one localized name', () {
    for (final e in species) {
      final sci = e['scientific_name'];
      expect(sci, isA<String>());
      expect((sci as String).trim(), isNotEmpty);

      final common = e['common'] as Map<String, dynamic>;
      expect(common, isNotEmpty, reason: '$sci has no common names');
      for (final entry in common.entries) {
        final names = (entry.value as List).cast<String>();
        expect(names, isNotEmpty);
        expect(names.every((n) => n.trim().isNotEmpty), isTrue);
      }
    }
  });

  test('identifiers and reference fields are well typed', () {
    for (final e in species) {
      expect(e['wikidata_qid'], matches(r'^Q\d+$'));
      if (e.containsKey('gbif_key')) expect(e['gbif_key'], isA<int>());
      if (e.containsKey('viability_years')) {
        expect(e['viability_years'], isA<int>());
      }
    }
  });

  test('scientific names are unique', () {
    final names = species.map((e) => e['scientific_name'] as String).toList();
    expect(names.length, names.toSet().length);
  });

  test('is genuinely multilingual (non-Latin scripts present)', () {
    int withLang(String code) =>
        species.where((e) => (e['common'] as Map).containsKey(code)).length;
    // International by design: Arabic (RTL) and CJK must be represented, not
    // an es/en-only catalog.
    expect(withLang('ar'), greaterThan(0), reason: 'no Arabic names');
    expect(withLang('zh'), greaterThan(0), reason: 'no Chinese names');
    expect(withLang('ja'), greaterThan(0), reason: 'no Japanese names');
  });

  test('keeps the curated core crops with their authoritative names', () {
    final byName = {for (final e in species) e['scientific_name'] as String: e};
    final tomato = byName['Solanum lycopersicum'];
    final carrot = byName['Daucus carota'];
    expect(tomato, isNotNull);
    expect(carrot, isNotNull);
    // Curated names lead (not Wikidata's messier taxon labels) and viability
    // reference data survives the regeneration.
    expect((tomato!['common'] as Map)['es'], contains('Tomate'));
    expect((carrot!['common'] as Map)['es'], contains('Zanahoria'));
    expect(carrot['viability_years'], 3);
  });

  test('parses through the app loader without error', () {
    final seeds = parseSpeciesCatalog(file.readAsStringSync());
    expect(seeds.length, species.length);
  });
}
