import { describe, it, expect } from 'vitest';
import {
  isValidRequest,
  parseMessage,
  parseNodeRegisterParams,
  parseNodeLocationSetParams,
  parseNodeLocationGetParams,
  parseNodeStatusSetParams,
  parseNodePushTokenSetParams,
  makeResponse,
  makeError,
  makeEvent,
  ErrorCode,
} from './protocol.js';

describe('protocol', () => {
  describe('isValidRequest', () => {
    it('accepts valid request with params', () => {
      expect(isValidRequest({ id: 1, method: 'agent.send', params: { message: 'hello' } })).toBe(true);
    });

    it('accepts valid request without params', () => {
      expect(isValidRequest({ id: 1, method: 'system.health' })).toBe(true);
    });

    it('rejects missing id', () => {
      expect(isValidRequest({ method: 'test' })).toBe(false);
    });

    it('rejects missing method', () => {
      expect(isValidRequest({ id: 1 })).toBe(false);
    });

    it('rejects non-object', () => {
      expect(isValidRequest('not an object')).toBe(false);
      expect(isValidRequest(null)).toBe(false);
      expect(isValidRequest(42)).toBe(false);
    });

    it('rejects non-numeric id', () => {
      expect(isValidRequest({ id: 'abc', method: 'test' })).toBe(false);
    });

    it('rejects non-string method', () => {
      expect(isValidRequest({ id: 1, method: 42 })).toBe(false);
    });

    it('rejects non-object params', () => {
      expect(isValidRequest({ id: 1, method: 'test', params: 'bad' })).toBe(false);
    });
  });

  describe('parseMessage', () => {
    it('parses valid JSON into GatewayRequest', () => {
      const msg = parseMessage('{"id":1,"method":"agent.send","params":{"message":"hi"}}');
      expect(msg).toEqual({ id: 1, method: 'agent.send', params: { message: 'hi' } });
    });

    it('returns null for invalid JSON', () => {
      expect(parseMessage('not json')).toBeNull();
    });

    it('returns null for valid JSON that is not a valid request', () => {
      expect(parseMessage('{"method":"test"}')).toBeNull();
    });
  });

  describe('parseNodeRegisterParams', () => {
    it('parses valid node registration params', () => {
      const parsed = parseNodeRegisterParams({
        connectionId: 'conn-1',
        nodeId: 'node-a',
        role: 'companion',
        protocolVersion: 1,
        capabilities: ['ui.canvas', 'notifications'],
      });
      expect(parsed).toEqual({
        connectionId: 'conn-1',
        nodeId: 'node-a',
        role: 'companion',
        protocolVersion: 1,
        capabilities: ['ui.canvas', 'notifications'],
      });
    });

    it('rejects invalid node registration params', () => {
      expect(parseNodeRegisterParams({
        connectionId: 'conn-1',
        nodeId: '',
        role: 'companion',
        protocolVersion: 1,
        capabilities: [],
      })).toBeNull();
      expect(parseNodeRegisterParams({
        connectionId: 'conn-1',
        nodeId: 'node',
        role: 'companion',
        protocolVersion: 0,
        capabilities: [],
      })).toBeNull();
      expect(parseNodeRegisterParams({
        connectionId: 'conn-1',
        nodeId: 'node',
        role: 'companion',
        protocolVersion: 1,
        capabilities: [1],
      })).toBeNull();
    });
  });

  describe('parseNodeLocationSetParams', () => {
    it('parses valid node location set params', () => {
      const parsed = parseNodeLocationSetParams({
        connectionId: 'conn-1',
        latitude: 40.7128,
        longitude: -74.0060,
        accuracyMeters: 12.5,
        source: 'gps',
      });
      expect(parsed).toEqual({
        connectionId: 'conn-1',
        latitude: 40.7128,
        longitude: -74.006,
        accuracyMeters: 12.5,
        altitudeMeters: undefined,
        headingDegrees: undefined,
        speedMps: undefined,
        source: 'gps',
        capturedAt: undefined,
      });
    });

    it('rejects invalid node location set params', () => {
      expect(parseNodeLocationSetParams({
        connectionId: 'conn-1',
        latitude: 200,
        longitude: -74,
      })).toBeNull();
      expect(parseNodeLocationSetParams({
        connectionId: 'conn-1',
        latitude: 10,
        longitude: -190,
      })).toBeNull();
      expect(parseNodeLocationSetParams({
        connectionId: 'conn-1',
        latitude: 10,
        longitude: 20,
        source: 'beacon',
      })).toBeNull();
      expect(parseNodeLocationSetParams({
        connectionId: 'conn-1',
        latitude: 10,
        longitude: 20,
        headingDegrees: 361,
      })).toBeNull();
    });
  });

  describe('parseNodeLocationGetParams', () => {
    it('parses valid node location get params', () => {
      const parsed = parseNodeLocationGetParams({
        connectionId: 'conn-1',
      });
      expect(parsed).toEqual({
        connectionId: 'conn-1',
      });
    });

    it('rejects invalid node location get params', () => {
      expect(parseNodeLocationGetParams({})).toBeNull();
      expect(parseNodeLocationGetParams({ connectionId: '' })).toBeNull();
      expect(parseNodeLocationGetParams(null)).toBeNull();
    });
  });

  describe('parseNodeStatusSetParams', () => {
    it('parses valid node status set params', () => {
      const parsed = parseNodeStatusSetParams({
        connectionId: 'conn-1',
        platform: 'macos',
        appVersion: '0.1.0',
        deviceName: 'Willbook',
        statusText: 'Idle',
        batteryPct: 73,
        powerSource: 'battery',
      });
      expect(parsed).toEqual({
        connectionId: 'conn-1',
        platform: 'macos',
        appVersion: '0.1.0',
        deviceName: 'Willbook',
        statusText: 'Idle',
        batteryPct: 73,
        powerSource: 'battery',
      });
    });

    it('rejects invalid node status set params', () => {
      expect(parseNodeStatusSetParams({
        connectionId: 'conn-1',
        platform: 'beos',
      })).toBeNull();
      expect(parseNodeStatusSetParams({
        connectionId: 'conn-1',
        platform: 'macos',
        batteryPct: 120,
      })).toBeNull();
      expect(parseNodeStatusSetParams({
        connectionId: '',
        platform: 'macos',
      })).toBeNull();
    });
  });

  describe('parseNodePushTokenSetParams', () => {
    it('parses valid node push token params', () => {
      const parsed = parseNodePushTokenSetParams({
        connectionId: 'conn-1',
        provider: 'apns',
        token: 'abcd1234abcd1234abcd1234abcd1234',
        topic: 'com.example.flynn',
        environment: 'production',
      });
      expect(parsed).toEqual({
        connectionId: 'conn-1',
        provider: 'apns',
        token: 'abcd1234abcd1234abcd1234abcd1234',
        topic: 'com.example.flynn',
        environment: 'production',
      });
    });

    it('rejects invalid node push token params', () => {
      expect(parseNodePushTokenSetParams({
        connectionId: 'conn-1',
        provider: 'webpush',
        token: 'abcd1234abcd1234abcd1234abcd1234',
      })).toBeNull();
      expect(parseNodePushTokenSetParams({
        connectionId: 'conn-1',
        provider: 'apns',
        token: 'short',
      })).toBeNull();
      expect(parseNodePushTokenSetParams({
        connectionId: '',
        provider: 'apns',
        token: 'abcd1234abcd1234abcd1234abcd1234',
      })).toBeNull();
    });

    it('parses valid fcm token params for android nodes', () => {
      const parsed = parseNodePushTokenSetParams({
        connectionId: 'conn-2',
        provider: 'fcm',
        token: 'fcm_abcdefghijklmnopqrstuvwxyz123456',
      });
      expect(parsed).toEqual({
        connectionId: 'conn-2',
        provider: 'fcm',
        token: 'fcm_abcdefghijklmnopqrstuvwxyz123456',
        topic: undefined,
        environment: undefined,
      });
    });
  });

  describe('makeResponse', () => {
    it('creates a response message', () => {
      expect(makeResponse(1, { status: 'ok' })).toEqual({
        id: 1,
        result: { status: 'ok' },
      });
    });
  });

  describe('makeError', () => {
    it('creates an error message', () => {
      expect(makeError(1, ErrorCode.MethodNotFound, 'Not found')).toEqual({
        id: 1,
        error: { code: -3, message: 'Not found' },
      });
    });
  });

  describe('makeEvent', () => {
    it('creates an event message', () => {
      expect(makeEvent(1, 'content', { text: 'hello' })).toEqual({
        id: 1,
        event: 'content',
        data: { text: 'hello' },
      });
    });

    it('creates an attachment event message', () => {
      const data = { mimeType: 'image/png', data: 'iVBOR...', filename: 'screenshot.png' };
      expect(makeEvent(1, 'attachment', data)).toEqual({
        id: 1,
        event: 'attachment',
        data,
      });
    });

    it('creates an attachment event with url', () => {
      const data = { mimeType: 'application/pdf', url: 'https://example.com/doc.pdf', filename: 'doc.pdf' };
      expect(makeEvent(2, 'attachment', data)).toEqual({
        id: 2,
        event: 'attachment',
        data,
      });
    });

    it('creates a context warning event message', () => {
      const data = {
        level: 'warning',
        message: 'Context usage is 76.0%',
        budget: {
          estimatedTokens: 76000,
          contextWindow: 100000,
          remainingTokens: 24000,
          usagePct: 76,
          thresholdPct: 80,
          thresholdTokens: 80000,
          shouldCompact: false,
        },
        actions: {
          checkpointSaved: false,
          autoCompacted: false,
        },
      };
      expect(makeEvent(3, 'context_warning', data)).toEqual({
        id: 3,
        event: 'context_warning',
        data,
      });
    });
  });
});
