import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';

class ArModelAssetException implements Exception {
  const ArModelAssetException(this.message);

  final String message;

  @override
  String toString() => message;
}

class ArModelAssetService {
  const ArModelAssetService();

  static const int _maxRecommendedBytes = 4 * 1024 * 1024;

  Future<String> prepareGlbForAr(String modelPath) async {
    debugPrint('[AR Model] Validasi asset: $modelPath');

    final uri = Uri.tryParse(modelPath);
    final isRemote = uri != null && uri.hasScheme;

    if (!isRemote &&
        (!modelPath.startsWith('assets/models/') ||
            !modelPath.toLowerCase().endsWith('.glb'))) {
      throw const ArModelAssetException(
        'Path model harus berada di assets/models/ dan berekstensi .glb.',
      );
    }

    final bytes = isRemote
        ? await _downloadAndValidateGlb(uri)
        : await _loadAndValidateGlb(modelPath);
    final documentsDirectory = await getApplicationDocumentsDirectory();
    final modelDirectory = Directory('${documentsDirectory.path}/ar_models');
    if (!await modelDirectory.exists()) {
      await modelDirectory.create(recursive: true);
    }

    final fileName = _safeFileName(
        isRemote ? uri.pathSegments.last : modelPath.split('/').last);
    final targetFile = File('${modelDirectory.path}/$fileName');
    if (!await targetFile.exists() ||
        await targetFile.length() != bytes.length) {
      debugPrint('[AR Model] Menyalin GLB ke: ${targetFile.path}');
      await targetFile.writeAsBytes(bytes, flush: true);
    } else {
      debugPrint('[AR Model] File lokal sudah siap: ${targetFile.path}');
    }

    final relativePath = 'ar_models/$fileName';
    debugPrint('[AR Model] Path untuk plugin: $relativePath');
    return relativePath;
  }

  Future<Uint8List> _loadAndValidateGlb(String assetPath) async {
    final byteData = await rootBundle.load(assetPath);
    final bytes = byteData.buffer.asUint8List(
      byteData.offsetInBytes,
      byteData.lengthInBytes,
    );

    debugPrint('[AR Model] Ukuran $assetPath: ${bytes.length} bytes');
    if (bytes.isEmpty) {
      throw const ArModelAssetException('File GLB kosong.');
    }
    if (bytes.length < 20) {
      throw const ArModelAssetException('File GLB terlalu kecil atau rusak.');
    }
    if (bytes.length > _maxRecommendedBytes) {
      debugPrint(
        '[AR Model] Peringatan: ukuran GLB di atas 4 MB, pertimbangkan optimasi.',
      );
    }

    final magic = String.fromCharCodes(bytes.sublist(0, 4));
    final version = ByteData.sublistView(bytes).getUint32(4, Endian.little);
    if (magic != 'glTF' || version != 2) {
      throw const ArModelAssetException(
        'File bukan GLB valid. Export ulang sebagai glTF Binary (.glb) versi 2.0.',
      );
    }

    return bytes;
  }

  Future<Uint8List> _downloadAndValidateGlb(Uri uri) async {
    final response = await http.get(uri).timeout(const Duration(seconds: 30));
    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw ArModelAssetException(
        'Model GLB gagal diunduh. HTTP ${response.statusCode}.',
      );
    }

    final bytes = response.bodyBytes;
    _validateGlbBytes(bytes, uri.toString());
    return bytes;
  }

  void _validateGlbBytes(Uint8List bytes, String source) {
    debugPrint('[AR Model] Ukuran $source: ${bytes.length} bytes');
    if (bytes.isEmpty) {
      throw const ArModelAssetException('File GLB kosong.');
    }
    if (bytes.length < 20) {
      throw const ArModelAssetException('File GLB terlalu kecil atau rusak.');
    }
    if (bytes.length > _maxRecommendedBytes) {
      debugPrint(
        '[AR Model] Peringatan: ukuran GLB di atas 4 MB, pertimbangkan optimasi.',
      );
    }

    final magic = String.fromCharCodes(bytes.sublist(0, 4));
    final version = ByteData.sublistView(bytes).getUint32(4, Endian.little);
    if (magic != 'glTF' || version != 2) {
      throw const ArModelAssetException(
        'File bukan GLB valid. Export ulang sebagai glTF Binary (.glb) versi 2.0.',
      );
    }
  }

  String _safeFileName(String value) {
    final name = value.trim().isEmpty ? 'model.glb' : value.trim();
    final sanitized = name.replaceAll(RegExp(r'[^a-zA-Z0-9._-]'), '_');
    return sanitized.toLowerCase().endsWith('.glb')
        ? sanitized
        : '$sanitized.glb';
  }
}
