import React, { useMemo, useState } from 'react';
import { Badge, Card, Stack, Table } from 'react-bootstrap';
import { MapContainer, TileLayer, Popup, useMap, CircleMarker, Marker } from 'react-leaflet';
import { divIcon, type DivIcon } from 'leaflet';
import { renderToStaticMarkup } from 'react-dom/server';
import { useSearchParams } from 'react-router';
import VerticalSplit from '../../components/VerticalSplit';
import HorizontalSplit from '../../components/HorizontalSplit';
import CountryFlag from '../../components/CountryFlag';
import KeyboardShortcutsModal from '../../components/KeyboardShortcutsModal';
import StreamStatus from '../../components/StreamStatus';
import { APRSSymbol } from '../../components/aprs';
import { ClusteredMarkers } from '../../components/map';
import type { ClusterableItem, Cluster } from '../../components/map';
import { useKeyboardListNavigation } from '../../hooks/useKeyboardListNavigation';
import { useAPRSData } from './APRSData';
import type { APRSPacketRecord } from './APRSData';

const HeaderFact: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (
  <div className="aprs-fact-row">
    <span className="aprs-fact-label">{label}</span>
    <span className="aprs-fact-value">{value}</span>
  </div>
);

const Callsign: React.FC<{ call: string; ssid?: string | number; plain?: boolean }> = ({ call, ssid, plain = false }) => (
  <span className={plain ? 'callsign callsign--plain' : 'callsign'}>
    {call}
    {(ssid !== undefined && ssid !== '') ? <span>-{ssid}</span> : null}
  </span>
);

const getPacketKey = (packet: APRSPacketRecord): string => {
  const ssid = packet.frame.source.ssid ?? 0;
  return `${packet.frame.source.call}-${ssid}-${packet.timestamp.toISOString()}-${packet.raw}`;
};

const calculateBounds = (packets: APRSPacketRecord[]): [[number, number], [number, number]] | null => {
  const validPackets = packets.filter(p => p.latitude !== undefined && p.longitude !== undefined);

  if (validPackets.length === 0) {
    return null;
  }

  if (validPackets.length === 1) {
    const lat = validPackets[0].latitude!;
    const lng = validPackets[0].longitude!;
    const offset = 2;
    return [[lat - offset, lng - offset], [lat + offset, lng + offset]];
  }

  let minLat = validPackets[0].latitude!;
  let maxLat = validPackets[0].latitude!;
  let minLng = validPackets[0].longitude!;
  let maxLng = validPackets[0].longitude!;

  validPackets.forEach(packet => {
    const lat = packet.latitude!;
    const lng = packet.longitude!;
    if (lat < minLat) minLat = lat;
    if (lat > maxLat) maxLat = lat;
    if (lng < minLng) minLng = lng;
    if (lng > maxLng) maxLng = lng;
  });

  // Add 10% padding to the bounds
  const latPadding = (maxLat - minLat) * 0.1 || 0.5;
  const lngPadding = (maxLng - minLng) * 0.1 || 0.5;

  return [
    [minLat - latPadding, minLng - lngPadding],
    [maxLat + latPadding, maxLng + lngPadding]
  ];
};

const toRadians = (degrees: number): number => (degrees * Math.PI) / 180;

