import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import '../core/database.dart';
import '../core/preferences.dart';
import '../models/trip.dart';
import '../models/participant.dart';
import '../models/photo.dart';




class SharingService {
  static final SharingService instance = SharingService._init();
  SharingService._init();

  HttpServer? _server;
  int? _serverPort;
  String? _activeTripId;

  // Callback to update UI when a participant joins
  Function(Participant)? onParticipantJoined;

  bool get isHosting => _server != null;
  int? get port => _serverPort;
  String? get activeTripId => _activeTripId;

  /// Get the active local IP address (typically WiFi)
  Future<String?> getLocalIpAddress() async {
    try {
      final interfaces = await NetworkInterface.list(
        includeLinkLocal: false,
        type: InternetAddressType.IPv4,
      );
      
      // Try to find WiFi interface (often contains 'wlan', 'wl', 'en')
      for (final interface in interfaces) {
        final name = interface.name.toLowerCase();
        if (name.contains('wlan') || name.contains('wl') || name.contains('en') || name.contains('eth')) {
          for (final addr in interface.addresses) {
            if (!addr.isLoopback) {
              return addr.address;
            }
          }
        }
      }

      // Fallback to any non-loopback IPv4
      for (final interface in interfaces) {
        for (final addr in interface.addresses) {
          if (!addr.isLoopback) {
            return addr.address;
          }
        }
      }
    } catch (e) {
      // Ignore
    }
    return null;
  }

  /// Start local sharing server for a trip
  Future<int> startServer(String tripId, {int defaultPort = 8080}) async {
    if (_server != null) return _serverPort!;

    _activeTripId = tripId;
    
    try {
      _server = await HttpServer.bind(InternetAddress.anyIPv4, defaultPort);
      _serverPort = _server!.port;
    } catch (e) {
      // Fallback to random free port if default is occupied
      _server = await HttpServer.bind(InternetAddress.anyIPv4, 0);
      _serverPort = _server!.port;
    }

    _listen();
    return _serverPort!;
  }

  /// Stop local sharing server
  Future<void> stopServer() async {
    await _server?.close(force: true);
    _server = null;
    _serverPort = null;
    _activeTripId = null;
  }

  /// Listen for HTTP requests
  void _listen() {
    _server?.listen((HttpRequest request) async {
      try {
        final path = request.uri.path;
        final method = request.method;

        if (method == 'POST' && path == '/join') {
          await _handleJoin(request);
        } else if (method == 'GET' && path == '/trip') {
          await _handleGetTrip(request);
        } else if (method == 'GET' && path == '/photos') {
          await _handleGetPhotosCatalog(request);
        } else if (method == 'GET' && path.startsWith('/photo/')) {
          await _handleGetPhotoBytes(request);
        } else {
          request.response
            ..statusCode = HttpStatus.notFound
            ..write(jsonEncode({'error': 'Not found'}))
            ..close();
        }
      } catch (e) {
        request.response
          ..statusCode = HttpStatus.internalServerError
          ..write(jsonEncode({'error': e.toString()}))
          ..close();
      }
    });
  }

  /// Handle participant joining requests
  Future<void> _handleJoin(HttpRequest request) async {
    final bodyString = await utf8.decoder.bind(request).join();
    final body = jsonDecode(bodyString) as Map<String, dynamic>;

    final id = body['id'] as String;
    final displayName = body['displayName'] as String;
    final deviceId = body['deviceId'] as String;

    final participant = Participant(
      id: id,
      tripId: _activeTripId!,
      displayName: displayName,
      deviceId: deviceId,
      isOwner: false,
      joinedAtUtc: DateTime.now().millisecondsSinceEpoch,
    );

    // Save joined participant to Database
    await DatabaseHelper.instance.insertParticipant(participant);

    // Notify UI
    if (onParticipantJoined != null) {
      onParticipantJoined!(participant);
    }

    request.response
      ..statusCode = HttpStatus.ok
      ..headers.contentType = ContentType.json
      ..write(jsonEncode({
        'status': 'success',
        'message': 'Joined trip successfully',
        'hostName': AppPreferences.displayName,
        'hostDeviceId': AppPreferences.deviceId,
      }))
      ..close();
  }

  /// Return trip metadata and participant list
  Future<void> _handleGetTrip(HttpRequest request) async {
    final db = DatabaseHelper.instance;
    final trip = await db.getTrip(_activeTripId!);
    final participants = await db.getParticipantsForTrip(_activeTripId!);

    if (trip == null) {
      request.response
        ..statusCode = HttpStatus.notFound
        ..write(jsonEncode({'error': 'Trip not found'}))
        ..close();
      return;
    }

    final data = {
      'trip': trip.toMap(),
      'participants': participants.map((p) => p.toMap()).toList(),
    };

    request.response
      ..statusCode = HttpStatus.ok
      ..headers.contentType = ContentType.json
      ..write(jsonEncode(data))
      ..close();
  }

