'use strict'
import { createStore, applyMiddleware } from 'redux'
import Transport, {
  StoreSyncMessage,
  MessageListener,
} from '@store-sync/transport'
import StoreSync from './'
import Differ from '@store-sync/differ'

describe('storeSync', () => {
  const alwaysTheDiff = [1, 2, 3]
  class TestTransport implements Transport {
    messages: StoreSyncMessage[] = []
    listeners: MessageListener[] = []

    send = jest.fn((msg: StoreSyncMessage) => {
      this.messages.push(msg)
    })

    addMessageListener(listener: MessageListener) {
      this.listeners.push(listener)
    }

    // receiveChange simulates a message coming through out
    // actual transport. Used only for testing here
    receiveChange(msg: StoreSyncMessage) {
      this.listeners.forEach(listener => {
        listener(msg)
      })
    }
  }

  class TestDiffer implements Differ<any[]> {
    diff(a: [], b: []): any[] {
      return alwaysTheDiff // for testing purposes, we always return the same diff
    }
    apply(a: [], patch: []) {
      return [...a, ...patch]
    }
  }

  const transport = new TestTransport()
  const differ = new TestDiffer()
  const storeSync = StoreSync({ transport, differ })

  const theOneActionWeCareAbout = 'HELLO_WORLD'
  const reducer = (
    state: number[] = [],
    action: { type: string; payload: number }
  ) => {
    switch (action.type) {
      case theOneActionWeCareAbout:
        return [...state, action.payload]
    }

    return state
  }

  it('sends diffs', () => {
    const store = createStore(reducer, [], applyMiddleware(storeSync))

    store.dispatch({
      type: theOneActionWeCareAbout,
      payload: 1,
    })

    expect(store.getState()).toStrictEqual([1])
    expect(transport.send).toHaveBeenCalledWith({
      diff: alwaysTheDiff,
      datetime: expect.any(Number),
    })
  })

  it('applies diffs it receives', () => {
    const mockReducer = jest.fn(reducer)

    createStore(mockReducer, [], applyMiddleware(storeSync))

    transport.receiveChange({
      datetime: Date.now(),
      diff: alwaysTheDiff,
    })

    expect(mockReducer).toHaveBeenLastCalledWith([], {
      type: 'STORE_SYNC_UPDATE',
      payload: alwaysTheDiff,
      meta: {
        datetime: expect.any(Number),
      },
    })
  })
})
