import React, { useEffect, useMemo, useState } from 'react';
import { bytesToHex } from '@noble/hashes/utils.js';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import BrandingWatermarkIcon from '@mui/icons-material/BrandingWatermark';
import PersonIcon from '@mui/icons-material/Person';
import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
import ReplyIcon from '@mui/icons-material/Reply';
import RouteIcon from '@mui/icons-material/Route';
import SensorsIcon from '@mui/icons-material/Sensors';
import SignalCellularAltIcon from '@mui/icons-material/SignalCellularAlt';
import StorageIcon from '@mui/icons-material/Storage';

import { Packet } from '../../protocols/meshcore';
import { NodeType, PayloadType, RouteType } from '../../protocols/meshcore.types';
import { MeshCoreStream } from '../../services/MeshCoreStream';
import type { MeshCoreMessage } from '../../services/MeshCoreStream';
import type { Payload, AdvertPayload } from '../../protocols/meshcore.types';
import API from '../../services/API';
import MeshCoreServiceImpl from '../../services/MeshCoreService';
import { base64ToBytes } from '../../util';

import {
  MeshCoreDataContext,
  type MeshCoreDataContextValue,
  type MeshCorePacketRecord,
  type MeshCoreGroupChatRecord,
  type MeshCoreNodePoint,
} from './MeshCoreContext';

export {
  MeshCoreDataContext,
  useMeshCoreData,
  type MeshCoreDataContextValue,
  type MeshCorePacketRecord,
  type MeshCoreGroupChatRecord,
  type MeshCoreNodePoint,
} from './MeshCoreContext';

export const payloadNameByValue = Object.fromEntries(
  Object.entries(PayloadType).map(([name, value]) => [value, name])
) as Record<number, string>;

export const nodeTypeNameByValue = Object.fromEntries(
  Object.entries(NodeType).map(([name, value]) => [value, name])
) as Record<number, string>;

export const routeTypeNameByValue = Object.fromEntries(
  Object.entries(RouteType).map(([name, value]) => [value, name])
) as Record<number, string>;

export const payloadValueByName = Object.fromEntries(
  Object.entries(PayloadType).map(([name, value]) => [name, value])
) as Record<string, number>;

export const PayloadTypeIcon: React.FC<{ payloadType: number }> = ({ payloadType }) => {
  switch (payloadType) {
    case PayloadType.REQUEST:
      return <ArrowForwardIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.RESPONSE:
      return <ArrowBackIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.TEXT:
      return <PersonIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.ACK:
      return <ReplyIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.ADVERT:
      return <SignalCellularAltIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.GROUP_TEXT:
      return <PersonIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.GROUP_DATA:
      return <StorageIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.ANON_REQ:
      return <ArrowForwardIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.PATH:
      return <RouteIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.TRACE:
      return <RouteIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.MULTIPART:
      return <BrandingWatermarkIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.CONTROL:
      return <SensorsIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    case PayloadType.RAW_CUSTOM:
      return <QuestionMarkIcon className="meshcore-payload-icon" titleAccess={payloadDisplayByValue[payloadType]} />;
    default:
      return <QuestionMarkIcon className="meshcore-payload-icon" titleAccess="Unknown" />;
  }
};
export const nodeTypeValueByName = Object.fromEntries(
  Object.entries(NodeType).map(([name, value]) => [name, value])
) as Record<string, number>;

export const routeTypeValueByName = Object.fromEntries(
  Object.entries(RouteType).map(([name, value]) => [name, value])
) as Record<string, number>;

// Human-readable display names (for UI)
export const nodeTypeDisplayByValue: Record<number, string> = {
  [NodeType.TYPE_UNKNOWN]: 'Unknown',
  [NodeType.TYPE_CHAT_NODE]: 'Chat Node',
  [NodeType.TYPE_REPEATER]: 'Repeater',
  [NodeType.TYPE_ROOM_SERVER]: 'Room Server',
  [NodeType.TYPE_SENSOR]: 'Sensor',
};