  /// Return list of photos with their metadata (Stage 5 catalog sync)
  Future<void> _handleGetPhotosCatalog(HttpRequest request) async {
    final List<Photo> photos = await DatabaseHelper.instance.getPhotosForTrip(_activeTripId!);
    final data = photos.map((p) => p.toMap()).toList();

    request.response
      ..statusCode = HttpStatus.ok
      ..headers.contentType = ContentType.json
      ..write(jsonEncode(data))
      ..close();
  }

  /// Return raw bytes of a photo Content URI (Stage 5 file transfer)
  Future<void> _handleGetPhotoBytes(HttpRequest request) async {
    final photoId = request.uri.pathSegments.last;
    final List<Photo> photos = await DatabaseHelper.instance.getPhotosForTrip(_activeTripId!);
    final Photo photo = photos.firstWhere((p) => p.id == photoId);

    // Open file stream using ContentResolver on Android (or direct file if applicable)
    // In our case, we can get bytes using native code, but wait!
    // Since we are running in Kotlin, we can implement getPhotoBytes method in PlatformChannel,
    // or since this is Dart, we can get the file if it's stored locally,
    // or call our native MainActivity channel to return the raw photo bytes!
    // Let's call MethodChannel getPhotoBytes:
    try {
      final Uint8List? bytes = await const MethodChannel('cz.coolcode.live_vacation_memories/media')
          .invokeMethod<Uint8List>('getPhotoBytes', {'uri': photo.contentUri});
      
      if (bytes == null) {
        request.response
          ..statusCode = HttpStatus.notFound
          ..write(jsonEncode({'error': 'Photo bytes not found'}))
          ..close();
        return;
      }

      request.response
        ..statusCode = HttpStatus.ok
        ..headers.contentType = ContentType.parse(photo.mimeType)
        ..headers.contentLength = bytes.length
        ..add(bytes)
        ..close();
    } catch (e) {
      request.response
        ..statusCode = HttpStatus.internalServerError
        ..write(jsonEncode({'error': e.toString()}))
        ..close();
    }
  }

  // ================= CLIENT SIDE =================

