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

import 'package:build/build.dart';
import 'package:path/path.dart' as p;

class StoreSeedBuilder implements Builder {
  StoreSeedBuilder();

  @override
  final Map<String, List<String>> buildExtensions = const {
    'doc/restaurant_data/store.db': [
      'assets/data/store_seed.json',
      'assets/data/store_seed.meta.json',
    ],
  };

  @override
  Future<void> build(BuildStep buildStep) async {
    final inputId = buildStep.inputId;
    final bytes = await buildStep.readAsBytes(inputId);
    if (bytes.isEmpty) {
      log.warning('store.db가 비어 있습니다. 시드를 건너뜁니다.');
      return;
    }

    final tempDir = await Directory.systemTemp.createTemp('store_seed_');
    final tempDbPath = p.join(tempDir.path, 'store.db');
    await File(tempDbPath).writeAsBytes(bytes, flush: true);

    final sqlitePath = await _findSqliteBinary();
    if (sqlitePath == null) {
      log.severe('sqlite3 바이너리를 찾을 수 없습니다. 설치 후 다시 시도하세요.');
      return;
    }

    final rows = await _fetchRows(sqlitePath, tempDbPath);
    if (rows.isEmpty) {
      log.warning('restaurants 테이블에서 가져온 행이 없습니다.');
      await tempDir.delete(recursive: true);
      return;
    }

    final newSeeds = rows.map(_seedFromMap).toList();
    final merged = await _mergeWithExisting(buildStep, newSeeds);

    final signature = _buildSignature(bytes);
    final generatedAt = DateTime.now().toUtc().toIso8601String();
    final meta = {
      'version': signature,
      'generatedAt': generatedAt,
      'sourceDb': inputId.path,
      'itemCount': merged.length,
      'sourceSignature': {'hash': signature, 'size': bytes.length},
    };

    final encoder = const JsonEncoder.withIndent('  ');
    await buildStep.writeAsString(
      AssetId(inputId.package, 'assets/data/store_seed.json'),
      '${encoder.convert(merged)}\n',
    );
    await buildStep.writeAsString(
      AssetId(inputId.package, 'assets/data/store_seed.meta.json'),
      '${encoder.convert(meta)}\n',
    );

    await tempDir.delete(recursive: true);
    log.info(
      'store_seed 생성 완료: ${merged.length}개 (sig: $signature, src: ${inputId.path})',
    );
  }

  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) {
      throw StateError('sqlite3 실행 실패: ${result.stderr}');
    }

    final output = result.stdout as String;
    final decoded = jsonDecode(output);
    if (decoded is! List) {
      throw const FormatException('예상치 못한 JSON 포맷입니다.');
    }

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

  Map<String, dynamic> _seedFromMap(Map<String, dynamic> map) {
    return {
      '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(),
    };
  }

  Future<List<Map<String, dynamic>>> _mergeWithExisting(
    BuildStep buildStep,
    List<Map<String, dynamic>> newSeeds,
  ) async {
    final existingId = AssetId(
      buildStep.inputId.package,
      'assets/data/store_seed.json',
    );

    List<Map<String, dynamic>> existing = [];
    if (await buildStep.canRead(existingId)) {
      final raw = await buildStep.readAsString(existingId);
      try {
        final decoded = jsonDecode(raw);
        if (decoded is List) {
          existing = decoded.cast<Map<String, dynamic>>();
        }
      } catch (_) {
        log.warning('기존 store_seed.json 파싱 실패, 신규 데이터로 대체합니다.');
      }
    }

    final byId = <String, Map<String, dynamic>>{};
    for (final seed in existing) {
      final id = _seedId(seed);
      byId[id] = seed;
    }

    for (final seed in newSeeds) {
      final id = _seedId(seed);
      if (!byId.containsKey(id)) {
        byId[id] = seed;
        continue;
      }

      final isDuplicateByNameAndAddress = byId.values.any((existingSeed) {
        return existingSeed['name'] == seed['name'] &&
            existingSeed['roadAddress'] == seed['roadAddress'];
      });

      if (!isDuplicateByNameAndAddress) {
        byId[id] = seed; // 같은 ID는 최신 값으로 교체
      }
    }

    final merged = byId.values.toList()
      ..sort((a, b) => (_seedId(a)).compareTo(_seedId(b)));
    return merged;
  }

  String _seedId(Map<String, dynamic> seed) => 'store-${seed['storeId']}';

  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;
  }

  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');
  }
}

Builder storeSeedBuilder(BuilderOptions options) => StoreSeedBuilder();
// ignore_for_file: depend_on_referenced_packages
