import * as plugins from '../plugins.js';
import * as interfaces from '../../ts_interfaces/index.js';
import { runBackgroundRefresh } from './shared.js';
import { getActionContext, loginStatePart, logoutAction } from './login.js';
import { uiStatePart } from './ui.js';
import { statsStatePart } from './stats.js';
import { logStatePart, type ILogState } from './logs.js';
import { networkStatePart, refreshNetworkIpIntelligence } from './network.js';
import { securityPolicyStatePart, fetchSecurityPolicyAction } from './security.js';
import { certificateStatePart, fetchCertificateOverviewAction } from './certificates.js';
import { remoteIngressStatePart, fetchRemoteIngressAction } from './remoteingress.js';
import { vpnStatePart, fetchVpnAction } from './vpn.js';


// ============================================================================
// TypedSocket Client for Real-time Log Streaming
// ============================================================================

let socketClient: plugins.typedsocket.TypedSocket | null = null;
const socketRouter = new plugins.domtools.plugins.typedrequest.TypedRouter();

// Batched log entry handler — buffers incoming entries and flushes once per animation frame
let logEntryBuffer: interfaces.data.ILogEntry[] = [];
let logFlushScheduled = false;

function flushLogEntries() {
  logFlushScheduled = false;
  if (logEntryBuffer.length === 0) return;
  const current = logStatePart.getState()!;
  const updated = [...current.recentLogs, ...logEntryBuffer];
  logEntryBuffer = [];
  // Cap at 2000 entries
  if (updated.length > 2000) {
    updated.splice(0, updated.length - 2000);
  }
  logStatePart.setState({ ...current, recentLogs: updated } as ILogState);
}

// Register handler for pushed log entries from the server
socketRouter.addTypedHandler(
  new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushLogEntry>(
    'pushLogEntry',
    async (dataArg) => {
      logEntryBuffer.push(dataArg.entry);
      if (!logFlushScheduled) {
        logFlushScheduled = true;
        requestAnimationFrame(flushLogEntries);
      }
      return {};
    }
  )
);

async function connectSocket() {
  if (socketClient) return;
  try {
    socketClient = await plugins.typedsocket.TypedSocket.createClient(
      socketRouter,
      plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl(),
    );
    await socketClient.setTag('role', 'ops_dashboard');
  } catch (err) {
    console.error('TypedSocket connection failed:', err);
    socketClient = null;
  }
}

async function disconnectSocket() {
  if (socketClient) {
    try {
      await socketClient.stop();
    } catch {
      // ignore disconnect errors
    }
    socketClient = null;
  }
}

// In-flight guard to prevent concurrent refresh requests
let isRefreshing = false;

// Combined refresh action for efficient polling
async function dispatchCombinedRefreshAction() {
  if (isRefreshing) return;
  isRefreshing = true;
  try {
    await dispatchCombinedRefreshActionInner();
  } finally {
    isRefreshing = false;
  }
}