const distanceKm = (lat1: number, lng1: number, lat2: number, lng2: number): number => {
  const earthRadiusKm = 6371;
  const dLat = toRadians(lat2 - lat1);
  const dLng = toRadians(lng2 - lng1);
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
    Math.sin(dLng / 2) * Math.sin(dLng / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return earthRadiusKm * c;
};

const median = (values: number[]): number | null => {
  if (values.length === 0) {
    return null;
  }

  const sorted = [...values].sort((a, b) => a - b);
  const middle = Math.floor(sorted.length / 2);

  if (sorted.length % 2 === 0) {
    return (sorted[middle - 1] + sorted[middle]) / 2;
  }

  return sorted[middle];
};

const filterByClusterRadius = (packets: APRSPacketRecord[], radiusKm: number): APRSPacketRecord[] => {
  const validPackets = packets.filter(p => p.latitude !== undefined && p.longitude !== undefined);
  if (validPackets.length <= 1) {
    return validPackets;
  }

  const latCenter = median(validPackets.map((p) => p.latitude!));
  const lngCenter = median(validPackets.map((p) => p.longitude!));
  if (latCenter === null || lngCenter === null) {
    return validPackets;
  }

  const filtered = validPackets.filter((p) => distanceKm(latCenter, lngCenter, p.latitude!, p.longitude!) <= radiusKm);
  return filtered.length > 0 ? filtered : validPackets;
};

/**
 * Validate if an APRS symbol string is valid
 */
const isValidSymbol = (symbol: string | undefined): boolean => {
  if (!symbol || symbol.length < 2) return false;

  const table = symbol.charAt(0);
  const code = symbol.charAt(1);
  const charCode = code.charCodeAt(0);

  // Primary and secondary tables
  if (table === '/' || table === '\\') {
    return charCode >= 33 && charCode <= 125;
  }

  // Overlay characters: 0-9, A-Z
  if ((charCode >= 48 && charCode <= 57) || (charCode >= 65 && charCode <= 90)) {
    return true;
  }

  return false;
};

/**
 * Create a Leaflet DivIcon for an APRS symbol
 * Uses 32px source image rendered at 16px for crisp display
 */
const createAPRSIcon = (symbol: string) => {
  const iconHtml = renderToStaticMarkup(
    <APRSSymbol symbol={symbol} size={32} className="aprs-map-marker" />
  );

  return divIcon({
    html: iconHtml,
    className: 'aprs-marker-icon',
    iconSize: [16, 16],
    iconAnchor: [8, 8],
    popupAnchor: [0, -8],
  });
};

const MapPanHandler: React.FC<{
  packet: APRSPacketRecord | null;
  defaultBounds: [[number, number], [number, number]] | null;
}> = ({ packet, defaultBounds }) => {
  const map = useMap();
  const prevPacketRef = React.useRef<APRSPacketRecord | null>(packet);
  const prevBoundsRef = React.useRef<[[number, number], [number, number]] | null>(defaultBounds);

  React.useEffect(() => {
    if (packet && packet.latitude && packet.longitude) {
      const currentZoom = map.getZoom();
      // Don't zoom out if we're already zoomed in further than default
      const targetZoom = Math.max(currentZoom, 12);

      map.flyTo([packet.latitude as number, packet.longitude as number], targetZoom, {
        duration: 1.5,
        easeLinearity: 0.25,
      });
      prevPacketRef.current = packet;
    } else if (!packet) {
      // No packet selected - show all markers
      if (defaultBounds && (!prevPacketRef.current || JSON.stringify(prevBoundsRef.current) !== JSON.stringify(defaultBounds))) {
        map.fitBounds(defaultBounds, {
          padding: [50, 50],
          maxZoom: 15,
        });
        prevBoundsRef.current = defaultBounds;
      }
      prevPacketRef.current = null;
    }
  }, [packet, defaultBounds, map]);

  return null;
};

/**
 * Render popup content for an APRS packet
 */
const renderPacketPopup = (p: APRSPacketRecord) => (
  <div>
    <Callsign call={p.frame.source.call} ssid={p.frame.source.ssid} />
    <br />
    Lat: {p.latitude?.toFixed(4)}, Lon: {p.longitude?.toFixed(4)}
    {p.altitude && <div>Alt: {p.altitude.toFixed(0)}m</div>}
    {p.speed && <div>Speed: {p.speed}kt</div>}
    {p.comment && <div style={{ marginTop: '0.25rem', fontSize: '0.875rem' }}>{p.comment}</div>}
  </div>
);

/**
 * Render popup content for a cluster
 */
const renderClusterPopup = (cluster: Cluster<ClusterableItem>) => {
  const packets = cluster.items as APRSPacketRecord[];
  return (
    <div>
      <strong>{cluster.count} stations</strong>
      <div style={{ marginTop: '0.5rem', maxHeight: '200px', overflowY: 'auto' }}>
        {packets.map((p, i) => (
          <div key={i} style={{ marginBottom: '0.25rem' }}>
            <Callsign call={p.frame.source.call} ssid={p.frame.source.ssid} />
          </div>
        ))}
      </div>
      <div style={{ marginTop: '0.5rem', fontSize: '0.875rem', color: '#666' }}>
        Click to zoom in
      </div>
    </div>
  );
};

/**
 * Get icon for an APRS packet marker
 */
const getPacketIcon = (item: ClusterableItem): DivIcon | null => {
  const packet = item as APRSPacketRecord;
  if (isValidSymbol(packet.symbol)) {
    return createAPRSIcon(packet.symbol!);
  }
  return null; // Will use default blue circle
};

const APRSMapPane: React.FC<{
  packet: APRSPacketRecord | null;
  packets: APRSPacketRecord[];
  onSelectPacket: (packet: APRSPacketRecord) => void;
}> = ({ packet, packets, onSelectPacket }) => {
  const hasPosition = packet && packet.latitude && packet.longitude;

  // Create bounds from center point (offset by ~2 degrees)
  const createBoundsFromCenter = (lat: number, lng: number): [[number, number], [number, number]] => {
    const offset = 2;
    return [[lat - offset, lng - offset], [lat + offset, lng + offset]];
  };

  // Get latest location for each source
  const latestBySource = useMemo(() => {
    const sourceMap = new Map<string, APRSPacketRecord>();
    packets.forEach(p => {
      if (p.latitude !== undefined && p.longitude !== undefined) {
        const sourceSsid = p.frame.source.ssid ?? '';
        const key = `${p.frame.source.call}-${sourceSsid}`;
        // Keep the most recent packet for each source
        if (!sourceMap.has(key) || p.timestamp > sourceMap.get(key)!.timestamp) {
          sourceMap.set(key, p);
        }
      }
    });
    return Array.from(sourceMap.values());
  }, [packets]);

  const overviewLocations = useMemo(() => filterByClusterRadius(latestBySource, 250), [latestBySource]);

  const defaultBounds = useMemo(() => calculateBounds(overviewLocations), [overviewLocations]);

  const bounds = hasPosition
    ? createBoundsFromCenter(packet.latitude as number, packet.longitude as number)
    : (defaultBounds ?? [[48.0, 3.0], [52.0, 7.0]]);

  return (
    <Card className="data-table-card h-100" style={{ display: 'flex', flexDirection: 'column', padding: 0 }}>
      <MapContainer
        bounds={bounds}
        style={{ flex: 1, width: '100%' }}
        className="aprs-map"
      >
        <MapPanHandler packet={packet} defaultBounds={defaultBounds} />
        <TileLayer
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        {/* Clustered markers for latest location per source */}
        <ClusteredMarkers<ClusterableItem>
          items={overviewLocations}
          getItemKey={(item) => getPacketKey(item as APRSPacketRecord)}
          onItemClick={(item) => onSelectPacket(item as APRSPacketRecord)}
          getIcon={getPacketIcon}
          renderPopupContent={(item) => renderPacketPopup(item as APRSPacketRecord)}
          renderClusterPopupContent={renderClusterPopup}
        />
        {/* Highlight selected packet - use APRS symbol or red circle */}
        {hasPosition && (
          isValidSymbol(packet.symbol) ? (
            <Marker
              position={[packet.latitude as number, packet.longitude as number]}
              icon={createAPRSIcon(packet.symbol!)}
            >
              <Popup>
                <div>
                  <Callsign call={packet.frame.source.call} ssid={packet.frame.source.ssid} />
                  <br />
                  Lat: {packet.latitude?.toFixed(4)}, Lon: {packet.longitude?.toFixed(4)}
                  {packet.altitude && <div>Alt: {packet.altitude.toFixed(0)}m</div>}
                  {packet.speed && <div>Speed: {packet.speed}kt</div>}
                  {packet.comment && <div style={{ marginTop: '0.25rem', fontSize: '0.875rem' }}>{packet.comment}</div>}
                </div>
              </Popup>
            </Marker>
          ) : (
            <CircleMarker
              center={[packet.latitude as number, packet.longitude as number]}
              radius={8}
              pathOptions={{
                color: '#dc3545',
                fillColor: '#dc3545',
                fillOpacity: 0.8,
                weight: 3,
              }}
            >
              <Popup>
                <div>
                  <Callsign call={packet.frame.source.call} ssid={packet.frame.source.ssid} />
                  <br />
                  Lat: {packet.latitude?.toFixed(4)}, Lon: {packet.longitude?.toFixed(4)}
                  {packet.altitude && <div>Alt: {packet.altitude.toFixed(0)}m</div>}
                  {packet.speed && <div>Speed: {packet.speed}kt</div>}
                  {packet.comment && <div style={{ marginTop: '0.25rem', fontSize: '0.875rem' }}>{packet.comment}</div>}
                </div>
              </Popup>
            </CircleMarker>
          )
        )}
    </MapContainer>
    </Card>
  );
};

const PacketDetailsPane: React.FC<{ packet: APRSPacketRecord | null }> = ({ packet }) => {
  if (!packet) {
    return (
      <Card body className="data-table-card h-100">
        <h6>Select a packet</h6>
        <div>Click any packet in the list to view details and map.</div>
      </Card>
    );
  }

  return (
    <Stack gap={2} className="h-100 aprs-detail-stack">
      <Card body className="data-table-card">
        <Stack direction="horizontal" gap={2} className="mb-2">
          <h6 className="mb-0">Packet Details</h6>
          <Badge bg="primary">
            <Callsign call={packet.frame.source.call} ssid={packet.frame.source.ssid} plain />
          </Badge>
        </Stack>
        <HeaderFact label="Timestamp" value={packet.timestamp.toLocaleTimeString()} />
        <HeaderFact
          label="Source"
          value={<Callsign call={packet.frame.source.call} ssid={packet.frame.source.ssid} />}
        />
        {packet.radioName && <HeaderFact label="Radio" value={packet.radioName} />}
        <HeaderFact
          label="Destination"
          value={<Callsign call={packet.frame.destination.call} ssid={packet.frame.destination.ssid} />}
        />
        <HeaderFact label="Path" value={packet.frame.path.map((addr) => `${addr.call}${addr.ssid ? `-${addr.ssid}` : ''}${addr.isRepeated ? '*' : ''}`).join(', ') || 'None'} />
      </Card>

      {(packet.latitude || packet.longitude || packet.altitude || packet.speed || packet.course) && (
        <Card body className="data-table-card">
          <h6 className="mb-2">Position Data</h6>
          {packet.latitude && <HeaderFact label="Latitude" value={packet.latitude.toFixed(6)} />}
          {packet.longitude && <HeaderFact label="Longitude" value={packet.longitude.toFixed(6)} />}
          {packet.altitude && <HeaderFact label="Altitude" value={`${packet.altitude.toFixed(0)} m`} />}
          {packet.speed !== undefined && <HeaderFact label="Speed" value={`${packet.speed} kt`} />}
          {packet.course !== undefined && <HeaderFact label="Course" value={`${packet.course}°`} />}
        </Card>
      )}

      {packet.comment && (
        <Card body className="data-table-card">
          <h6 className="mb-2">Comment</h6>
          <div>{packet.comment}</div>
        </Card>
      )}

      <Card body className="data-table-card">
        <h6 className="mb-2">Raw Data</h6>
        <code className="aprs-raw-code">{packet.raw}</code>
      </Card>
    </Stack>
  );
};

const PacketTable: React.FC<{
  packets: APRSPacketRecord[];
  radioFilter?: string;
  selectedPacketKey: string | null;
  onSelectPacket: (packet: APRSPacketRecord) => void;
  onClearSelection: () => void;
  streamReady: boolean;
}> = ({ packets, radioFilter, selectedPacketKey, onSelectPacket, onClearSelection, streamReady }) => {
  const scrollRef = React.useRef<HTMLDivElement>(null);

  const selectedIndex = useMemo(() => {
    if (!selectedPacketKey) {
      return null;
    }
    const index = packets.findIndex((packet) => getPacketKey(packet) === selectedPacketKey);
    return index >= 0 ? index : null;
  }, [packets, selectedPacketKey]);

  const { showShortcuts, setShowShortcuts, shortcuts } = useKeyboardListNavigation({
    itemCount: packets.length,
    selectedIndex,
    onSelectIndex: (index) => {
      if (index === null) {
        onClearSelection();
        return;
      }
      const packet = packets[index];
      if (packet) {
        onSelectPacket(packet);
      }
    },
    scrollContainerRef: scrollRef,
  });

  return (
    <Card className="data-table-card h-100 d-flex flex-column">
      <Card.Header className="data-table-header d-flex justify-content-between align-items-center">
        <span>APRS Packets</span>
        <div className="d-flex align-items-center gap-2">
          <StreamStatus ready={streamReady} />
        </div>
      </Card.Header>
      <Card.Body className="data-table-body p-0">
        <div className="data-table-scroll" ref={scrollRef}>
        <Table hover responsive className="data-table mb-0" size="sm">
          <thead>
            <tr>
              <th>Time</th>
              <th>&nbsp;</th>
              <th>Source</th>
              <th>Destination</th>
              <th>&nbsp;</th>
              <th>Comment</th>
            </tr>
          </thead>
          <tbody>
            {packets.map((packet, index) => (
              <tr
                key={getPacketKey(packet)}
                data-nav-item="true"
                className={selectedIndex === index ? 'is-selected' : ''}
                onClick={() => onSelectPacket(packet)}
              >
                <td style={{ verticalAlign: 'top' }}>{packet.timestamp.toLocaleTimeString()}</td>
                <td style={{ verticalAlign: 'top' }}>
                  <CountryFlag callsign={packet.frame.source.call} size={1.25} />
                </td>
                <td style={{ verticalAlign: 'top' }}>
                  <Callsign call={packet.frame.source.call} ssid={packet.frame.source.ssid} />
                </td>
                <td style={{ verticalAlign: 'top' }}>
                  <Callsign call={packet.frame.destination.call} ssid={packet.frame.destination.ssid} />
                </td>
                <td style={{ verticalAlign: 'top' }}>
                  {packet.symbol && <APRSSymbol symbol={packet.symbol} size={24} />}
                </td>
                <td style={{ verticalAlign: 'top' }}>{packet.comment || '-'}</td>
              </tr>
            ))}
          </tbody>
        </Table>
        </div>
      </Card.Body>
      {radioFilter && <Card.Footer className="text-muted" style={{ fontSize: '0.875rem' }}>Filtered by radio: {radioFilter}</Card.Footer>}
      <KeyboardShortcutsModal
        show={showShortcuts}
        onHide={() => setShowShortcuts(false)}
        shortcuts={shortcuts}
      />
    </Card>
  );
};

const APRSPacketsView: React.FC = () => {
  const { packets, streamReady } = useAPRSData();
  const [searchParams] = useSearchParams();
  const radioFilter = searchParams.get('radio') || undefined;
  const [selectedPacketKey, setSelectedPacketKey] = useState<string | null>(null);

  // Filter packets by radio name if specified
  const filteredPackets = useMemo(() => {
    if (!radioFilter) return packets;
    return packets.filter(packet => packet.radioName === radioFilter);
  }, [packets, radioFilter]);

  const selectedPacket = useMemo(() => {
    if (!selectedPacketKey) {
      return null;
    }
    return filteredPackets.find((packet) => getPacketKey(packet) === selectedPacketKey) ?? null;
  }, [filteredPackets, selectedPacketKey]);

  return (
    <VerticalSplit
      ratio="1:1"
      left={<PacketTable packets={filteredPackets} radioFilter={radioFilter} selectedPacketKey={selectedPacketKey} onSelectPacket={(packet) => setSelectedPacketKey(getPacketKey(packet))} onClearSelection={() => setSelectedPacketKey(null)} streamReady={streamReady} />}
      right={
        <HorizontalSplit
          top={<APRSMapPane packet={selectedPacket} packets={filteredPackets} onSelectPacket={(packet) => setSelectedPacketKey(getPacketKey(packet))} />}
          bottom={<PacketDetailsPane packet={selectedPacket} />}
        />
      }
    />
  );
};

export default APRSPacketsView;
