import { Deck, PlayingCard, Shoe } from "../src";
import { InvalidArgumentError, OutOfBoundsError } from "../src/errors";

let shoe: Shoe;
describe("Check constructor works with and without arguments: ", () => {
  test("Constructor without arguments: ", () => {
    shoe = new Shoe();
    expect(shoe.count).toBe(0);
  });
  test("Constructor with argument produces corrent number of decks: ", () => {
    shoe = new Shoe(6);
    expect(shoe.count).toBe(6);
  });
  test("Constructor with a non-numberical argument: ", () => {
    expect(() => new Shoe("A" as unknown as number)).toThrowError(new InvalidArgumentError("Argument must be a number."));
  });
  test("Constructor with an argument less than zero: ", () => {
    expect(() => new Shoe(-1)).toThrowError(new OutOfBoundsError("Argument must be a number greater than zero."));
  });
});

describe("Test addDeck function: ", () => {
  test("Adding a new Deck to the shoe: ", () => {
    shoe = new Shoe(1);
    expect(shoe.addDeck(new Deck()).count).toBe(2);
  });
  test("Error generation on non-deck object as argument: ", () => {
    shoe = new Shoe(1);
    expect(() => shoe.addDeck(1 as unknown as Deck)).toThrowError(
      new InvalidArgumentError("Argument must be a valid Deck object.")
    );
  });
});

describe("Test addStandardDecks function: ", () => {
  test("Adding a new Deck to the shoe: ", () => {
    shoe = new Shoe(1);
    expect(shoe.addStandardDecks(1).count).toBe(2);
  });
  test("Error generation on non-numerical argument: ", () => {
    shoe = new Shoe(1);
    expect(() => shoe.addStandardDecks(new Deck() as unknown as number)).toThrowError(
      new InvalidArgumentError("Argument must be a number.")
    );
  });
});

describe("Test class property getters: ", () => {
  test("count property: ", () => {
    shoe = new Shoe(4);
    expect(shoe.count).toBe(4);
  });
  test("cardCount property: ", () => {
    shoe = new Shoe(4);
    expect(shoe.cardCount).toBe(4 * 52);
  });
});

describe("Test drawCard function: ", () => {
  test("Returns valid Card object: ", () => {
    shoe = new Shoe(2);
    expect(shoe.drawCard().constructor).toBe(PlayingCard);
  });
  test("Can draw until all decks are exhausted: ", () => {
    shoe = new Shoe(2);
    const mockFn = jest.fn(() => {
      shoe.drawCard();
    });
    for (let i = 0; i < 104; i++) {
      mockFn();
    }
    expect(mockFn).toHaveReturnedTimes(104);
  });
  test("Throws error once decks are empty: ", () => {
    shoe = new Shoe();
    shoe.addDeck(new Deck()).addDeck(new Deck());
    expect(() => shoe.drawCard()).toThrowError(new OutOfBoundsError("All decks are empty."));
  });
});

describe("Test empty function: ", () => {
  test("Function returns a zero deck count: ", () => {
    shoe = new Shoe(6);
    expect(shoe.count).toBe(6);
    shoe.empty();
    expect(shoe.count).toBe(0);
  });
});