export const payloadDisplayByValue: Record<number, string> = Object.fromEntries(
  Object.entries(PayloadType).map(([name, value]) => {
    const cleanName = name.replace(/^PAYLOAD_TYPE_/, '').replace(/_/g, ' ');
    return [value, cleanName.charAt(0) + cleanName.slice(1).toLowerCase()];
  })
) as Record<number, string>;

export const routeDisplayByValue: Record<number, string> = Object.fromEntries(
  Object.entries(RouteType).map(([name, value]) => {
    const cleanName = name.replace(/^ROUTE_TYPE_/, '').replace(/_/g, ' ');
    return [value, cleanName.charAt(0) + cleanName.slice(1).toLowerCase()];
  })
) as Record<number, string>;

// URL-friendly names (lowercase, no TYPE_ prefix)
export const nodeTypeUrlByValue: Record<number, string> = {
  [NodeType.TYPE_UNKNOWN]: 'unknown',
  [NodeType.TYPE_CHAT_NODE]: 'chat_node',
  [NodeType.TYPE_REPEATER]: 'repeater',
  [NodeType.TYPE_ROOM_SERVER]: 'room_server',
  [NodeType.TYPE_SENSOR]: 'sensor',
};

export const nodeTypeValueByUrl: Record<string, number> = {
  'unknown': NodeType.TYPE_UNKNOWN,
  'chat_node': NodeType.TYPE_CHAT_NODE,
  'repeater': NodeType.TYPE_REPEATER,
  'room_server': NodeType.TYPE_ROOM_SERVER,
  'sensor': NodeType.TYPE_SENSOR,
};

export const payloadUrlByValue: Record<number, string> = Object.fromEntries(
  Object.entries(PayloadType).map(([name, value]) => {
    const urlName = name.replace(/^PAYLOAD_TYPE_/, '').toLowerCase();
    return [value, urlName];
  })
) as Record<number, string>;

export const payloadValueByUrl: Record<string, number> = Object.fromEntries(
  Object.entries(PayloadType).map(([name, value]) => {
    const urlName = name.replace(/^PAYLOAD_TYPE_/, '').toLowerCase();
    return [urlName, value];
  })
) as Record<string, number>;

export const routeUrlByValue: Record<number, string> = Object.fromEntries(
  Object.entries(RouteType).map(([name, value]) => {
    const urlName = name.replace(/^ROUTE_TYPE_/, '').toLowerCase();
    return [value, urlName];
  })
) as Record<number, string>;

export const routeValueByUrl: Record<string, number> = Object.fromEntries(
  Object.entries(RouteType).map(([name, value]) => {
    const urlName = name.replace(/^ROUTE_TYPE_/, '').toLowerCase();
    return [urlName, value];
  })
) as Record<string, number>;

const DISCARD_DUPLICATE_PATH_PACKETS = true;

const getPacketPathKey = (raw: Uint8Array): string => {
  if (raw.length < 2) {
    return '';
  }

  const pathField = raw[1];
  const hashSize = (pathField >> 6) + 1;
  const hashCount = pathField & 0x3f;

  if (hashCount === 0 || hashSize === 4) {
    return '';
  }

  const pathByteLength = hashCount * hashSize;
  const availablePathBytes = Math.min(pathByteLength, Math.max(raw.length - 2, 0));
  if (availablePathBytes <= 0) {
    return '';
  }

  return bytesToHex(raw.slice(2, 2 + availablePathBytes));
};

const dedupeByHashAndPath = (packets: MeshCorePacketRecord[]): MeshCorePacketRecord[] => {
  if (!DISCARD_DUPLICATE_PATH_PACKETS) {
    return packets;
  }

  const sortedByReceiveTime = [...packets].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
  const seen = new Set<string>();
  const deduped: MeshCorePacketRecord[] = [];

  sortedByReceiveTime.forEach((packet) => {
    const signature = `${packet.hash}:${getPacketPathKey(packet.raw)}`;
    if (seen.has(signature)) {
      return;
    }

    seen.add(signature);
    deduped.push(packet);
  });

  return deduped;
};

