import { DeesElement, property, html, customElement, type TemplateResult, css, state, cssManager } from '@design.estate/dees-element';
import * as appstate from '../../appstate.js';
import * as interfaces from '../../../dist_ts_interfaces/index.js';
import { viewHostCss } from '../shared/css.js';
import { type IStatsTile } from '@design.estate/dees-catalog';

declare global {
  interface HTMLElementTagNameMap {
    'ops-view-network-activity': OpsViewNetworkActivity;
  }
}

@customElement('ops-view-network-activity')
export class OpsViewNetworkActivity extends DeesElement {
  /** How far back the traffic chart shows */
  private static readonly CHART_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
  /** How often a new data point is added */
  private static readonly UPDATE_INTERVAL_MS = 1000; // 1 second
  /** Derived: max data points the buffer holds */
  private static readonly MAX_DATA_POINTS = OpsViewNetworkActivity.CHART_WINDOW_MS / OpsViewNetworkActivity.UPDATE_INTERVAL_MS;

  @state()
  accessor statsState = appstate.statsStatePart.getState()!;

  @state()
  accessor networkState = appstate.networkStatePart.getState()!;


  @state()
  accessor trafficDataIn: Array<{ x: string | number; y: number }> = [];

  @state()
  accessor trafficDataOut: Array<{ x: string | number; y: number }> = [];

  // Track if we need to update the chart to avoid unnecessary re-renders
  private lastChartUpdate = 0;
  private chartUpdateThreshold = OpsViewNetworkActivity.UPDATE_INTERVAL_MS; // Minimum ms between chart updates

  private trafficUpdateTimer: any = null;
  private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
  private historyLoaded = false; // Whether server-side throughput history has been loaded
  private visibilityHandler: (() => void) | null = null;

  constructor() {
    super();
    this.subscribeToStateParts();
    this.initializeTrafficData();
    this.updateNetworkData();
    this.startTrafficUpdateTimer();
  }

