import { SphereController, SphereEventes } from '../sphereController';
import { SphereContext } from '../sphereContext';
import { Eventer } from '@tingwu/common';
import { Controller } from '@tingwu/core';

// Mock context
const createMockContext = (): SphereContext => {
  return new SphereContext(new Controller());
};

// Test implementation of SphereController
class TestController extends SphereController<
  { count: number; name: string },
  { test: { value: number } }
> {
  key = 'test-controller';

  constructor(context: SphereContext, state: { count: number; name: string }) {
    super(context, state);
  }
}

describe('SphereController', () => {
  let context: SphereContext;
  let controller: TestController;

  beforeEach(() => {
    context = createMockContext();
    controller = new TestController(context, { count: 0, name: 'test' });
  });

  describe('constructor', () => {
    it('should initialize with context and state', () => {
      expect(controller.context).toBe(context);
      expect(controller.state).toEqual({ count: 0, name: 'test' });
      expect(controller.eventer).toBeInstanceOf(Eventer);
    });

    it('should have a key property', () => {
      expect(controller.key).toBe('test-controller');
    });
  });

  describe('getAllState', () => {
    it('should return the current state', () => {
      const state = controller.getAllState();
      expect(state).toEqual({ count: 0, name: 'test' });
    });
  });

  describe('setAllState', () => {
    it('should replace the entire state', () => {
      const newState = { count: 10, name: 'updated' };
      controller.setAllState(newState);
      expect(controller.state).toEqual(newState);
    });
  });

  describe('getState', () => {
    it('should return the value for a specific key', () => {
      expect(controller.getState('count')).toBe(0);
      expect(controller.getState('name')).toBe('test');
    });
  });

  describe('setState', () => {
    it('should update a specific state key', () => {
      controller.setState('count', 5);
      expect(controller.getState('count')).toBe(5);
      expect(controller.getState('name')).toBe('test');
    });

    it('should emit stateChange event', () => {
      const callback = jest.fn();
      controller.onStateChange(callback);

      controller.setState('count', 10);

      expect(callback).toHaveBeenCalledTimes(1);
      expect(callback).toHaveBeenCalledWith(
        {
          key: 'count',
          value: 10,
        },
        expect.any(Object) // EventResult
      );
    });
  });

  describe('onStateChange', () => {
    it('should register a state change listener', () => {
      const callback = jest.fn();
      const unsubscribe = controller.onStateChange(callback);

      controller.setState('count', 1);
      expect(callback).toHaveBeenCalledTimes(1);

      unsubscribe();
      controller.setState('count', 2);
      expect(callback).toHaveBeenCalledTimes(1); // Should not be called again
    });

    it('should support multiple listeners', () => {
      const callback1 = jest.fn();
      const callback2 = jest.fn();

      controller.onStateChange(callback1);
      controller.onStateChange(callback2);

      controller.setState('count', 5);

      expect(callback1).toHaveBeenCalledTimes(1);
      expect(callback2).toHaveBeenCalledTimes(1);
    });
  });

  describe('offStateChange', () => {
    it('should remove a specific state change listener', () => {
      const callback1 = jest.fn();
      const callback2 = jest.fn();

      controller.onStateChange(callback1);
      controller.onStateChange(callback2);

      controller.offStateChange(callback1);
      controller.setState('count', 10);

      expect(callback1).not.toHaveBeenCalled();
      expect(callback2).toHaveBeenCalledTimes(1);
    });
  });

  describe('offAllStateChange', () => {
    it('should remove all state change listeners by calling off without callback', () => {
      const callback1 = jest.fn();
      const callback2 = jest.fn();

      controller.onStateChange(callback1);
      controller.onStateChange(callback2);

      // Remove all by calling off without callback
      controller.offStateChange(callback1);
      controller.offStateChange(callback2);
      controller.setState('count', 10);

      expect(callback1).not.toHaveBeenCalled();
      expect(callback2).not.toHaveBeenCalled();
    });
  });

  describe('emitStateChange', () => {
    it('should emit state change event through eventer', () => {
      const callback = jest.fn();
      controller.onStateChange(callback);

      controller.emitStateChange({ key: 'name', value: 'new-name' });

      expect(callback).toHaveBeenCalledWith(
        {
          key: 'name',
          value: 'new-name',
        },
        expect.any(Object) // EventResult
      );
    });
  });
});