const summarizePayload = (payloadType: number, decodedPayload: Payload | undefined, payloadBytes: Uint8Array): string => {
  switch (payloadType) {
    case PayloadType.REQUEST:
      return `request len=${payloadBytes.length}`;
    case PayloadType.RESPONSE:
      return `response len=${payloadBytes.length}`;
    case PayloadType.TEXT:
      if (decodedPayload && 'dstHash' in decodedPayload && 'srcHash' in decodedPayload) {
        return `text ${decodedPayload.srcHash}->${decodedPayload.dstHash}`;
      }
      return 'text message';
    case PayloadType.ACK:
      return 'acknowledgement';
    case PayloadType.ADVERT:
      const advert = decodedPayload as AdvertPayload;
      console.log('advert', advert);
      if (advert && decodedPayload && 'appdata' in decodedPayload && advert.appdata && 'name' in advert.appdata) {
        return `advertisement: ${advert.appdata.name}`;
      }
      return 'advertisement';
    case PayloadType.GROUP_TEXT:
      if (decodedPayload && 'channelHash' in decodedPayload) {
        return `group channel=${decodedPayload.channelHash}`;
      }
      return `group text len=${payloadBytes.length}`;
    case PayloadType.GROUP_DATA:
      return `group data len=${payloadBytes.length}`;
    case PayloadType.ANON_REQ:
      if (decodedPayload && 'dstHash' in decodedPayload) {
        return `anon req dst=${decodedPayload.dstHash}`;
      }
      return 'anon request';
    case PayloadType.PATH:
      return `path len=${payloadBytes.length}`;
    case PayloadType.TRACE:
      return `trace len=${payloadBytes.length}`;
    case PayloadType.MULTIPART:
      return `multipart len=${payloadBytes.length}`;
    case PayloadType.CONTROL:
      if (decodedPayload && 'flags' in decodedPayload && typeof decodedPayload.flags === 'number') {
        return `control flags=0x${decodedPayload.flags.toString(16)}`;
      }
      return `control raw=${bytesToHex(payloadBytes.slice(0, 4))}`;
    case PayloadType.RAW_CUSTOM:
    default:
      return `raw=${bytesToHex(payloadBytes.slice(0, Math.min(6, payloadBytes.length)))}`;
  }
};

const toGroupChats = (packets: MeshCorePacketRecord[]): MeshCoreGroupChatRecord[] => {
  return packets
    .filter((packet) => packet.payloadType === PayloadType.GROUP_TEXT || packet.payloadType === PayloadType.TEXT)
    .map((packet, index) => {
      const payload = packet.decodedPayload as Record<string, unknown> | undefined;
      const channel = (payload && typeof payload === 'object' && 'channelHash' in payload
        ? (payload.channelHash as string)
        : 'general') as string;
      const sender = (payload && typeof payload === 'object' && 'srcHash' in payload
        ? (payload.srcHash as string)
        : packet.path[0].toString(16).padStart(2, '0')) as string;

      return {
        hash: packet.hash,
        timestamp: packet.timestamp,
        channel,
        sender,
        message: `Mock message #${index + 1} (${packet.payloadSummary})`,
      };
    });
};

const toMapPoints = (packets: MeshCorePacketRecord[]): MeshCoreNodePoint[] => {
  const byNode = new Map<string, MeshCoreNodePoint>();

  packets.forEach((packet) => {
    const nodeId = packet.path[0].toString(16).padStart(2, '0');
    const existing = byNode.get(nodeId);

    if (existing) {
      existing.packetCount += 1;
      return;
    }

    const byte = packet.path[0];
    byNode.set(nodeId, {
      nodeId,
      nodeType: packet.nodeType,
      packetCount: 1,
      latitude: 52.0 + ((byte % 20) - 10) * 0.02,
      longitude: 5.0 + (((byte >> 1) % 20) - 10) * 0.03,
    });
  });

  return Array.from(byNode.values());
};

