const mockDevice = {
  registerGpio: jest.fn(() => Promise.resolve()),
  unregisterGpio: jest.fn(() => Promise.resolve()),
};
jest.mock("../../../device", () => ({ getDevice: () => mockDevice }));

jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));

import * as React from "react";
import { mount, shallow } from "enzyme";
import {
  buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
import { TaggedSequence } from "farmbot";
import {
  fakeSequence
} from "../../../__test_support__/fake_state/resources";
import { initSave } from "../../../api/crud";
import { PinBindingInputGroupProps } from "../interfaces";
import {
  PinBindingInputGroup, PinNumberInputGroup, BindingTypeDropDown,
  ActionTargetDropDown, SequenceTargetDropDown
} from "../pin_binding_input_group";
import {
  fakeResourceIndex
} from "../../../sequences/locals_list/test_helpers";
import {
  PinBindingType, PinBindingSpecialAction
} from "farmbot/dist/resources/api_resources";
import { error, warning } from "../../../toast/toast";

const AVAILABLE_PIN = 18;

describe("<PinBindingInputGroup/>", () => {
  function fakeProps(): PinBindingInputGroupProps {
    const fakeResources: TaggedSequence[] = [fakeSequence(), fakeSequence()];
    fakeResources[0].body.id = 1;
    fakeResources[0].body.name = "Sequence 1";
    fakeResources[1].body.id = 2;
    fakeResources[1].body.name = "Sequence 2";
    const resources = buildResourceIndex(fakeResources).index;
    return {
      pinBindings: [
        { pin_number: 4, sequence_id: 1 },
        { pin_number: 5, sequence_id: 2 },
      ],
      dispatch: jest.fn(),
      resources: resources,
    };
  }

  it("renders", () => {
    const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />);
    const buttons = wrapper.find("button");
    expect(buttons.length).toBe(4);
  });

  it("no pin selected", () => {
    const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />);
    const buttons = wrapper.find("button");
    expect(buttons.last().text()).toEqual("BIND");
    buttons.last().simulate("click");
    expect(error).toHaveBeenCalledWith("Pin number cannot be blank.");
  });

  it("no target selected", () => {
    const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />);
    const buttons = wrapper.find("button");
    expect(buttons.last().text()).toEqual("BIND");
    wrapper.setState({ pinNumberInput: AVAILABLE_PIN });
    buttons.last().simulate("click");
    expect(error).toHaveBeenCalledWith("Please select a sequence or action.");
  });

  it("registers pin: api", () => {
    const p = fakeProps();
    p.dispatch = jest.fn();
    const wrapper = mount(<PinBindingInputGroup {...p} />);
    const buttons = wrapper.find("button");
    expect(buttons.last().text()).toEqual("BIND");
    wrapper.setState({ pinNumberInput: 1, sequenceIdInput: 2 });
    buttons.last().simulate("click");
    expect(mockDevice.registerGpio).not.toHaveBeenCalled();
    expect(initSave).toHaveBeenCalledWith("PinBinding",
      {
        pin_num: 1,
        sequence_id: 2,
        binding_type: PinBindingType.standard
      });
  });

  it("registers pin: api (special action)", () => {
    const p = fakeProps();
    p.dispatch = jest.fn();
    const wrapper = mount(<PinBindingInputGroup {...p} />);
    const buttons = wrapper.find("button");
    expect(buttons.last().text()).toEqual("BIND");
    wrapper.setState({
      pinNumberInput: 0,
      bindingType: PinBindingType.special,
      sequenceIdInput: undefined,
      specialActionInput: PinBindingSpecialAction.emergency_lock
    });
    buttons.last().simulate("click");
    expect(mockDevice.registerGpio).not.toHaveBeenCalled();
    expect(initSave).toHaveBeenCalledWith("PinBinding",
      {
        pin_num: 0,
        binding_type: PinBindingType.special,
        special_action: PinBindingSpecialAction.emergency_lock
      });
  });

  it("sets sequence id", () => {
    const p = fakeProps();
    const key = Object.keys(p.resources.byKind.Sequence)[0];
    const s = p.resources.references[key];
    const id = s?.body.id;
    const wrapper = mount<PinBindingInputGroup>(<PinBindingInputGroup {...p} />);
    expect(wrapper.instance().state.sequenceIdInput).toEqual(undefined);
    wrapper.instance().setSequenceIdInput({ label: "label", value: "" + id });
    expect(wrapper.instance().state.sequenceIdInput).toEqual(id);
  });

  it("sets pin", () => {
    const p = fakeProps();
    const wrapper = mount<PinBindingInputGroup>(<PinBindingInputGroup {...p} />);
    expect(wrapper.instance().state.pinNumberInput).toEqual(undefined);
    const { pin_number } = p.pinBindings[0];
    wrapper.instance().setSelectedPin(pin_number); // pin already bound
    expect(wrapper.instance().state.pinNumberInput).toEqual(undefined);
    wrapper.instance().setSelectedPin(99); // invalid pin
    expect(wrapper.instance().state.pinNumberInput).toEqual(undefined);
    wrapper.instance().setSelectedPin(AVAILABLE_PIN); // available pin
    expect(wrapper.instance().state.pinNumberInput).toEqual(AVAILABLE_PIN);
    wrapper.instance().setSelectedPin(1); // reserved pin
    expect(wrapper.instance().state.pinNumberInput).toEqual(1);
    expect(warning).toHaveBeenCalledWith(
      "Reserved Raspberry Pi pin may not work as expected.");
  });

  it("changes pin number", () => {
    const wrapper = shallow<PinBindingInputGroup>(<PinBindingInputGroup
      {...fakeProps()} />);
    expect(wrapper.instance().state.pinNumberInput).toEqual(undefined);
    wrapper.instance().setSelectedPin(AVAILABLE_PIN);
    expect(wrapper.instance().state.pinNumberInput).toEqual(AVAILABLE_PIN);
  });

  it("changes binding type", () => {
    const wrapper = shallow<PinBindingInputGroup>(<PinBindingInputGroup
      {...fakeProps()} />);
    expect(wrapper.instance().state.bindingType).toEqual(PinBindingType.standard);
    wrapper.instance().setBindingType({ label: "", value: PinBindingType.special });
    expect(wrapper.instance().state.bindingType).toEqual(PinBindingType.special);
  });

  it("changes special action", () => {
    const wrapper = shallow<PinBindingInputGroup>(<PinBindingInputGroup
      {...fakeProps()} />);
    wrapper.setState({ bindingType: PinBindingType.special });
    expect(wrapper.instance().state.specialActionInput).toEqual(undefined);
    wrapper.instance().setSpecialAction({
      label: "",
      value: PinBindingSpecialAction.sync
    });
    expect(wrapper.instance().state.specialActionInput)
      .toEqual(PinBindingSpecialAction.sync);
  });
});

