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

class StoreSeedConfig {
  final String dbPath;
  final String dataOutputPath;
  final String metaOutputPath;
  final String version;

  StoreSeedConfig({
    required this.dbPath,
    required this.dataOutputPath,
    required this.metaOutputPath,
    required this.version,
  });

  factory StoreSeedConfig.fromArgs(List<String> args) {
    String dbPath = 'doc/restaurant_data/store.db';
    String dataOutputPath = 'assets/data/store_seed.json';
    String metaOutputPath = 'assets/data/store_seed.meta.json';
    String version = DateTime.now().toUtc().toIso8601String();

    for (final arg in args) {
      if (arg.startsWith('--db=')) {
        dbPath = arg.substring('--db='.length);
      } else if (arg.startsWith('--data=')) {
        dataOutputPath = arg.substring('--data='.length);
      } else if (arg.startsWith('--meta=')) {
        metaOutputPath = arg.substring('--meta='.length);
      } else if (arg.startsWith('--version=')) {
        version = arg.substring('--version='.length);
      }
    }

    return StoreSeedConfig(
      dbPath: dbPath,
      dataOutputPath: dataOutputPath,
      metaOutputPath: metaOutputPath,
      version: version,
    );
  }
}

class StoreSeedRow {
  final int storeId;
  final String province;
  final String district;
  final String name;
  final String title;
  final String address;
  final String roadAddress;
  final double latitude;
  final double longitude;

  StoreSeedRow({
    required this.storeId,
    required this.province,
    required this.district,
    required this.name,
    required this.title,
    required this.address,
    required this.roadAddress,
    required this.latitude,
    required this.longitude,
  });

  factory StoreSeedRow.fromMap(Map<String, dynamic> map) {
    return StoreSeedRow(
      storeId: map['id'] as int,
      province: (map['province'] as String).trim(),
      district: (map['district'] as String).trim(),
      name: (map['name'] as String).trim(),
      title: (map['title'] as String).trim(),
      address: (map['address'] as String).trim(),
      roadAddress: (map['road_address'] as String).trim(),
      latitude: (map['latitude'] as num).toDouble(),
      longitude: (map['longitude'] as num).toDouble(),
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'storeId': storeId,
      'province': province,
      'district': district,
      'name': name,
      'title': title,
      'address': address,
      'roadAddress': roadAddress,
      'latitude': latitude,
      'longitude': longitude,
    };
  }
}

class StoreSeedMeta {
  final String version;
  final String generatedAt;
  final String sourceDb;
  final int itemCount;
  final Map<String, dynamic> sourceSignature;

  StoreSeedMeta({
    required this.version,
    required this.generatedAt,
    required this.sourceDb,
    required this.itemCount,
    required this.sourceSignature,
  });

  Map<String, dynamic> toJson() {
    return {
      'version': version,
      'generatedAt': generatedAt,
      'sourceDb': sourceDb,
      'itemCount': itemCount,
      'sourceSignature': sourceSignature,
    };
  }
}

Future<void> main(List<String> args) async {
  final config = StoreSeedConfig.fromArgs(args);

  final dbFile = File(config.dbPath);
  if (!dbFile.existsSync()) {
    stderr.writeln('DB 파일을 찾을 수 없습니다: ${config.dbPath}');
    exit(1);
  }

  final sqlitePath = await _findSqliteBinary();
  if (sqlitePath == null) {
    stderr.writeln('sqlite3 바이너리를 찾을 수 없습니다. 시스템에 설치되어 있는지 확인하세요.');
    exit(1);
  }

  final rows = await _fetchRows(sqlitePath, dbFile.path);
  if (rows.isEmpty) {
    stderr.writeln('restaurants 테이블에서 가져온 행이 없습니다.');
  }

  final seeds = rows.map(StoreSeedRow.fromMap).toList();
  final sourceBytes = await dbFile.readAsBytes();
  final sourceSignature = _buildSignature(sourceBytes);
  await _writeJson(
    config.dataOutputPath,
    seeds.map((e) => e.toJson()).toList(),
  );

  final generatedAt = DateTime.now().toUtc().toIso8601String();
  final meta = StoreSeedMeta(
    version: config.version.isNotEmpty ? config.version : sourceSignature,
    generatedAt: generatedAt,
    sourceDb: dbFile.path,
    itemCount: seeds.length,
    sourceSignature: {
      'hash': sourceSignature,
      'size': sourceBytes.length,
      'modifiedMs': dbFile.lastModifiedSync().millisecondsSinceEpoch,
    },
  );
  await _writeJson(config.metaOutputPath, meta.toJson());

  stdout.writeln(
    '변환 완료: ${seeds.length}개 항목 → '
    '${config.dataOutputPath} / ${config.metaOutputPath}',
  );
}

Future<String?> _findSqliteBinary() async {
  try {
    final result = await Process.run('which', ['sqlite3']);
    if (result.exitCode == 0) {
      final path = (result.stdout as String).trim();
      if (path.isNotEmpty) {
        return path;
      }
    }
  } catch (_) {
    return null;
  }
  return null;
}

Future<List<Map<String, dynamic>>> _fetchRows(
  String sqlitePath,
  String dbPath,
) async {
  const query =
      'SELECT id, province, district, name, title, address, road_address, '
      'latitude, longitude FROM restaurants';

  final result = await Process.run(
    sqlitePath,
    ['-json', dbPath, query],
    stdoutEncoding: utf8,
    stderrEncoding: utf8,
  );

  if (result.exitCode != 0) {
    stderr.writeln('sqlite3 실행 실패: ${result.stderr}');
    exit(result.exitCode);
  }

  final output = result.stdout as String;
  final decoded = jsonDecode(output);
  if (decoded is! List) {
    stderr.writeln('예상치 못한 JSON 포맷입니다: ${decoded.runtimeType}');
    exit(1);
  }

  return decoded.cast<Map<String, dynamic>>();
}

Future<void> _writeJson(String path, Object data) async {
  final file = File(path);
  await file.parent.create(recursive: true);
  final encoder = const JsonEncoder.withIndent('  ');
  final content = encoder.convert(data);
  await file.writeAsString('$content\n');
}

String _buildSignature(List<int> bytes) {
  int hash = 0;
  for (final byte in bytes) {
    hash = (hash * 31 + byte) & 0x7fffffff;
  }
  return hash.toRadixString(16).padLeft(8, '0');
}
