import { describe, expect, it } from 'vitest';
import { createNodeHandlers, type NodeConnectionState } from './node.js';

describe('node handlers', () => {
  it('registers node and returns negotiated capabilities', async () => {
    const states = new Map<string, NodeConnectionState>([['conn-1', {}]]);
    const handlers = createNodeHandlers({
      enabled: true,
      locationEnabled: true,
      pushEnabled: true,
      allowedRoles: ['companion'],
      featureGates: { 'ui.canvas': true, 'dangerous.write': false },
      getConnectionState: (connectionId) => states.get(connectionId),
      setNodeRegistration: (connectionId, registration) => {
        const prior = states.get(connectionId) ?? {};
        states.set(connectionId, { ...prior, node: registration });
      },
      setNodeLocation: (connectionId, location) => {
        const prior = states.get(connectionId) ?? {};
        states.set(connectionId, { ...prior, location });
      },
      setNodeStatus: (connectionId, status) => {
        const prior = states.get(connectionId) ?? {};
        states.set(connectionId, { ...prior, status });
      },
      setNodePushToken: (connectionId, pushToken) => {
        const prior = states.get(connectionId) ?? {};
        states.set(connectionId, { ...prior, pushToken });
      },
    });

    const result = await handlers['node.register']({
      id: 1,
      method: 'node.register',
      params: {
        connectionId: 'conn-1',
        nodeId: 'node-a',
        role: 'companion',
        protocolVersion: 1,
        capabilities: ['ui.canvas', 'dangerous.write'],
      },
    });

    expect((result as { result: { registered: boolean } }).result.registered).toBe(true);
    const caps = (result as { result: { capabilities: { enabled: string[] } } }).result.capabilities.enabled;
    expect(caps).toEqual(['ui.canvas']);
    expect(states.get('conn-1')?.node?.role).toBe('companion');
  });

  it('denies registration for disallowed roles', async () => {
    const states = new Map<string, NodeConnectionState>([['conn-1', {}]]);
    const handlers = createNodeHandlers({
      enabled: true,
      locationEnabled: true,
      pushEnabled: true,
      allowedRoles: ['companion'],
      featureGates: {},
      getConnectionState: (connectionId) => states.get(connectionId),
      setNodeRegistration: () => {},
      setNodeLocation: () => {},
      setNodeStatus: () => {},
      setNodePushToken: () => {},
    });

    const result = await handlers['node.register']({
      id: 2,
      method: 'node.register',
      params: {
        connectionId: 'conn-1',
        nodeId: 'node-a',
        role: 'observer',
        protocolVersion: 1,
        capabilities: [],
      },
    });

    expect((result as { error: { message: string } }).error.message).toContain('not allowed');
  });

  it('returns capabilities for registered node connections', async () => {
    const states = new Map<string, NodeConnectionState>([['conn-1', {
      node: {
        nodeId: 'node-a',
        role: 'companion',
        protocolVersion: 1,
        capabilities: ['ui.canvas'],
        registeredAt: Date.now(),
      },
    }]]);
    const handlers = createNodeHandlers({
      enabled: true,
      locationEnabled: true,
      pushEnabled: true,
      allowedRoles: ['companion'],
      featureGates: { 'ui.canvas': true },
      getConnectionState: (connectionId) => states.get(connectionId),
      setNodeRegistration: () => {},
      setNodeLocation: () => {},
      setNodeStatus: () => {},
      setNodePushToken: () => {},
    });

    const result = await handlers['node.capabilities.get']({
      id: 3,
      method: 'node.capabilities.get',
      params: { connectionId: 'conn-1' },
    });

    const enabled = (result as { result: { capabilities: { enabled: string[] } } }).result.capabilities.enabled;
    expect(enabled).toEqual(['ui.canvas']);
  });

  it('stores location updates and returns latest location', async () => {
    const states = new Map<string, NodeConnectionState>([['conn-1', {
      node: {
        nodeId: 'node-a',
        role: 'companion',
        protocolVersion: 1,
        capabilities: ['location'],
        registeredAt: Date.now(),
      },
    }]]);
    const handlers = createNodeHandlers({
      enabled: true,
      locationEnabled: true,
      pushEnabled: true,
      allowedRoles: ['companion'],
      featureGates: {},
      getConnectionState: (connectionId) => states.get(connectionId),
      setNodeRegistration: () => {},
      setNodeLocation: (connectionId, location) => {
        const prior = states.get(connectionId) ?? {};
        states.set(connectionId, { ...prior, location });
      },
      setNodeStatus: () => {},
      setNodePushToken: () => {},
    });

    const setResult = await handlers['node.location.set']({
      id: 4,
      method: 'node.location.set',
      params: {
        connectionId: 'conn-1',
        latitude: 37.7749,
        longitude: -122.4194,
        accuracyMeters: 8,
        source: 'gps',
      },
    });
    expect((setResult as { result: { updated: boolean } }).result.updated).toBe(true);

    const getResult = await handlers['node.location.get']({
      id: 5,
      method: 'node.location.get',
      params: { connectionId: 'conn-1' },
    });
    const location = (getResult as { result: { location: { latitude: number; longitude: number } } }).result.location;
    expect(location.latitude).toBe(37.7749);
    expect(location.longitude).toBe(-122.4194);
  });

  it('rejects location methods when location access is disabled', async () => {
    const states = new Map<string, NodeConnectionState>([['conn-1', {
      node: {
        nodeId: 'node-a',
        role: 'companion',
        protocolVersion: 1,
        capabilities: [],
        registeredAt: Date.now(),
      },
    }]]);
    const handlers = createNodeHandlers({
      enabled: true,
      locationEnabled: false,
      pushEnabled: false,
      allowedRoles: ['companion'],
      featureGates: {},
      getConnectionState: (connectionId) => states.get(connectionId),
      setNodeRegistration: () => {},
      setNodeLocation: () => {},
      setNodeStatus: () => {},
      setNodePushToken: () => {},
    });

    const result = await handlers['node.location.set']({
      id: 6,
      method: 'node.location.set',
      params: { connectionId: 'conn-1', latitude: 1, longitude: 2 },
    });
    expect((result as { error: { message: string } }).error.message).toContain('disabled');
  });

  it('stores companion node status updates', async () => {
    const states = new Map<string, NodeConnectionState>([['conn-1', {
      node: {
        nodeId: 'node-a',
        role: 'companion',
        protocolVersion: 1,
        capabilities: ['status'],
        registeredAt: Date.now(),
      },
    }]]);
    const handlers = createNodeHandlers({
      enabled: true,
      locationEnabled: true,
      pushEnabled: true,
      allowedRoles: ['companion'],
      featureGates: {},
      getConnectionState: (connectionId) => states.get(connectionId),
      setNodeRegistration: () => {},
      setNodeLocation: () => {},
      setNodeStatus: (connectionId, status) => {
        const prior = states.get(connectionId) ?? {};
        states.set(connectionId, { ...prior, status });
      },
      setNodePushToken: () => {},
    });

    const result = await handlers['node.status.set']({
      id: 7,
      method: 'node.status.set',
      params: {
        connectionId: 'conn-1',
        platform: 'macos',
        appVersion: '0.2.0',
        deviceName: 'Office Mac',
        batteryPct: 81,
        powerSource: 'ac',
      },
    });

    expect((result as { result: { updated: boolean } }).result.updated).toBe(true);
    expect(states.get('conn-1')?.status?.platform).toBe('macos');
    expect(states.get('conn-1')?.status?.appVersion).toBe('0.2.0');
  });

  it('registers push token and returns masked preview', async () => {
    const states = new Map<string, NodeConnectionState>([['conn-1', {
      node: {
        nodeId: 'ios-node',
        role: 'companion',
        protocolVersion: 1,
        capabilities: ['notifications'],
        registeredAt: Date.now(),
      },
    }]]);
    const handlers = createNodeHandlers({
      enabled: true,
      locationEnabled: true,
      pushEnabled: true,
      allowedRoles: ['companion'],
      featureGates: {},
      getConnectionState: (connectionId) => states.get(connectionId),
      setNodeRegistration: () => {},
      setNodeLocation: () => {},
      setNodeStatus: () => {},
      setNodePushToken: (connectionId, pushToken) => {
        const prior = states.get(connectionId) ?? {};
        states.set(connectionId, { ...prior, pushToken });
      },
    });

    const result = await handlers['node.push_token.set']({
      id: 8,
      method: 'node.push_token.set',
      params: {
        connectionId: 'conn-1',
        provider: 'apns',
        token: 'abcd1234abcd1234abcd1234abcd1234',
        topic: 'com.example.flynn',
        environment: 'sandbox',
      },
    });
    expect((result as { result: { updated: boolean } }).result.updated).toBe(true);
    expect((result as { result: { push: { tokenPreview: string } } }).result.push.tokenPreview).toContain('abcd1234');
    expect(states.get('conn-1')?.pushToken?.provider).toBe('apns');
  });

  it('accepts fcm push token registration for android companions', async () => {
    const states = new Map<string, NodeConnectionState>([['conn-1', {
      node: {
        nodeId: 'android-node',
        role: 'companion',
        protocolVersion: 1,
        capabilities: ['notifications'],
        registeredAt: Date.now(),
      },
    }]]);
    const handlers = createNodeHandlers({
      enabled: true,
      locationEnabled: true,
      pushEnabled: true,
      allowedRoles: ['companion'],
      featureGates: {},
      getConnectionState: (connectionId) => states.get(connectionId),
      setNodeRegistration: () => {},
      setNodeLocation: () => {},
      setNodeStatus: () => {},
      setNodePushToken: (connectionId, pushToken) => {
        const prior = states.get(connectionId) ?? {};
        states.set(connectionId, { ...prior, pushToken });
      },
    });

    const result = await handlers['node.push_token.set']({
      id: 9,
      method: 'node.push_token.set',
      params: {
        connectionId: 'conn-1',
        provider: 'fcm',
        token: 'fcm_abcdefghijklmnopqrstuvwxyz123456',
      },
    });
    expect((result as { result: { updated: boolean } }).result.updated).toBe(true);
    expect(states.get('conn-1')?.pushToken?.provider).toBe('fcm');
    expect(states.get('conn-1')?.pushToken?.environment).toBeUndefined();
  });
});