export const MeshCoreDataProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [packets, setPackets] = useState<MeshCorePacketRecord[]>([]);
  const [streamReady, setStreamReady] = useState(false);

  const stream = useMemo(() => new MeshCoreStream(false), []);
  const meshCoreService = useMemo(() => new MeshCoreServiceImpl(API), []);

  useEffect(() => {
    let isMounted = true;

    const fetchPackets = async () => {
      try {
        const fetchedPackets = await meshCoreService.fetchPackets();
        if (!isMounted) {
          return;
        }

        const records: MeshCorePacketRecord[] = fetchedPackets.map((packet) => {
          const raw = base64ToBytes(packet.raw);

          let decodedPayload: Payload | undefined;
          try {
            const p = new Packet();
            p.parse(raw);
            decodedPayload = p.decode();
          } catch {
            decodedPayload = undefined;
          }

          const pathLength = raw[1] & 0x3f;
          const payloadBytes = raw.slice(2 + pathLength);

          return {
            timestamp: new Date(packet.received_at),
            hash: packet.hash,
            nodeType: packet.payload_type === PayloadType.ADVERT ? NodeType.TYPE_UNKNOWN : 0,
            payloadType: packet.payload_type,
            routeType: packet.route_type,
            version: packet.version,
            path: raw.slice(2, 2 + pathLength),
            raw,
            decodedPayload,
            payloadSummary: summarizePayload(packet.payload_type, decodedPayload, payloadBytes),
          };
        });

        setPackets((prev) => {
          const merged = dedupeByHashAndPath([...records, ...prev]);
          return merged
            .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
            .slice(0, 500);
        });
      } catch (error) {
        console.error('Failed to fetch MeshCore packets:', error);
      }
    };

    fetchPackets();
    stream.connect();

    const unsubscribeState = stream.subscribeToState((state) => {
      setStreamReady(state.isConnected);
    });

    const unsubscribePackets = stream.subscribe<MeshCoreMessage>(
      'meshcore/packet/#',
      (message) => {
        setPackets((prev) => {
          const packet: MeshCorePacketRecord = {
            timestamp: message.receivedAt,
            hash: message.hash,
            nodeType: 0, // Default; would be extracted from payload if needed
            payloadType: 0,
            routeType: 0,
            version: 1,
            path: new Uint8Array(),
            raw: message.raw,
            decodedPayload: message.decodedPayload,
            payloadSummary: '',
            radioName: message.radioName,
          };

          // Extract details from raw packet
          try {
            const p = new Packet();
            p.parse(message.raw);
            packet.version = (message.raw[0] >> 6) & 0x03;
            packet.payloadType = (message.raw[0] >> 2) & 0x0f;
            packet.routeType = message.raw[0] & 0x03;

            const pathLength = message.raw[1] & 0x3f;
            packet.path = message.raw.slice(2, 2 + pathLength);

            // Summarize payload
            const payloadBytes = message.raw.slice(2 + pathLength);
            packet.payloadSummary = summarizePayload(packet.payloadType, message.decodedPayload, payloadBytes);
          } catch (error) {
            console.error('Failed to parse packet:', error);
            packet.payloadSummary = `raw=${bytesToHex(packet.raw.slice(0, Math.min(6, packet.raw.length)))}`;
          }

          const merged = dedupeByHashAndPath([packet, ...prev]);
          return merged
            .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
            .slice(0, 500);
        });
      }
    );

    return () => {
      isMounted = false;
      unsubscribeState();
      unsubscribePackets();
      stream.disconnect();
    };
  }, [stream, meshCoreService]);

  const groupChats = useMemo(() => toGroupChats(packets), [packets]);
  const mapPoints = useMemo(() => toMapPoints(packets), [packets]);

  const value = useMemo<MeshCoreDataContextValue>(() => {
    return {
      packets,
      groupChats,
      mapPoints,
      streamReady,
    };
  }, [packets, groupChats, mapPoints, streamReady]);

  return <MeshCoreDataContext.Provider value={value}>{children}</MeshCoreDataContext.Provider>;
};