  /// Parse the scanned QR code and request to join the host trip
  Future<String> joinTripFromQr(String qrString) async {
    // Expected format: lvm://join?host=192.168.1.10&port=8080&tripId=abc&tripName=Trip&ownerName=John
    final uri = Uri.parse(qrString);
    if (uri.scheme != 'lvm' || uri.host != 'join') {
      throw Exception('Neplatný formát pozvánky (QR kódu).');
    }

    final host = uri.queryParameters['host'];
    final port = uri.queryParameters['port'];
    final tripId = uri.queryParameters['tripId'];
    final tripName = uri.queryParameters['tripName'] ?? 'Společná dovolená';
    final ownerName = uri.queryParameters['ownerName'] ?? 'Hostitel';

    if (host == null || port == null || tripId == null) {
      throw Exception('Pozvánka neobsahuje potřebná síťová metadata.');
    }

    final joinEndpoint = Uri.parse('http://$host:$port/join');
    
    // 1. Send Join request
    final response = await http.post(
      joinEndpoint,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'id': AppPreferences.participantId,
        'displayName': AppPreferences.displayName,
        'deviceId': AppPreferences.deviceId,
      }),
    ).timeout(const Duration(seconds: 10));

    if (response.statusCode != HttpStatus.ok) {
      throw Exception('Chyba při připojování k hostiteli: ${response.body}');
    }

    final responseData = jsonDecode(response.body) as Map<String, dynamic>;
    final hostDeviceId = responseData['hostDeviceId'] as String? ?? 'host-device';

    final db = DatabaseHelper.instance;
    final now = DateTime.now().millisecondsSinceEpoch;

    // 2. Insert Trip locally if client doesn't have it
    var trip = await db.getTrip(tripId);
    if (trip == null) {
      trip = Trip(
        id: tripId,
        name: tripName,
        startTimeUtc: now, // Will be updated on sync
        finishTimeUtc: null,
        createdAtUtc: now,
        status: TripStatus.active,
        ownerParticipantId: uri.queryParameters['ownerId'] ?? 'host-id',
        timezoneId: DateTime.now().timeZoneName,
        shareCode: '$host:$port',
      );
      await db.insertTrip(trip);
    } else {
      // Update share code with latest host IP/port
      final updatedTrip = Trip(
        id: trip.id,
        name: trip.name,
        startTimeUtc: trip.startTimeUtc,
        finishTimeUtc: trip.finishTimeUtc,
        createdAtUtc: trip.createdAtUtc,
        status: trip.status,
        ownerParticipantId: trip.ownerParticipantId,
        timezoneId: trip.timezoneId,
        shareCode: '$host:$port',
        coverPhotoId: trip.coverPhotoId,
      );
      await db.updateTrip(updatedTrip);
    }

    // 3. Save Host Participant in local database
    final hostParticipant = Participant(
      id: uri.queryParameters['ownerId'] ?? 'host-id',
      tripId: tripId,
      displayName: ownerName,
      deviceId: hostDeviceId,
      isOwner: true,
      joinedAtUtc: now,
    );
    await db.insertParticipant(hostParticipant);

    // 4. Save Self Participant in local database
    final selfParticipant = Participant(
      id: AppPreferences.participantId,
      tripId: tripId,
      displayName: AppPreferences.displayName,
      deviceId: AppPreferences.deviceId,
      isOwner: false,
      joinedAtUtc: now,
    );
    await db.insertParticipant(selfParticipant);

    return tripId;
  }

  /// Sync photos from host for a trip. Returns number of synced photos.
  Future<int> syncPhotosFromHost(Trip trip) async {
    final socket = trip.shareCode;
    if (socket == null || !socket.contains(':')) {
      throw Exception('Chybí síťová metadata hostitele. Naskenujte QR kód znovu.');
    }

    final db = DatabaseHelper.instance;
    final catalogUrl = Uri.parse('http://$socket/photos');

    // 1. Fetch host's photo catalog
    final response = await http.get(catalogUrl).timeout(const Duration(seconds: 10));
    if (response.statusCode != HttpStatus.ok) {
      throw Exception('Nelze stáhnout katalog fotek z hostitele: ${response.body}');
    }

    final List<dynamic> hostCatalog = jsonDecode(response.body);
    final List<Photo> localPhotos = await db.getPhotosForTrip(trip.id);
    
    // Create lookup set for local SHA-256 hashes to prevent duplicates!
    final localHashes = localPhotos.map((p) => p.sha256).whereType<String>().toSet();
    final localIds = localPhotos.map((p) => p.id).toSet();

    int syncedCount = 0;

    // Get directories
    final docDir = await getApplicationDocumentsDirectory();
    final photosDir = Directory('${docDir.path}/photos');
    if (!await photosDir.exists()) {
      await photosDir.create(recursive: true);
    }

    for (final hostPhotoMap in hostCatalog) {
      final hostPhoto = Photo(
        id: hostPhotoMap['id']?.toString() ?? '',
        tripId: hostPhotoMap['tripId']?.toString() ?? '',
        authorParticipantId: hostPhotoMap['authorParticipantId']?.toString() ?? '',
        contentUri: hostPhotoMap['contentUri']?.toString() ?? '',
        displayName: hostPhotoMap['displayName']?.toString() ?? '',
        mimeType: hostPhotoMap['mimeType']?.toString() ?? 'image/jpeg',
        sizeBytes: hostPhotoMap['sizeBytes'] as int? ?? 0,
        dateTakenUtc: hostPhotoMap['dateTakenUtc'] as int?,
        dateAddedUtc: hostPhotoMap['dateAddedUtc'] as int?,
        latitude: hostPhotoMap['latitude'] as double?,
        longitude: hostPhotoMap['longitude'] as double?,
        altitude: hostPhotoMap['altitude'] as double?,
        width: hostPhotoMap['width'] as int?,
        height: hostPhotoMap['height'] as int?,
        orientation: hostPhotoMap['orientation'] as int?,
        cameraMake: hostPhotoMap['cameraMake']?.toString(),
        cameraModel: hostPhotoMap['cameraModel']?.toString(),
        sha256: hostPhotoMap['sha256']?.toString(),
        isLocalOriginal: false, // Synced photo, not original to this device
        sourceDeviceId: hostPhotoMap['sourceDeviceId']?.toString() ?? '',
        importState: 'synced',
        shareState: 'shared',
      );

      // Check SHA-256 duplicate OR ID check
      if (localHashes.contains(hostPhoto.sha256) || localIds.contains(hostPhoto.id)) {
        continue; // Already exists, skip
      }

      // Download raw bytes of the photo
      final fileUrl = Uri.parse('http://$socket/photo/${hostPhoto.id}');
      final fileResponse = await http.get(fileUrl).timeout(const Duration(seconds: 15));
      
      if (fileResponse.statusCode == HttpStatus.ok) {
        // Save file locally to app document directory
        final localFile = File('${photosDir.path}/${hostPhoto.id}.jpg');
        await localFile.writeAsBytes(fileResponse.bodyBytes);

        // Update contentUri to local file path
        final localPhoto = Photo(
          id: hostPhoto.id,
          tripId: hostPhoto.tripId,
          authorParticipantId: hostPhoto.authorParticipantId,
          contentUri: localFile.path, // Store local path
          displayName: hostPhoto.displayName,
          mimeType: hostPhoto.mimeType,
          sizeBytes: hostPhoto.sizeBytes,
          dateTakenUtc: hostPhoto.dateTakenUtc,
          dateAddedUtc: hostPhoto.dateAddedUtc,
          latitude: hostPhoto.latitude,
          longitude: hostPhoto.longitude,
          altitude: hostPhoto.altitude,
          width: hostPhoto.width,
          height: hostPhoto.height,
          orientation: hostPhoto.orientation,
          cameraMake: hostPhoto.cameraMake,
          cameraModel: hostPhoto.cameraModel,
          sha256: hostPhoto.sha256,
          isLocalOriginal: false,
          sourceDeviceId: hostPhoto.sourceDeviceId,
          importState: 'synced',
          shareState: 'shared',
        );

        await db.insertPhoto(localPhoto);
        syncedCount++;
      }
    }

    return syncedCount;
  }
}

