import type { GatewayRequest, OutboundMessage } from '../protocol.js';
import {
  makeError,
  makeResponse,
  ErrorCode,
  GATEWAY_PROTOCOL_VERSION,
  parseNodeRegisterParams,
  parseNodeLocationSetParams,
  parseNodeLocationGetParams,
  parseNodeStatusSetParams,
  parseNodePushTokenSetParams,
} from '../protocol.js';

export interface NodeRegistration {
  nodeId: string;
  role: string;
  protocolVersion: number;
  capabilities: string[];
  registeredAt: number;
}

export interface NodeConnectionState {
  identity?: string;
  node?: NodeRegistration;
  location?: NodeLocation;
  status?: NodeStatus;
  pushToken?: NodePushToken;
}

export interface NodeLocation {
  latitude: number;
  longitude: number;
  accuracyMeters?: number;
  altitudeMeters?: number;
  headingDegrees?: number;
  speedMps?: number;
  source: 'gps' | 'network' | 'manual' | 'unknown';
  capturedAt: number;
  receivedAt: number;
}

export interface NodeStatus {
  platform: 'macos' | 'ios' | 'android' | 'linux' | 'windows' | 'unknown';
  appVersion?: string;
  deviceName?: string;
  statusText?: string;
  batteryPct?: number;
  powerSource: 'ac' | 'battery' | 'unknown';
  reportedAt: number;
}

export interface NodePushToken {
  provider: 'apns' | 'fcm';
  token: string;
  topic?: string;
  environment?: 'sandbox' | 'production';
  registeredAt: number;
}

export interface NodeHandlerDeps {
  enabled: boolean;
  locationEnabled: boolean;
  pushEnabled: boolean;
  allowedRoles: string[];
  featureGates: Record<string, boolean>;
  getConnectionState: (connectionId: string) => NodeConnectionState | undefined;
  setNodeRegistration: (connectionId: string, registration: NodeRegistration) => void;
  setNodeLocation: (connectionId: string, location: NodeLocation) => void;
  setNodeStatus: (connectionId: string, status: NodeStatus) => void;
  setNodePushToken: (connectionId: string, pushToken: NodePushToken) => void;
}

