// @flow
import noop from 'lodash/noop'

import * as SharedData from '@opentrons/shared-data'
import * as Selectors from '../selectors'
import * as Fixtures from '../__fixtures__'
import * as RobotSelectors from '../../robot/selectors'
import type { State } from '../../types'

jest.mock('@opentrons/shared-data')
jest.mock('../../robot/selectors')

type SelectorSpec = {|
  name: string,
  selector: (State, string) => mixed,
  state: $Shape<State>,
  expected: mixed,
  before?: () => mixed,
  after?: () => mixed,
|}

const mockGetPipetteModelSpecs: JestMockFn<
  [string],
  $Call<typeof SharedData.getPipetteModelSpecs, string>
> = SharedData.getPipetteModelSpecs

const mockGetPipetteNameSpecs: JestMockFn<
  [string],
  $Call<typeof SharedData.getPipetteNameSpecs, string>
> = SharedData.getPipetteNameSpecs

const mockGetProtocolPipettes: JestMockFn<
  [State],
  $Call<typeof RobotSelectors.getPipettes, State>
> = RobotSelectors.getPipettes

const mockLeftPipette: any = {
  id: 'abc',
  model: 'mock_left_model',
}

const mockRightPipette: any = {
  id: 'def',
  model: 'mock_right_model',
}

const mockLeftSpecs: any = {
  displayName: 'Left Pipette',
  name: 'mock_left',
  backCompatNames: ['mock_left_legacy'],
}

const mockLeftLegacySpecs: any = {
  displayName: 'Left Pipette Legacy',
  name: 'mock_left_legacy',
}

const mockRightSpecs: any = {
  displayName: 'Right Pipette',
  name: 'mock_right',
}

// NOTE: protocol pipettes use "name" for the exact "model" because reasons
const mockLeftProtoPipette: any = {
  mount: 'left',
  name: 'mock_left_model',
  modelSpecs: mockLeftSpecs,
}

const mockRightProtoPipette: any = {
  mount: 'right',
  name: 'mock_right_model',
  modelSpecs: mockRightSpecs,
}

const SPECS: Array<SelectorSpec> = [
  {
    name: 'returns nulls by default',
    selector: Selectors.getProtocolPipettesInfo,
    state: {
      pipettes: {
        robotName: {
          attachedByMount: null,
          settingsById: null,
        },
      },
    },
    expected: {
      left: { compatibility: 'match', protocol: null, actual: null },
      right: { compatibility: 'match', protocol: null, actual: null },
    },
  },
  {
    name: 'gets model specs for attached pipettes',
    selector: Selectors.getProtocolPipettesInfo,
    state: {
      pipettes: {
        robotName: {
          attachedByMount: {
            left: mockLeftPipette,
            right: mockRightPipette,
          },
          settingsById: null,
        },
      },
    },
    expected: {
      left: {
        compatibility: 'match',
        protocol: null,
        actual: {
          ...mockLeftPipette,
          modelSpecs: mockLeftSpecs,
          displayName: 'Left Pipette',
        },
      },
      right: {
        compatibility: 'match',
        protocol: null,
        actual: {
          ...mockRightPipette,
          modelSpecs: mockRightSpecs,
          displayName: 'Right Pipette',
        },
      },
    },
  },
  {
    name: 'marks as match if spec names match',
    selector: Selectors.getProtocolPipettesInfo,
    state: {
      pipettes: {
        robotName: {
          attachedByMount: {
            left: mockLeftPipette,
            right: mockRightPipette,
          },
          settingsById: null,
        },
      },
    },
    before: () => {
      mockGetProtocolPipettes.mockReturnValue([
        mockLeftProtoPipette,
        mockRightProtoPipette,
      ])
    },
    expected: {
      left: {
        compatibility: 'match',
        protocol: {
          ...mockLeftProtoPipette,
          displayName: 'Left Pipette',
        },
        actual: {
          ...mockLeftPipette,
          modelSpecs: mockLeftSpecs,
          displayName: 'Left Pipette',
        },
      },
      right: {
        compatibility: 'match',
        protocol: {
          ...mockRightProtoPipette,
          displayName: 'Right Pipette',
        },
        actual: {
          ...mockRightPipette,
          modelSpecs: mockRightSpecs,
          displayName: 'Right Pipette',
        },
      },
    },
  },
  {
    name: 'marks as inexact match if spec names match in backcompat',
    selector: Selectors.getProtocolPipettesInfo,
    state: {
      pipettes: {
        robotName: {
          attachedByMount: {
            left: mockLeftPipette,
            right: Fixtures.mockUnattachedPipette,
          },
          settingsById: null,
        },
      },
    },
    before: () => {
      mockGetProtocolPipettes.mockReturnValue([
        { ...mockLeftProtoPipette, modelSpecs: mockLeftLegacySpecs },
      ])
    },
    expected: {
      left: {
        compatibility: 'inexact_match',
        protocol: {
          ...mockLeftProtoPipette,
          modelSpecs: mockLeftLegacySpecs,
          displayName: 'Left Pipette Legacy',
        },
        actual: {
          ...mockLeftPipette,
          modelSpecs: mockLeftSpecs,
          displayName: 'Left Pipette',
        },
      },
      right: { compatibility: 'match', protocol: null, actual: null },
    },
  },
  {
    name: 'uses requestedAs from protocol pipette if available',
    selector: Selectors.getProtocolPipettesInfo,
    state: {
      pipettes: {
        robotName: {
          attachedByMount: {
            left: mockLeftPipette,
            right: Fixtures.mockUnattachedPipette,
          },
          settingsById: null,
        },
      },
    },
    before: () => {
      mockGetProtocolPipettes.mockReturnValue([
        { ...mockLeftProtoPipette, requestedAs: mockLeftLegacySpecs.name },
      ])
    },
    expected: {
      left: {
        compatibility: 'inexact_match',
        protocol: {
          ...mockLeftProtoPipette,
          requestedAs: mockLeftLegacySpecs.name,
          displayName: 'Left Pipette Legacy',
        },
        actual: {
          ...mockLeftPipette,
          modelSpecs: mockLeftSpecs,
          displayName: 'Left Pipette',
        },
      },
      right: { compatibility: 'match', protocol: null, actual: null },
    },
  },
]

describe('protocol pipettes comparison selectors', () => {
  beforeEach(() => {
    mockGetProtocolPipettes.mockReturnValue([])

    mockGetPipetteModelSpecs.mockImplementation(model => {
      if (model === mockLeftPipette.model) return mockLeftSpecs
      if (model === mockRightPipette.model) return mockRightSpecs
      return null
    })

    mockGetPipetteNameSpecs.mockImplementation(name => {
      if (name === mockLeftLegacySpecs.name) return mockLeftLegacySpecs
      return null
    })
  })

  SPECS.forEach(spec => {
    const {
      name,
      selector,
      state,
      expected,
      before = noop,
      after = noop,
    } = spec

    it(name, () => {
      before()
      expect(selector(state, 'robotName')).toEqual(expected)
      after()
    })
  })
})