describe("<PinNumberInputGroup />", () => {
  it("sets pin", () => {
    const setSelectedPin = jest.fn();
    const wrapper = shallow(<PinNumberInputGroup
      pinNumberInput={undefined}
      boundPins={[]}
      setSelectedPin={setSelectedPin} />);
    wrapper.find("FBSelect").simulate("change", {
      label: "", value: AVAILABLE_PIN
    });
    expect(setSelectedPin).toHaveBeenCalledWith(AVAILABLE_PIN);
  });
});

describe("<BindingTypeDropDown />", () => {
  it("sets binding type", () => {
    const setBindingType = jest.fn();
    const wrapper = shallow(<BindingTypeDropDown
      bindingType={PinBindingType.standard}
      setBindingType={setBindingType} />);
    const ddi = { label: "", value: PinBindingType.special };
    wrapper.find("FBSelect").simulate("change", ddi);
    expect(setBindingType).toHaveBeenCalledWith(ddi);
  });
});

describe("<ActionTargetDropDown />", () => {
  it("sets action", () => {
    const setSpecialAction = jest.fn();
    const wrapper = shallow(<ActionTargetDropDown
      specialActionInput={undefined}
      setSpecialAction={setSpecialAction} />);
    const ddi = { label: "", value: PinBindingSpecialAction.sync };
    wrapper.find("FBSelect").simulate("change", ddi);
    expect(setSpecialAction).toHaveBeenCalledWith(ddi);
  });
});

describe("<SequenceTargetDropDown />", () => {
  it("sets sequence ID", () => {
    const setSequenceIdInput = jest.fn();
    const wrapper = shallow(<SequenceTargetDropDown
      sequenceIdInput={undefined}
      resources={fakeResourceIndex()}
      setSequenceIdInput={setSequenceIdInput} />);
    const ddi = { label: "", value: 1 };
    wrapper.find("SequenceSelectBox").simulate("change", ddi);
    expect(setSequenceIdInput).toHaveBeenCalledWith(ddi);
  });
});