  async connectedCallback() {
    await super.connectedCallback();

    // Pause/resume traffic timer when tab visibility changes
    this.visibilityHandler = () => {
      if (document.hidden) {
        this.stopTrafficUpdateTimer();
      } else {
        this.startTrafficUpdateTimer();
      }
    };
    document.addEventListener('visibilitychange', this.visibilityHandler);

    // When network view becomes visible, ensure we fetch network data
    await appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null);
  }

  async disconnectedCallback() {
    await super.disconnectedCallback();
    this.stopTrafficUpdateTimer();
    if (this.visibilityHandler) {
      document.removeEventListener('visibilitychange', this.visibilityHandler);
      this.visibilityHandler = null;
    }
  }

  private subscribeToStateParts() {
    // Subscribe and track unsubscribe functions
    const statsUnsubscribe = appstate.statsStatePart.select().subscribe((state) => {
      this.statsState = state;
    });
    this.rxSubscriptions.push(statsUnsubscribe);

    const networkUnsubscribe = appstate.networkStatePart.select().subscribe((state) => {
      this.networkState = state;
      this.updateNetworkData();
    });
    this.rxSubscriptions.push(networkUnsubscribe);
  }

  private initializeTrafficData() {
    const now = Date.now();
    const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetworkActivity;

    // Initialize with empty data points for both in and out
    const emptyData = Array.from({ length: MAX_DATA_POINTS }, (_, i) => {
      const time = now - ((MAX_DATA_POINTS - 1 - i) * UPDATE_INTERVAL_MS);
      return {
        x: new Date(time).toISOString(),
        y: 0,
      };
    });

    this.trafficDataIn = [...emptyData];
    this.trafficDataOut = emptyData.map(point => ({ ...point }));
  }

  /**
   * Load server-side throughput history into the chart.
   * Called once when history data first arrives from the Rust engine.
   * This pre-populates the chart so users see historical data immediately
   * instead of starting from all zeros.
   */
  private loadThroughputHistory() {
    const history = this.networkState.throughputHistory;
    if (!history || history.length === 0) return;

    this.historyLoaded = true;

    // Convert history points to chart data format (bytes/sec → Mbit/s)
    const historyIn = history.map(p => ({
      x: new Date(p.timestamp).toISOString(),
      y: Math.round((p.in * 8) / 1000000 * 10) / 10,
    }));
    const historyOut = history.map(p => ({
      x: new Date(p.timestamp).toISOString(),
      y: Math.round((p.out * 8) / 1000000 * 10) / 10,
    }));

    const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetworkActivity;

    // Use history as the chart data, keeping the most recent points within the window
    const sliceStart = Math.max(0, historyIn.length - MAX_DATA_POINTS);
    this.trafficDataIn = historyIn.slice(sliceStart);
    this.trafficDataOut = historyOut.slice(sliceStart);

    // If fewer than MAX_DATA_POINTS, pad the front with zeros
    if (this.trafficDataIn.length < MAX_DATA_POINTS) {
      const now = Date.now();
      const padCount = MAX_DATA_POINTS - this.trafficDataIn.length;
      const firstTimestamp = this.trafficDataIn.length > 0
        ? new Date(this.trafficDataIn[0].x).getTime()
        : now;

      const padIn = Array.from({ length: padCount }, (_, i) => ({
        x: new Date(firstTimestamp - ((padCount - i) * UPDATE_INTERVAL_MS)).toISOString(),
        y: 0,
      }));
      const padOut = padIn.map(p => ({ ...p }));

      this.trafficDataIn = [...padIn, ...this.trafficDataIn];
      this.trafficDataOut = [...padOut, ...this.trafficDataOut];
    }
  }

  public static styles = [
    cssManager.defaultStyles,
    viewHostCss,
    css`
      .networkContainer {
        display: flex;
        flex-direction: column;
        gap: 24px;
      }


      .protocolBadge {
        display: inline-flex;
        align-items: center;
        padding: 4px 8px;
        border-radius: 4px;
        font-size: 12px;
        font-weight: 500;
      }

      .protocolBadge.http {
        background: ${cssManager.bdTheme('#e3f2fd', '#1a2c3a')};
        color: ${cssManager.bdTheme('#1976d2', '#5a9fd4')};
      }

      .protocolBadge.https {
        background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')};
        color: ${cssManager.bdTheme('#388e3c', '#66bb6a')};
      }

      .protocolBadge.tcp {
        background: ${cssManager.bdTheme('#fff3e0', '#3a2a1a')};
        color: ${cssManager.bdTheme('#f57c00', '#ff9933')};
      }

      .protocolBadge.smtp {
        background: ${cssManager.bdTheme('#f3e5f5', '#2a1a3a')};
        color: ${cssManager.bdTheme('#7b1fa2', '#ba68c8')};
      }

      .protocolBadge.dns {
        background: ${cssManager.bdTheme('#e0f2f1', '#1a3a3a')};
        color: ${cssManager.bdTheme('#00796b', '#4db6ac')};
      }

      .protocolBadge.h1 {
        background: ${cssManager.bdTheme('#e3f2fd', '#1a2c3a')};
        color: ${cssManager.bdTheme('#1976d2', '#5a9fd4')};
      }

      .protocolBadge.h2 {
        background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')};
        color: ${cssManager.bdTheme('#388e3c', '#66bb6a')};
      }

      .protocolBadge.h3 {
        background: ${cssManager.bdTheme('#f3e5f5', '#2a1a3a')};
        color: ${cssManager.bdTheme('#7b1fa2', '#ba68c8')};
      }

      .protocolBadge.unknown {
        background: ${cssManager.bdTheme('#f5f5f5', '#2a2a2a')};
        color: ${cssManager.bdTheme('#757575', '#999999')};
      }

      .suppressionBadge {
        display: inline-flex;
        align-items: center;
        padding: 2px 6px;
        border-radius: 3px;
        font-size: 11px;
        font-weight: 500;
        background: ${cssManager.bdTheme('#fff3e0', '#3a2a1a')};
        color: ${cssManager.bdTheme('#f57c00', '#ff9933')};
        margin-left: 4px;
      }

      .statusBadge {
        display: inline-flex;
        align-items: center;
        padding: 4px 8px;
        border-radius: 4px;
        font-size: 12px;
        font-weight: 500;
      }

      .statusBadge.success {
        background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')};
        color: ${cssManager.bdTheme('#388e3c', '#66bb6a')};
      }

      .statusBadge.error {
        background: ${cssManager.bdTheme('#ffebee', '#3a1a1a')};
        color: ${cssManager.bdTheme('#d32f2f', '#ff6666')};
      }

      .statusBadge.warning {
        background: ${cssManager.bdTheme('#fff3e0', '#3a2a1a')};
        color: ${cssManager.bdTheme('#f57c00', '#ff9933')};
      }

      .intelligenceBadge {
        display: inline-flex;
        align-items: center;
        padding: 4px 8px;
        border-radius: 999px;
        font-size: 12px;
        font-weight: 500;
        background: ${cssManager.bdTheme('#eef2ff', '#1e1b4b')};
        color: ${cssManager.bdTheme('#4338ca', '#a5b4fc')};
      }

      .protocolChartGrid {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 16px;
      }
    `,
  ];

  public render() {
    return html`
      <dees-heading level="3">Network Activity</dees-heading>

      <div class="networkContainer">
        <!-- Stats Grid -->
        ${this.renderNetworkStats()}

        <!-- Traffic Chart -->
        <dees-chart-area
          .label=${'Network Traffic'}
          .series=${[
            {
              name: 'Inbound',
              data: this.trafficDataIn,
              color: '#22c55e',
            },
            {
              name: 'Outbound',
              data: this.trafficDataOut,
              color: '#8b5cf6',
            }
          ]}
          .realtimeMode=${true}
          .rollingWindow=${OpsViewNetworkActivity.CHART_WINDOW_MS}
          .yAxisFormatter=${(val: number) => `${val} Mbit/s`}
        ></dees-chart-area>

        <!-- Protocol Distribution Charts -->
        ${this.renderProtocolCharts()}

        <!-- Top IPs by Connection Count -->
        ${this.renderTopIPs()}

        <!-- Top ASNs by Connection Count -->
        ${this.renderTopASNs()}

        <!-- Top IPs by Bandwidth -->
        ${this.renderTopIPsByBandwidth()}

        <!-- Domain Activity -->
        ${this.renderDomainActivity()}

        <!-- Backend Protocols Section -->
        ${this.renderBackendProtocols()}
      </div>
    `;
  }

  private formatNumber(num: number): string {
    if (num >= 1000000) {
      return (num / 1000000).toFixed(1) + 'M';
    } else if (num >= 1000) {
      return (num / 1000).toFixed(1) + 'K';
    }
    return num.toFixed(0);
  }

  private formatBytes(bytes: number): string {
    const units = ['B', 'KB', 'MB', 'GB'];
    let size = bytes;
    let unitIndex = 0;

    while (size >= 1024 && unitIndex < units.length - 1) {
      size /= 1024;
      unitIndex++;
    }

    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }

  private formatBitsPerSecond(bytesPerSecond: number): string {
    const bitsPerSecond = bytesPerSecond * 8; // Convert bytes to bits
    const units = ['bit/s', 'kbit/s', 'Mbit/s', 'Gbit/s'];
    let size = bitsPerSecond;
    let unitIndex = 0;

    while (size >= 1000 && unitIndex < units.length - 1) {
      size /= 1000; // Use 1000 for bits (not 1024)
      unitIndex++;
    }

    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }

  private formatOptional(value: unknown): string {
    if (value === null || value === undefined || value === '') return '-';
    return String(value);
  }

  private formatDateTime(timestamp?: number | null): string {
    return timestamp ? new Date(timestamp).toLocaleString() : '-';
  }

  private getIpIntelligence(ip: string): interfaces.data.IIpIntelligenceRecord | undefined {
    return this.networkState.ipIntelligence?.find((record) => record.ipAddress === ip);
  }

  private getIpOrganization(record?: interfaces.data.IIpIntelligenceRecord): string {
    return record?.asnOrg || record?.registrantOrg || '';
  }

  private getIpIntelligenceColumns(ip: string): Record<string, unknown> {
    const record = this.getIpIntelligence(ip);
    const organization = this.getIpOrganization(record);
    return {
      'Intelligence': record
        ? html`<span class="intelligenceBadge">${this.formatOptional(organization || record.countryCode || 'Known')}</span>`
        : html`<span class="statusBadge warning">Enriching...</span>`,
      'ASN': record?.asn ? `AS${record.asn}` : '-',
      'Organization': this.formatOptional(organization),
      'Country': this.formatOptional(record?.countryCode || record?.country),
      'Network Range': this.formatOptional(record?.networkRange),
      'Last Seen': this.formatDateTime(record?.lastSeenAt),
    };
  }

  private getIpDataActions() {
    return [
      {
        name: 'Refresh Intelligence',
        iconName: 'lucide:refresh-cw',
        type: ['inRow', 'contextmenu'] as any,
        actionFunc: async (actionData: any) => {
          const ip = actionData.item.ip;
          await appstate.securityPolicyStatePart.dispatchAction(appstate.refreshIpIntelligenceAction, ip);
          await appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null);
        },
      },
      {
        name: 'Block IP',
        iconName: 'lucide:shield-ban',
        type: ['inRow', 'contextmenu'] as any,
        actionFunc: async (actionData: any) => {
          await this.createBlockRuleDialog('ip', actionData.item.ip, 'Blocked from Network Activity');
        },
      },
      {
        name: 'Block Network Range',
        iconName: 'lucide:network',
        type: ['contextmenu'] as any,
        actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)?.networkRange),
        actionFunc: async (actionData: any) => {
          const record = this.getIpIntelligence(actionData.item.ip);
          await this.createBlockRuleDialog('cidr', record!.networkRange!, 'Blocked network range from Network Activity');
        },
      },
      {
        name: 'Block ASN',
        iconName: 'lucide:radio-tower',
        type: ['contextmenu'] as any,
        actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)?.asn),
        actionFunc: async (actionData: any) => {
          const record = this.getIpIntelligence(actionData.item.ip);
          await this.createBlockRuleDialog('asn', String(record!.asn), 'Blocked ASN from Network Activity');
        },
      },
      {
        name: 'Block Organization',
        iconName: 'lucide:building-2',
        type: ['contextmenu'] as any,
        actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpOrganization(this.getIpIntelligence(actionData.item.ip))),
        actionFunc: async (actionData: any) => {
          const record = this.getIpIntelligence(actionData.item.ip);
          await this.createBlockRuleDialog('organization', this.getIpOrganization(record), 'Blocked organization from Network Activity');
        },
      },
      {
        name: 'View Intelligence',
        iconName: 'lucide:info',
        type: ['doubleClick', 'contextmenu'] as any,
        actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)),
        actionFunc: async (actionData: any) => {
          await this.showIpIntelligenceDetails(actionData.item.ip);
        },
      },
    ];
  }

  private getAsnDataActions() {
    return [
      {
        name: 'Block ASN',
        iconName: 'lucide:radio-tower',
        type: ['inRow', 'contextmenu'] as any,
        actionFunc: async (actionData: any) => {
          await this.createBlockRuleDialog('asn', String(actionData.item.asn), 'Blocked ASN from Network Activity');
        },
      },
      {
        name: 'Block Organization',
        iconName: 'lucide:building-2',
        type: ['contextmenu'] as any,
        actionRelevancyCheckFunc: (actionData: any) => Boolean(actionData.item.organization),
        actionFunc: async (actionData: any) => {
          await this.createBlockRuleDialog('organization', actionData.item.organization, 'Blocked organization from Network Activity');
        },
      },
    ];
  }

  private calculateThroughput(): { in: number; out: number } {
    // Use real throughput data from network state
    return {
      in: this.networkState.throughputRate.bytesInPerSecond,
      out: this.networkState.throughputRate.bytesOutPerSecond,
    };
  }

  private renderNetworkStats(): TemplateResult {
    // Use server-side requests/sec from SmartProxy's Rust engine
    const reqPerSec = this.networkState.requestsPerSecond || 0;
    const throughput = this.calculateThroughput();
    const activeConnections = this.statsState.serverStats?.activeConnections || 0;

    // Build trend data from pre-computed history (mutated in updateNetworkData, not here)
    const trendData = [...this.requestsPerSecHistory];
    while (trendData.length < 20) {
      trendData.unshift(0);
    }

    const tiles: IStatsTile[] = [
      {
        id: 'connections',
        title: 'Active Connections',
        value: activeConnections,
        type: 'number',
        icon: 'lucide:Plug',
        color: activeConnections > 100 ? '#f59e0b' : '#22c55e',
        description: `Total: ${this.formatNumber(this.statsState.serverStats?.totalConnections || 0)} connections`,
        actions: [
          {
            name: 'View Details',
            iconName: 'fa:magnifyingGlass',
            action: async () => {
            },
          },
        ],
      },
      {
        id: 'requests',
        title: 'Requests/sec',
        value: reqPerSec,
        type: 'trend',
        icon: 'lucide:ChartLine',
        color: '#3b82f6',
        trendData: trendData,
        description: `Total: ${this.formatNumber(this.networkState.requestsTotal || 0)} requests`,
      },
      {
        id: 'throughputIn',
        title: 'Throughput In',
        value: this.formatBitsPerSecond(throughput.in),
        unit: '',
        type: 'number',
        icon: 'lucide:Download',
        color: '#22c55e',
        description: `Total: ${this.formatBytes(this.networkState.totalBytes?.in || 0)}`,
      },
      {
        id: 'throughputOut',
        title: 'Throughput Out',
        value: this.formatBitsPerSecond(throughput.out),
        unit: '',
        type: 'number',
        icon: 'lucide:Upload',
        color: '#8b5cf6',
        description: `Total: ${this.formatBytes(this.networkState.totalBytes?.out || 0)}`,
      },
    ];

    return html`
      <dees-statsgrid
        .tiles=${tiles}
        .minTileWidth=${200}
      ></dees-statsgrid>
    `;
  }

  private renderProtocolCharts(): TemplateResult {
    const fp = this.networkState.frontendProtocols;
    const bp = this.networkState.backendProtocols;

    const protoColors: Record<string, string> = {
      'HTTP/1.1': '#1976d2',
      'HTTP/2': '#388e3c',
      'HTTP/3': '#7b1fa2',
      'WebSocket': '#f57c00',
      'Other': '#757575',
    };

    const buildDonutData = (dist: interfaces.data.IProtocolDistribution | null) => {
      if (!dist) return [];
      const items: Array<{ name: string; value: number; color: string }> = [];
      if (dist.h1Active > 0) items.push({ name: 'HTTP/1.1', value: dist.h1Active, color: protoColors['HTTP/1.1'] });
      if (dist.h2Active > 0) items.push({ name: 'HTTP/2', value: dist.h2Active, color: protoColors['HTTP/2'] });
      if (dist.h3Active > 0) items.push({ name: 'HTTP/3', value: dist.h3Active, color: protoColors['HTTP/3'] });
      if (dist.wsActive > 0) items.push({ name: 'WebSocket', value: dist.wsActive, color: protoColors['WebSocket'] });
      if (dist.otherActive > 0) items.push({ name: 'Other', value: dist.otherActive, color: protoColors['Other'] });
      return items;
    };

    const frontendData = buildDonutData(fp);
    const backendData = buildDonutData(bp);

    return html`
      <div class="protocolChartGrid">
        <dees-chart-donut
          .label=${'Frontend Protocols'}
          .data=${frontendData.length > 0 ? frontendData : [{ name: 'No Traffic', value: 1, color: '#757575' }]}
          .showLegend=${true}
          .showLabels=${true}
          .innerRadiusPercent=${'55%'}
          .valueFormatter=${(val: number) => `${val} active`}
        ></dees-chart-donut>
        <dees-chart-donut
          .label=${'Backend Protocols'}
          .data=${backendData.length > 0 ? backendData : [{ name: 'No Traffic', value: 1, color: '#757575' }]}
          .showLegend=${true}
          .showLabels=${true}
          .innerRadiusPercent=${'55%'}
          .valueFormatter=${(val: number) => `${val} active`}
        ></dees-chart-donut>
      </div>
    `;
  }

  private renderTopIPs(): TemplateResult {
    if (this.networkState.topIPs.length === 0) {
      return html``;
    }

    // Build per-IP bandwidth lookup
    const bandwidthByIP = new Map<string, { in: number; out: number }>();
    if (this.networkState.throughputByIP) {
      for (const entry of this.networkState.throughputByIP) {
        bandwidthByIP.set(entry.ip, { in: entry.in, out: entry.out });
      }
    }

    // Calculate total connections across all top IPs
    const totalConnections = this.networkState.topIPs.reduce((sum, ipData) => sum + ipData.count, 0);

    return html`
      <dees-table
        .data=${this.networkState.topIPs}
        .rowKey=${'ip'}
        .highlightUpdates=${'flash'}
        .displayFunction=${(ipData: { ip: string; count: number }) => {
          const bw = bandwidthByIP.get(ipData.ip);
          return {
            'IP Address': ipData.ip,
            'Connections': ipData.count,
            'Bandwidth In': bw ? this.formatBitsPerSecond(bw.in) : '0 bit/s',
            'Bandwidth Out': bw ? this.formatBitsPerSecond(bw.out) : '0 bit/s',
            'Share': totalConnections > 0 ? ((ipData.count / totalConnections) * 100).toFixed(1) + '%' : '0%',
            ...this.getIpIntelligenceColumns(ipData.ip),
          };
        }}
        .dataActions=${this.getIpDataActions()}
        heading1="Top Connected IPs"
        heading2="IPs with most active connections, bandwidth, and intelligence"
        searchable
        .showColumnFilters=${true}
        .pagination=${false}
        dataName="ip"
      ></dees-table>
    `;
  }

  private renderTopASNs(): TemplateResult {
    if (!this.networkState.topASNs || this.networkState.topASNs.length === 0) {
      return html``;
    }

    return html`
      <dees-table
        .data=${this.networkState.topASNs}
        .rowKey=${'asn'}
        .highlightUpdates=${'flash'}
        .displayFunction=${(asnData: appstate.INetworkState['topASNs'][number]) => {
          return {
            'ASN': `AS${asnData.asn}`,
            'Organization': this.formatOptional(asnData.organization),
            'Connections': asnData.activeConnections,
            'IPs': asnData.ipCount,
            'Bandwidth In': this.formatBitsPerSecond(asnData.bytesInPerSecond),
            'Bandwidth Out': this.formatBitsPerSecond(asnData.bytesOutPerSecond),
            'Total Bandwidth': this.formatBitsPerSecond(asnData.bytesInPerSecond + asnData.bytesOutPerSecond),
            'Country': this.formatOptional(asnData.country),
            'Sample IPs': asnData.sampleIps.join(', '),
          };
        }}
        .dataActions=${this.getAsnDataActions()}
        heading1="Top Connected ASNs"
        heading2="Organizations causing the most active connections across observed IPs"
        searchable
        .showColumnFilters=${true}
        .pagination=${false}
        dataName="ASN"
      ></dees-table>
    `;
  }

  private renderTopIPsByBandwidth(): TemplateResult {
    if (!this.networkState.topIPsByBandwidth || this.networkState.topIPsByBandwidth.length === 0) {
      return html``;
    }

    return html`
      <dees-table
        .data=${this.networkState.topIPsByBandwidth}
        .rowKey=${'ip'}
        .highlightUpdates=${'flash'}
        .displayFunction=${(ipData: { ip: string; count: number; bwIn: number; bwOut: number }) => {
          return {
            'IP Address': ipData.ip,
            'Bandwidth In': this.formatBitsPerSecond(ipData.bwIn),
            'Bandwidth Out': this.formatBitsPerSecond(ipData.bwOut),
            'Total Bandwidth': this.formatBitsPerSecond(ipData.bwIn + ipData.bwOut),
            'Connections': ipData.count,
            ...this.getIpIntelligenceColumns(ipData.ip),
          };
        }}
        .dataActions=${this.getIpDataActions()}
        heading1="Top IPs by Bandwidth"
        heading2="IPs with highest throughput and intelligence"
        searchable
        .showColumnFilters=${true}
        .pagination=${false}
        dataName="ip"
      ></dees-table>
    `;
  }

  private renderDomainActivity(): TemplateResult {
    if (!this.networkState.domainActivity || this.networkState.domainActivity.length === 0) {
      return html``;
    }

    return html`
      <dees-table
        .data=${this.networkState.domainActivity}
        .rowKey=${'domain'}
        .highlightUpdates=${'flash'}
        .displayFunction=${(item: interfaces.data.IDomainActivity) => {
          const totalBytesPerMin = (item.bytesInPerSecond + item.bytesOutPerSecond) * 60;
          return {
            'Domain': item.domain,
            'Throughput In': this.formatBitsPerSecond(item.bytesInPerSecond),
            'Throughput Out': this.formatBitsPerSecond(item.bytesOutPerSecond),
            'Transferred / min': this.formatBytes(totalBytesPerMin),
            'Connections': item.activeConnections,
            'Req/s': item.requestsPerSecond != null ? item.requestsPerSecond.toFixed(1) : '-',
            'Req/min': item.requestsLastMinute != null ? item.requestsLastMinute.toFixed(0) : '-',
            'Requests': item.requestCount?.toLocaleString() ?? '0',
            'Routes': item.routeCount,
          };
        }}
        heading1="Domain Activity"
        heading2="Per-domain network activity from request-level metrics"
        searchable
        .showColumnFilters=${true}
        .pagination=${false}
        dataName="domain"
      ></dees-table>
    `;
  }

  private renderBackendProtocols(): TemplateResult {
    const backends = this.networkState.backends;
    if (!backends || backends.length === 0) {
      return html``;
    }

    return html`
      <dees-table
        .data=${backends}
        .rowKey=${'id'}
        .highlightUpdates=${'flash'}
        .displayFunction=${(item: interfaces.data.IBackendInfo) => {
          const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors;
          const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, '');

          return {
            'Backend': item.backend,
            'Domain': item.domain || '-',
            'Protocol': html`
              <span class="protocolBadge ${protocolClass}">${item.protocol.toUpperCase()}</span>
              ${item.h2Suppressed ? html`<span class="suppressionBadge" title="H2 suppressed: ${item.h2ConsecutiveFailures ?? 0} failures, cooldown ${item.h2CooldownRemainingSecs ?? 0}s">H2 suppressed</span>` : ''}
              ${item.h3Suppressed ? html`<span class="suppressionBadge" title="H3 suppressed: ${item.h3ConsecutiveFailures ?? 0} failures, cooldown ${item.h3CooldownRemainingSecs ?? 0}s">H3 suppressed</span>` : ''}
            `,
            'Active': item.activeConnections,
            'Total': this.formatNumber(item.totalConnections),
            'Avg Connect': item.avgConnectTimeMs > 0 ? `${item.avgConnectTimeMs.toFixed(1)}ms` : '-',
            'Pool Hit Rate': item.poolHitRate > 0 ? `${(item.poolHitRate * 100).toFixed(1)}%` : '-',
            'Errors': totalErrors > 0
              ? html`<span class="statusBadge error">${totalErrors}</span>`
              : html`<span class="statusBadge success">0</span>`,
            'Cache Age': item.cacheAgeSecs != null ? `${Math.round(item.cacheAgeSecs)}s` : '-',
          };
        }}
        .dataActions=${[
          {
            name: 'View Details',
            iconName: 'lucide:info',
            type: ['inRow', 'doubleClick', 'contextmenu'] as any,
            actionFunc: async (actionData: any) => {
              await this.showBackendDetails(actionData.item);
            }
          }
        ]}
        heading1="Backend Protocols"
        heading2="Auto-detected backend protocols and connection pool health"
        searchable
        .showColumnFilters=${true}
        .pagination=${false}
        dataName="backend"
      ></dees-table>
    `;
  }

  private async showBackendDetails(backend: interfaces.data.IBackendInfo) {
    const { DeesModal } = await import('@design.estate/dees-catalog');

    await DeesModal.createAndShow({
      heading: `Backend: ${backend.backend}`,
      content: html`
        <div style="padding: 20px;">
          <dees-dataview-codebox
            .heading=${'Backend Details'}
            progLang="json"
            .codeToDisplay=${JSON.stringify({
              backend: backend.backend,
              domain: backend.domain,
              protocol: backend.protocol,
              activeConnections: backend.activeConnections,
              totalConnections: backend.totalConnections,
              avgConnectTimeMs: backend.avgConnectTimeMs,
              poolHitRate: backend.poolHitRate,
              errors: {
                connect: backend.connectErrors,
                handshake: backend.handshakeErrors,
                request: backend.requestErrors,
                h2Failures: backend.h2Failures,
              },
              suppression: {
                h2Suppressed: backend.h2Suppressed,
                h3Suppressed: backend.h3Suppressed,
                h2CooldownRemainingSecs: backend.h2CooldownRemainingSecs,
                h3CooldownRemainingSecs: backend.h3CooldownRemainingSecs,
                h2ConsecutiveFailures: backend.h2ConsecutiveFailures,
                h3ConsecutiveFailures: backend.h3ConsecutiveFailures,
              },
              h3Port: backend.h3Port,
              cacheAgeSecs: backend.cacheAgeSecs,
            }, null, 2)}
          ></dees-dataview-codebox>
        </div>
      `,
      menuOptions: [
        {
          name: 'Copy Backend Key',
          iconName: 'lucide:Copy',
          action: async () => {
            await navigator.clipboard.writeText(backend.backend);
          }
        }
      ]
    });
  }

  private getDropdownKey(value: any): string {
    return typeof value === 'string' ? value : value?.key || '';
  }

  private async createBlockRuleDialog(
    type: interfaces.data.TSecurityBlockRuleType,
    value: string,
    reason: string,
  ): Promise<void> {
    const { DeesModal } = await import('@design.estate/dees-catalog');
    const typeOptions = [
      { key: 'ip', option: 'IP address' },
      { key: 'cidr', option: 'CIDR / network range' },
      { key: 'asn', option: 'ASN' },
      { key: 'organization', option: 'Organization' },
    ];
    const matchModeOptions = [
      { key: 'contains', option: 'Organization contains value' },
      { key: 'exact', option: 'Organization exactly matches value' },
    ];

    await DeesModal.createAndShow({
      heading: 'Create Security Block Rule',
      content: html`
        <dees-form>
          <dees-input-dropdown
            .key=${'type'}
            .label=${'Rule Type'}
            .options=${typeOptions}
            .selectedOption=${typeOptions.find((option) => option.key === type)}
          ></dees-input-dropdown>
          <dees-input-text .key=${'value'} .label=${'Value'} .value=${value} .required=${true}></dees-input-text>
          <dees-input-dropdown
            .key=${'matchMode'}
            .label=${'Organization Match Mode'}
            .description=${'Only used for organization rules'}
            .options=${matchModeOptions}
            .selectedOption=${matchModeOptions[0]}
          ></dees-input-dropdown>
          <dees-input-text .key=${'reason'} .label=${'Reason'} .value=${reason}></dees-input-text>
          <dees-input-checkbox .key=${'enabled'} .label=${'Enable immediately'} .value=${true}></dees-input-checkbox>
        </dees-form>
      `,
      menuOptions: [
        { name: 'Cancel', iconName: 'lucide:x', action: async (modalArg: any) => modalArg.destroy() },
        {
          name: 'Create',
          iconName: 'lucide:shield-ban',
          action: async (modalArg: any) => {
            const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
            if (!form) return;
            const data = await form.collectFormData();
            const selectedType = this.getDropdownKey(data.type) as interfaces.data.TSecurityBlockRuleType;
            const selectedValue = String(data.value || '').trim();
            if (!selectedType || !selectedValue) return;
            const matchMode = selectedType === 'organization'
              ? this.getDropdownKey(data.matchMode) as interfaces.data.TSecurityBlockRuleMatchMode
              : undefined;
            await appstate.securityPolicyStatePart.dispatchAction(appstate.createSecurityBlockRuleAction, {
              type: selectedType,
              value: selectedValue,
              matchMode,
              reason: String(data.reason || '').trim() || undefined,
              enabled: data.enabled !== false,
            });
            await appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null);
            await modalArg.destroy();
          },
        },
      ],
    });
  }

  private async showIpIntelligenceDetails(ip: string): Promise<void> {
    const record = this.getIpIntelligence(ip);
    if (!record) return;
    const { DeesModal } = await import('@design.estate/dees-catalog');

    await DeesModal.createAndShow({
      heading: `IP Intelligence: ${ip}`,
      content: html`
        <div style="padding: 20px;">
          <dees-dataview-codebox
            .heading=${'Intelligence Record'}
            progLang="json"
            .codeToDisplay=${JSON.stringify(record, null, 2)}
          ></dees-dataview-codebox>
        </div>
      `,
      menuOptions: [
        {
          name: 'Copy Abuse Contact',
          iconName: 'lucide:copy',
          action: async () => {
            if (record.abuseContact) await navigator.clipboard.writeText(record.abuseContact);
          },
        },
        {
          name: 'Block IP',
          iconName: 'lucide:shield-ban',
          action: async () => {
            await this.createBlockRuleDialog('ip', record.ipAddress, 'Blocked from IP intelligence details');
          },
        },
      ],
    });
  }

  private async updateNetworkData() {
    // Track requests/sec history for the trend sparkline (moved out of render)
    const reqPerSec = this.networkState.requestsPerSecond || 0;
    this.requestsPerSecHistory.push(reqPerSec);
    if (this.requestsPerSecHistory.length > 20) {
      this.requestsPerSecHistory.shift();
    }

    // Load server-side throughput history into chart (once)
    if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {
      this.loadThroughputHistory();
    }
  }

  private startTrafficUpdateTimer() {
    this.stopTrafficUpdateTimer(); // Clear any existing timer
    this.trafficUpdateTimer = setInterval(() => {
      this.addTrafficDataPoint();
    }, OpsViewNetworkActivity.UPDATE_INTERVAL_MS);
  }

  private addTrafficDataPoint() {
    const now = Date.now();

    // Throttle chart updates to avoid excessive re-renders
    if (now - this.lastChartUpdate < this.chartUpdateThreshold) {
      return;
    }

    const throughput = this.calculateThroughput();
    if (this.networkState.lastUpdated && now - this.networkState.lastUpdated > 3000) {
      return;
    }

    // Convert to Mbps (bytes * 8 / 1,000,000)
    const throughputInMbps = (throughput.in * 8) / 1000000;
    const throughputOutMbps = (throughput.out * 8) / 1000000;

    // Add new data points
    const timestamp = new Date(now).toISOString();

    const newDataPointIn = {
      x: timestamp,
      y: Math.round(throughputInMbps * 10) / 10
    };

    const newDataPointOut = {
      x: timestamp,
      y: Math.round(throughputOutMbps * 10) / 10
    };

    // In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
    if (this.trafficDataIn.length >= OpsViewNetworkActivity.MAX_DATA_POINTS) {
      this.trafficDataIn.shift();
      this.trafficDataOut.shift();
    }
    this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
    this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];

    this.lastChartUpdate = now;
  }

  private stopTrafficUpdateTimer() {
    if (this.trafficUpdateTimer) {
      clearInterval(this.trafficUpdateTimer);
      this.trafficUpdateTimer = null;
    }
  }
}
