import 'dart:convert';

import 'package:flutter/services.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:lunchpick/core/constants/app_constants.dart';
import 'package:lunchpick/core/utils/app_logger.dart';
import 'package:lunchpick/domain/entities/restaurant.dart';

class StoreSeedMeta {
  final String version;
  final DateTime generatedAt;
  final int itemCount;
  final StoreSeedSourceSignature? sourceSignature;

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

  factory StoreSeedMeta.fromJson(Map<String, dynamic> json) {
    StoreSeedSourceSignature? signature;
    if (json['sourceSignature'] != null) {
      signature = StoreSeedSourceSignature.fromJson(
        json['sourceSignature'] as Map<String, dynamic>,
      );
    }

    return StoreSeedMeta(
      version: json['version'] as String,
      generatedAt: DateTime.parse(json['generatedAt'] as String),
      itemCount: json['itemCount'] as int,
      sourceSignature: signature,
    );
  }
}

class StoreSeedSourceSignature {
  final String hash;
  final int? size;

  StoreSeedSourceSignature({required this.hash, this.size});

  factory StoreSeedSourceSignature.fromJson(Map<String, dynamic> json) {
    return StoreSeedSourceSignature(
      hash: json['hash'] as String,
      size: (json['size'] as num?)?.toInt(),
    );
  }
}

class StoreSeedItem {
  final int storeId;
  final String name;
  final String title;
  final String address;
  final String roadAddress;
  final double latitude;
  final double longitude;

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

  factory StoreSeedItem.fromJson(Map<String, dynamic> json) {
    return StoreSeedItem(
      storeId: json['storeId'] as int,
      name: (json['name'] as String).trim(),
      title: (json['title'] as String).trim(),
      address: (json['address'] as String).trim(),
      roadAddress: (json['roadAddress'] as String).trim(),
      latitude: (json['latitude'] as num).toDouble(),
      longitude: (json['longitude'] as num).toDouble(),
    );
  }
}

class StoreDatasetSeeder {
  Future<void> seedIfNeeded() async {
    final restaurantBox = Hive.box<Restaurant>(AppConstants.restaurantBox);
    final settingsBox = Hive.box(AppConstants.settingsBox);

    final meta = await _loadMeta();
    if (meta == null) {
      return;
    }

    final currentVersion =
        settingsBox.get(AppConstants.storeSeedVersionKey) as String?;
    final shouldSeed = restaurantBox.isEmpty || currentVersion != meta.version;
    if (!shouldSeed) {
      return;
    }

    final seeds = await _loadSeedItems();
    if (seeds.isEmpty) {
      AppLogger.info('store_seed.json 데이터가 비어 있어 시드를 건너뜁니다.');
      return;
    }

    await _applySeeds(
      restaurantBox: restaurantBox,
      seeds: seeds,
      generatedAt: meta.generatedAt,
    );
    await settingsBox.put(AppConstants.storeSeedVersionKey, meta.version);

    AppLogger.info(
      '스토어 시드 적용 완료: version=${meta.version}, count=${meta.itemCount}',
    );
  }

  Future<StoreSeedMeta?> _loadMeta() async {
    try {
      final metaJson = await rootBundle.loadString(
        AppConstants.storeSeedMetaAsset,
      );
      final decoded = jsonDecode(metaJson) as Map<String, dynamic>;
      return StoreSeedMeta.fromJson(decoded);
    } catch (e, stack) {
      AppLogger.error(
        'store_seed.meta.json 로딩 실패',
        error: e,
        stackTrace: stack,
      );
      return null;
    }
  }

  Future<List<StoreSeedItem>> _loadSeedItems() async {
    try {
      final dataJson = await rootBundle.loadString(
        AppConstants.storeSeedDataAsset,
      );
      final decoded = jsonDecode(dataJson);
      if (decoded is! List) {
        throw const FormatException('store_seed.json 포맷이 배열이 아닙니다.');
      }
      return decoded
          .cast<Map<String, dynamic>>()
          .map(StoreSeedItem.fromJson)
          .toList();
    } catch (e, stack) {
      AppLogger.error('store_seed.json 로딩 실패', error: e, stackTrace: stack);
      return [];
    }
  }

  Future<void> _applySeeds({
    required Box<Restaurant> restaurantBox,
    required List<StoreSeedItem> seeds,
    required DateTime generatedAt,
  }) async {
    final seedMap = {for (final seed in seeds) _buildId(seed.storeId): seed};
    int added = 0;
    int updated = 0;

    for (final entry in seedMap.entries) {
      final id = entry.key;
      final seed = entry.value;
      final existing = restaurantBox.get(id);

      if (existing == null) {
        final restaurant = _buildRestaurant(seed, generatedAt);
        await restaurantBox.put(id, restaurant);
        added++;
        continue;
      }

      if (existing.source == DataSource.PRESET) {
        final description = _buildDescription(seed, existing.description);
        final restaurant = existing.copyWith(
          name: seed.name,
          category: existing.category.isNotEmpty ? existing.category : '기타',
          subCategory: existing.subCategory.isNotEmpty
              ? existing.subCategory
              : '기타',
          description: description,
          roadAddress: seed.roadAddress,
          jibunAddress: seed.address.isNotEmpty
              ? seed.address
              : seed.roadAddress,
          latitude: seed.latitude,
          longitude: seed.longitude,
          updatedAt: generatedAt,
        );
        await restaurantBox.put(id, restaurant);
        updated++;
      }
    }

    final unchanged = restaurantBox.length - added - updated;
    AppLogger.debug(
      '스토어 시드 결과 - 추가: $added, 업데이트: $updated, 기존 유지: '
      '$unchanged',
    );
  }

  Restaurant _buildRestaurant(StoreSeedItem seed, DateTime generatedAt) {
    return Restaurant(
      id: _buildId(seed.storeId),
      name: seed.name,
      category: '기타',
      subCategory: '기타',
      description: _buildDescription(seed, null),
      phoneNumber: null,
      roadAddress: seed.roadAddress,
      jibunAddress: seed.address.isNotEmpty ? seed.address : seed.roadAddress,
      latitude: seed.latitude,
      longitude: seed.longitude,
      lastVisitDate: null,
      source: DataSource.PRESET,
      createdAt: generatedAt,
      updatedAt: generatedAt,
      naverPlaceId: null,
      naverUrl: null,
      businessHours: null,
      lastVisited: null,
      visitCount: 0,
    );
  }

  String _buildId(int storeId) => 'store-$storeId';

  String? _buildDescription(StoreSeedItem seed, String? existingDescription) {
    if (existingDescription != null && existingDescription.isNotEmpty) {
      return existingDescription;
    }

    if (seed.title.isNotEmpty && seed.title != seed.name) {
      return seed.title;
    }

    return null;
  }
}