async function dispatchCombinedRefreshActionInner() {
  const context = getActionContext();
  if (!context.identity) return;
  const currentView = uiStatePart.getState()!.activeView;
  const currentSubview = uiStatePart.getState()!.activeSubview;

  try {
    // Always fetch basic stats for dashboard widgets
    const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetCombinedMetrics
    >('/typedrequest', 'getCombinedMetrics');

    const combinedResponse = await combinedRequest.fire({
      identity: context.identity,
      sections: {
        server: true,
        email: true,
        dns: true,
        security: true,
        network: currentView === 'network' && currentSubview === 'activity',
        radius: true,
        vpn: true,
      },
    });

    // Update all stats from combined response
    const currentStatsState = statsStatePart.getState()!;
    statsStatePart.setState({
      ...currentStatsState,
      serverStats: combinedResponse.metrics.server || currentStatsState.serverStats,
      emailStats: combinedResponse.metrics.email || currentStatsState.emailStats,
      dnsStats: combinedResponse.metrics.dns || currentStatsState.dnsStats,
      securityMetrics: combinedResponse.metrics.security || currentStatsState.securityMetrics,
      radiusStats: combinedResponse.metrics.radius || currentStatsState.radiusStats,
      vpnStats: combinedResponse.metrics.vpn || currentStatsState.vpnStats,
      lastUpdated: Date.now(),
      isLoading: false,
      error: null,
    });

    // Update network stats if included
    if (combinedResponse.metrics.network && currentView === 'network') {
      const network = combinedResponse.metrics.network;
      const connectionsByIP: { [ip: string]: number } = {};

      // Build connectionsByIP from connectionDetails (now populated with real per-IP data)
      network.connectionDetails.forEach(conn => {
        connectionsByIP[conn.remoteAddress] = (connectionsByIP[conn.remoteAddress] || 0) + (conn.connectionCount || 1);
      });

      // Build connections from connectionDetails (real per-IP aggregates)
      const connections: interfaces.data.IConnectionInfo[] = network.connectionDetails.map((conn, i) => ({
        id: `ip-${conn.remoteAddress}`,
        remoteAddress: conn.remoteAddress,
        localAddress: 'server',
        startTime: conn.startTime,
        protocol: conn.protocol as any,
        state: conn.state as any,
        bytesReceived: conn.bytesIn,
        bytesSent: conn.bytesOut,
        connectionCount: conn.connectionCount,
      }));

      networkStatePart.setState({
        ...networkStatePart.getState()!,
        connections,
        connectionsByIP,
        throughputRate: {
          bytesInPerSecond: network.totalBandwidth.in,
          bytesOutPerSecond: network.totalBandwidth.out,
        },
        totalBytes: network.totalBytes || { in: 0, out: 0 },
        topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.connections })),
        topIPsByBandwidth: (network.topEndpointsByBandwidth || []).map(e => ({
          ip: e.endpoint,
          count: e.connections,
          bwIn: e.bandwidth?.in || 0,
          bwOut: e.bandwidth?.out || 0,
        })),
        topASNs: network.topASNs || [],
        throughputByIP: network.topEndpoints.map(e => ({ ip: e.endpoint, in: e.bandwidth?.in || 0, out: e.bandwidth?.out || 0 })),
        domainActivity: network.domainActivity || [],
        throughputHistory: network.throughputHistory || [],
        requestsPerSecond: network.requestsPerSecond || 0,
        requestsTotal: network.requestsTotal || 0,
        backends: network.backends || [],
        frontendProtocols: network.frontendProtocols || null,
        backendProtocols: network.backendProtocols || null,
        lastUpdated: Date.now(),
        isLoading: false,
        error: null,
      });

      refreshNetworkIpIntelligence(context.identity, [
        ...network.connectionDetails.map((conn) => conn.remoteAddress),
        ...network.topEndpoints.map((endpoint) => endpoint.endpoint),
        ...(network.topEndpointsByBandwidth || []).map((endpoint) => endpoint.endpoint),
      ]);
    }

    if (currentView === 'security') {
      runBackgroundRefresh('securityPolicy', 'Security policy refresh failed:', async () => {
        await securityPolicyStatePart.dispatchAction(fetchSecurityPolicyAction, null);
      });
    }

    // Refresh certificate data if on Domains > Certificates subview
    if (currentView === 'domains' && currentSubview === 'certificates') {
      runBackgroundRefresh('certificates', 'Certificate refresh failed:', async () => {
        await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
      });
    }

    // Refresh remote ingress data if on the Network → Remote Ingress subview
    if (currentView === 'network' && currentSubview === 'remoteingress') {
      runBackgroundRefresh('remoteIngress', 'Remote ingress refresh failed:', async () => {
        await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
      });
    }

    // Refresh VPN data if on the Network → VPN subview
    if (currentView === 'network' && currentSubview === 'vpn') {
      runBackgroundRefresh('vpn', 'VPN refresh failed:', async () => {
        await vpnStatePart.dispatchAction(fetchVpnAction, null);
      });
    }
  } catch (error) {
    console.error('Combined refresh failed:', error);
    // If the error looks like an auth failure (invalid JWT), force re-login
    const errMsg = String(error);
    if (errMsg.includes('invalid') || errMsg.includes('unauthorized') || errMsg.includes('401')) {
      await loginStatePart.dispatchAction(logoutAction, null);
      window.location.reload();
    }
  }
}

// Create a proper action for the combined refresh so we can use createScheduledAction
const combinedRefreshAction = statsStatePart.createAction<void>(async (statePartArg) => {
  await dispatchCombinedRefreshAction();
  // Return current state — dispatchCombinedRefreshAction already updates all state parts directly
  return statePartArg.getState()!;
});

// Scheduled refresh process with autoPause: 'visibility' — automatically pauses when tab is hidden
let refreshProcess: ReturnType<typeof statsStatePart.createScheduledAction> | null = null;

const startAutoRefresh = () => {
  const uiState = uiStatePart.getState()!;
  const loginState = loginStatePart.getState()!;

  if (uiState.autoRefresh && loginState.isLoggedIn) {
    // Dispose old process if interval changed or not running
    if (refreshProcess) {
      refreshProcess.dispose();
      refreshProcess = null;
    }
    refreshProcess = statsStatePart.createScheduledAction({
      action: combinedRefreshAction,
      payload: undefined,
      intervalMs: uiState.refreshInterval,
      autoPause: 'visibility',
    });
  } else {
    if (refreshProcess) {
      refreshProcess.dispose();
      refreshProcess = null;
    }
  }
};

// Watch for relevant changes
let previousAutoRefresh = uiStatePart.getState()!.autoRefresh;
let previousRefreshInterval = uiStatePart.getState()!.refreshInterval;
let previousIsLoggedIn = loginStatePart.getState()!.isLoggedIn;

uiStatePart.select((s) => ({ autoRefresh: s.autoRefresh, refreshInterval: s.refreshInterval }))
  .subscribe((state) => {
    if (state.autoRefresh !== previousAutoRefresh ||
        state.refreshInterval !== previousRefreshInterval) {
      previousAutoRefresh = state.autoRefresh;
      previousRefreshInterval = state.refreshInterval;
      startAutoRefresh();
    }
  });

loginStatePart.select((s) => s.isLoggedIn).subscribe((isLoggedIn) => {
  if (isLoggedIn !== previousIsLoggedIn) {
    previousIsLoggedIn = isLoggedIn;
    startAutoRefresh();

    // Connect/disconnect TypedSocket based on login state
    if (isLoggedIn) {
      connectSocket();
    } else {
      disconnectSocket();
    }
  }
});

// Pause/resume WebSocket when tab visibility changes
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    disconnectSocket();
  } else if (loginStatePart.getState()!.isLoggedIn) {
    connectSocket();
  }
});

// Initial start
startAutoRefresh();

// Connect TypedSocket if already logged in (e.g., persistent session)
if (loginStatePart.getState()!.isLoggedIn) {
  connectSocket();
}
