import 'dart:convert';
import 'dart:typed_data';

import 'package:crypto/crypto.dart' as crypto;

import '../connector/meshcore_protocol.dart';
import 'community.dart';

enum ChannelType { public, private, hashtag, communityPublic, communityHashtag }

class Channel {
  final int index;
  final String name;
  final Uint8List psk; // 16 bytes
  int unreadCount;

  Channel({
    required this.index,
    required this.name,
    required this.psk,
    this.unreadCount = 0,
  });

  String get pskHex => _bytesToHex(psk);

  bool get isEmpty => name.isEmpty && psk.every((b) => b == 0);

  bool get isPublicChannel => pskHex == publicChannelPsk;

  bool get isHashtagChannel => name.startsWith('#');

  bool get isPrivateChannel => !isPublicChannel && !isHashtagChannel;

  static Channel? fromFrame(Uint8List frame) {
    // CHANNEL_INFO format:
    // [0] = RESP_CODE_CHANNEL_INFO (18)
    // [1] = channel_idx
    // [2-33] = name (32 bytes, null-terminated)
    // [34-49] = psk (16 bytes)
    if (frame.length < 50) return null;
    final reader = BufferReader(frame);
    try {
      if (reader.readByte() != respCodeChannelInfo) return null;
      final index = reader.readByte();
      final name = reader.readCStringGreedy(32);
      final psk = reader.readBytes(16);
      return Channel(index: index, name: name, psk: psk);
    } catch (e) {
      return null;
    }
  }

  static Channel empty(int index) {
    return Channel(index: index, name: '', psk: Uint8List(16));
  }

  static Channel fromHex(int index, String name, String pskHex) {
    final psk = parsePskHex(pskHex);
    return Channel(index: index, name: name, psk: psk);
  }

  static Uint8List parsePskHex(String hex) {
    final cleaned = hex.replaceAll(RegExp(r'[^0-9a-fA-F]'), '');
    if (cleaned.length != 32) {
      throw const FormatException('PSK must be 32 hex characters');
    }
    final bytes = Uint8List(16);
    for (int i = 0; i < 16; i++) {
      final start = i * 2;
      bytes[i] = int.parse(cleaned.substring(start, start + 2), radix: 16);
    }
    return bytes;
  }

  /// Derive PSK from hashtag name using SHA256.
  /// The hashtag is normalized to include '#' prefix.
  /// Returns first 16 bytes of SHA256 hash as PSK.
  static Uint8List derivePskFromHashtag(String hashtag) {
    final name = hashtag.startsWith('#') ? hashtag : '#$hashtag';
    final hash = crypto.sha256.convert(utf8.encode(name)).bytes;
    return Uint8List.fromList(hash.sublist(0, 16));
  }

  /// Derive PSK for community public channel using HMAC-SHA256.
  /// PSK = HMAC-SHA256(K, "channel:v1:__public__")[:16]
  ///
  /// This creates a channel that is "public" only to members who have
  /// the community secret. Outsiders see only opaque IDs.
  static Uint8List deriveCommunityPublicPsk(Uint8List secret) {
    final hmac = crypto.Hmac(crypto.sha256, secret);
    final digest = hmac.convert(utf8.encode('channel:v1:__public__'));
    return Uint8List.fromList(digest.bytes.sublist(0, 16));
  }

  /// Derive PSK for community hashtag channel using HMAC-SHA256.
  /// PSK = HMAC-SHA256(K, "channel:v1:" + normalized_name)[:16]
  ///
  /// Community hashtag channels are deterministic for all members
  /// (same name => same id) but impossible to enumerate/guess without K.
  static Uint8List deriveCommunityHashtagPsk(Uint8List secret, String hashtag) {
    final normalized = _normalizeCommunityHashtag(hashtag);
    final hmac = crypto.Hmac(crypto.sha256, secret);
    final digest = hmac.convert(utf8.encode('channel:v1:$normalized'));
    return Uint8List.fromList(digest.bytes.sublist(0, 16));
  }

  /// Normalize a hashtag name for consistent community PSK derivation.
  /// Strips leading #, converts to lowercase, trims whitespace.
  static String _normalizeCommunityHashtag(String hashtag) {
    return hashtag.replaceFirst(RegExp(r'^#'), '').toLowerCase().trim();
  }

  static String formatPskHex(Uint8List psk) {
    return _bytesToHex(psk);
  }

  static String _bytesToHex(Uint8List bytes) {
    return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
  }

  static bool isCommunityChannel(ChannelType channelType) {
    switch (channelType) {
      case ChannelType.communityPublic:
      case ChannelType.communityHashtag:
        return true;
      case ChannelType.public:
      case ChannelType.private:
      case ChannelType.hashtag:
        return false;
    }
  }

  static ChannelType getChannelType(
    Channel channel,
    CommunityPskIndex communityIndex,
  ) {
    Community? community = communityIndex.getCommunityForChannel(channel);
    if (community != null) {
      if (Community.isCommunityPublicChannel(channel, community)) {
        return ChannelType.communityPublic;
      }
      return ChannelType.communityHashtag;
    }
    if (channel.isPublicChannel) {
      return ChannelType.public;
    } else if (channel.name.startsWith('#')) {
      return ChannelType.hashtag;
    }
    return ChannelType.private;
  }

  static const String publicChannelPsk = '8b3387e9c5cdea6ac9e5edbaa115cd72';
}