export function createNodeHandlers(deps: NodeHandlerDeps) {
  return {
    'node.register': async (request: GatewayRequest): Promise<OutboundMessage> => {
      if (!deps.enabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node RPC is disabled');
      }

      const parsed = parseNodeRegisterParams(request.params);
      if (!parsed) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'Invalid node.register params');
      }

      if (deps.allowedRoles.length > 0 && !deps.allowedRoles.includes(parsed.role)) {
        return makeError(request.id, ErrorCode.AuthFailed, `Node role '${parsed.role}' is not allowed`);
      }

      const negotiatedVersion = Math.min(GATEWAY_PROTOCOL_VERSION, parsed.protocolVersion);
      if (negotiatedVersion < 1) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'Unsupported protocolVersion');
      }

      const dedupedCapabilities = Array.from(new Set(parsed.capabilities.map((entry) => entry.trim()).filter(Boolean)));
      deps.setNodeRegistration(parsed.connectionId, {
        nodeId: parsed.nodeId,
        role: parsed.role,
        protocolVersion: parsed.protocolVersion,
        capabilities: dedupedCapabilities,
        registeredAt: Date.now(),
      });

      const enabledCapabilities = dedupedCapabilities.filter((capability) => deps.featureGates[capability] !== false);
      return makeResponse(request.id, {
        registered: true,
        node: {
          id: parsed.nodeId,
          role: parsed.role,
        },
        protocol: {
          serverVersion: GATEWAY_PROTOCOL_VERSION,
          clientVersion: parsed.protocolVersion,
          negotiatedVersion,
        },
        capabilities: {
          declared: dedupedCapabilities,
          enabled: enabledCapabilities,
        },
      });
    },

    'node.capabilities.get': async (request: GatewayRequest): Promise<OutboundMessage> => {
      if (!deps.enabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node RPC is disabled');
      }

      const params = request.params as { connectionId?: string } | undefined;
      const connectionId = params?.connectionId;
      if (!connectionId) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'connectionId is required');
      }

      const state = deps.getConnectionState(connectionId);
      if (!state?.node) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node is not registered for this connection');
      }

      const enabledCapabilities = state.node.capabilities.filter((capability) => deps.featureGates[capability] !== false);
      return makeResponse(request.id, {
        protocol: {
          serverVersion: GATEWAY_PROTOCOL_VERSION,
          nodeVersion: state.node.protocolVersion,
          negotiatedVersion: Math.min(GATEWAY_PROTOCOL_VERSION, state.node.protocolVersion),
        },
        node: {
          id: state.node.nodeId,
          role: state.node.role,
          registeredAt: state.node.registeredAt,
        },
        capabilities: {
          declared: state.node.capabilities,
          enabled: enabledCapabilities,
          featureGates: deps.featureGates,
        },
      });
    },

    'node.location.set': async (request: GatewayRequest): Promise<OutboundMessage> => {
      if (!deps.enabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node RPC is disabled');
      }
      if (!deps.locationEnabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node location access is disabled');
      }

      const parsed = parseNodeLocationSetParams(request.params);
      if (!parsed) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'Invalid node.location.set params');
      }

      const state = deps.getConnectionState(parsed.connectionId);
      if (!state?.node) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node is not registered for this connection');
      }

      const location: NodeLocation = {
        latitude: parsed.latitude,
        longitude: parsed.longitude,
        accuracyMeters: parsed.accuracyMeters,
        altitudeMeters: parsed.altitudeMeters,
        headingDegrees: parsed.headingDegrees,
        speedMps: parsed.speedMps,
        source: parsed.source ?? 'unknown',
        capturedAt: parsed.capturedAt ?? Date.now(),
        receivedAt: Date.now(),
      };
      deps.setNodeLocation(parsed.connectionId, location);

      return makeResponse(request.id, {
        updated: true,
        node: {
          id: state.node.nodeId,
          role: state.node.role,
        },
        location,
      });
    },

    'node.location.get': async (request: GatewayRequest): Promise<OutboundMessage> => {
      if (!deps.enabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node RPC is disabled');
      }
      if (!deps.locationEnabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node location access is disabled');
      }

      const parsed = parseNodeLocationGetParams(request.params);
      if (!parsed) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'Invalid node.location.get params');
      }

      const state = deps.getConnectionState(parsed.connectionId);
      if (!state?.node) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node is not registered for this connection');
      }

      return makeResponse(request.id, {
        node: {
          id: state.node.nodeId,
          role: state.node.role,
        },
        location: state.location ?? null,
      });
    },

    'node.status.set': async (request: GatewayRequest): Promise<OutboundMessage> => {
      if (!deps.enabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node RPC is disabled');
      }

      const parsed = parseNodeStatusSetParams(request.params);
      if (!parsed) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'Invalid node.status.set params');
      }

      const state = deps.getConnectionState(parsed.connectionId);
      if (!state?.node) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node is not registered for this connection');
      }

      const status: NodeStatus = {
        platform: parsed.platform,
        appVersion: parsed.appVersion?.trim() || undefined,
        deviceName: parsed.deviceName?.trim() || undefined,
        statusText: parsed.statusText?.trim() || undefined,
        batteryPct: parsed.batteryPct,
        powerSource: parsed.powerSource ?? 'unknown',
        reportedAt: Date.now(),
      };

      deps.setNodeStatus(parsed.connectionId, status);

      return makeResponse(request.id, {
        updated: true,
        node: {
          id: state.node.nodeId,
          role: state.node.role,
        },
        status,
      });
    },

    'node.push_token.set': async (request: GatewayRequest): Promise<OutboundMessage> => {
      if (!deps.enabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node RPC is disabled');
      }
      if (!deps.pushEnabled) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node push token registration is disabled');
      }

      const parsed = parseNodePushTokenSetParams(request.params);
      if (!parsed) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'Invalid node.push_token.set params');
      }

      const state = deps.getConnectionState(parsed.connectionId);
      if (!state?.node) {
        return makeError(request.id, ErrorCode.AuthFailed, 'Node is not registered for this connection');
      }

      const pushToken: NodePushToken = {
        provider: parsed.provider,
        token: parsed.token,
        topic: parsed.topic || undefined,
        environment: parsed.provider === 'apns' ? (parsed.environment ?? 'production') : undefined,
        registeredAt: Date.now(),
      };
      deps.setNodePushToken(parsed.connectionId, pushToken);

      return makeResponse(request.id, {
        updated: true,
        node: {
          id: state.node.nodeId,
          role: state.node.role,
        },
        push: {
          provider: pushToken.provider,
          tokenPreview: maskToken(pushToken.token),
          topic: pushToken.topic,
          environment: pushToken.environment,
          registeredAt: pushToken.registeredAt,
        },
      });
    },

    'system.capabilities': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const params = request.params as { connectionId?: string } | undefined;
      const connectionId = params?.connectionId;
      const state = connectionId ? deps.getConnectionState(connectionId) : undefined;
      return makeResponse(request.id, {
        protocol: {
          version: GATEWAY_PROTOCOL_VERSION,
        },
        nodes: {
          enabled: deps.enabled,
          locationEnabled: deps.locationEnabled,
          pushEnabled: deps.pushEnabled,
          allowedRoles: deps.allowedRoles,
          registered: Boolean(state?.node),
          role: state?.node?.role,
          nodeId: state?.node?.nodeId,
        },
        featureGates: deps.featureGates,
      });
    },
  };
}

function maskToken(token: string): string {
  if (token.length <= 8) {
    return '****';
  }
  const suffix = token.slice(-8);
  return `***${suffix}`;
}
