import JSBD from "JSBD";
import currency from "../src/currency";

test("should be immutable", () => {
  let value = new currency(1);
  let newValue = value.add(0.25);
  expect(value.toString()).toBe("1");
});

test("should be instance of currency", () => {
  let value = new currency(1);
  expect(value instanceof currency).toBe(true);
});

test("should allow numbers", () => {
  let value = new currency(1.23);
  expect(value.toString()).toBe("1.23");
});

test("should allow strings", () => {
  let value = new currency("1.23");
  expect(value.toString()).toBe("1.23");
});

test("should allow currency", () => {
  let value = new currency(new currency("1.23"));
  expect(value.toString()).toBe("1.23");
});

test("should accept strings with symbols", () => {
  let val1 = new currency("$1234.56"),
    val2 = new currency("£78.90");
  expect(val1.toString()).toBe("1234.56");
  expect(val2.toString()).toBe("78.9");
});

test("should allow comma delimited strings", () => {
  let val = new currency("1,234,567.89");
  expect(val.toString()).toBe("1234567.89");
});

test("should strip invalid characters", () => {
  let val = new currency("a1b2c3");
  expect(val.toString()).toBe("123");
});
test("should throw error under strict", () => {
  expect(() => new currency("a1b2c3", { strict: true })).toThrow(
    "number format error"
  );
});

test("should allow negative strings", () => {
  let val = new currency("-12.34");
  expect(val.toString()).toBe("-12.34");
});

test("should default to 0 with invalid strings", () => {
  expect(new currency("abc").value).toBe("0");
});

test("should return intValue", () => {
  expect(new currency("-12.34").JSBDVal.toString()).toBe("-12.34");
});

test("should add correctly", () => {
  let value1 = new currency(2.51).add(0.01).toString();
  let value2 = new currency(2.51).add(JSBD.BigDecimal(0.01)).toString();
  expect(value1).toBe("2.52");
  expect(value2).toBe("2.52");
});

test("should add floating point value", () => {
  let value = new currency(2.51).add(0.01).toString();
  expect(value).toBe("2.52");
  expect((2.51 + 0.01).toString() === "2.52").toBe(false);
});
test("should subtract correctly", () => {
  let value1 = new currency(2.52).subtract(0.01).toString();
  let value2 = new currency(2.52).subtract(JSBD.BigDecimal(0.01)).toString();
  expect(value1).toBe("2.51");
  expect(value2).toBe("2.51");
});
test("should subtract floating point", () => {
  let value = new currency(2.52).subtract(0.01);
  expect(value.toString()).toBe("2.51");
});

test("should round half up", () => {
  let value1 = new currency(17.955),
    value2 = new currency(17.855),
    value3 = new currency(17.455),
    stringValue1 = new currency("17.955"),
    stringValue2 = new currency("17.855"),
    stringValue3 = new currency("17.455");

  expect(value1.toString()).toBe("17.96");
  expect(value2.toString()).toBe("17.86");
  expect(value3.toString()).toBe("17.46");
  expect(stringValue1.toString()).toBe("17.96");
  expect(stringValue2.toString()).toBe("17.86");
  expect(stringValue3.toString()).toBe("17.46");
});

test("should round negative values half up", () => {
  let value1 = new currency(-17.955),
    value2 = new currency(-17.855),
    value3 = new currency(-17.455),
    stringValue1 = new currency("-17.955"),
    stringValue2 = new currency("-17.855"),
    stringValue3 = new currency("-17.455");

  expect(value1.toString()).toBe("-17.96");
  expect(value2.toString()).toBe("-17.86");
  expect(value3.toString()).toBe("-17.46");
  expect(stringValue1.toString()).toBe("-17.96");
  expect(stringValue2.toString()).toBe("-17.86");
  expect(stringValue3.toString()).toBe("-17.46");
});

test("currency multiplication", () => {
  let value = new currency(1.23).multiply(2);
  let floatingValue = new currency(0.1).multiply(0.2);
  expect(value.toString()).toBe("2.46");
  expect(floatingValue.toString()).toBe("0.02");
});

test("currency multiplication with jsbd", () => {
  let value = new currency(1.23).multiply(JSBD.BigDecimal(2));
  expect(value.toString()).toBe("2.46");
});

test("currency multiplication with precision", () => {
  let value = new currency(1.369, { precision: 3 }).multiply(3);
  expect(value.toString()).toBe("4.107");
});

test("currency division", () => {
  let value = new currency(9.87).divide(2);
  expect(value.toString()).toBe("4.94");
});
test("currency division with jsbd", () => {
  let value = new currency(9.87).divide(JSBD.BigDecimal(2));
  expect(value.toString()).toBe("4.94");
});
test("currency division with precision", () => {
  let value = new currency(4.107, { precision: 3 }).divide(3);
  expect(value.toString()).toBe("1.369");
});

test("should parse negative values", () => {
  let pos = new currency(1.23),
    neg = new currency(-1.23),
    distribute = neg.distribute(4);

  let total = 0;
  for (let i = 0; i < distribute.length; i++) {
    total += distribute[i].toNumber();
  }
  expect(neg.toString()).toBe("-1.23");
  expect(pos.subtract(2.01).toString()).toBe("-0.78");
  expect(distribute[0].toString()).toBe("-0.31");
  expect(total.toString()).toBe("-1.23");
});

test("should create equal distribution", () => {
  let values = new currency(1.0).distribute(4);

  let realValues = [];
  for (let i = 0; i < values.length; i++) {
    realValues.push(values[i].toNumber());
  }
  expect(values.length).toBe(4);
  expect(realValues).toEqual([0.25, 0.25, 0.25, 0.25]);
});

test("should create non-equal distribution with pennies", () => {
  let values = new currency(1.01).distribute(4);
  let total = 0;
  for (let i = 0; i < values.length; i++) {
    total += values[i].toNumber();
  }
  expect(values[0].toString()).toBe("0.26");
  expect(values[1].toString()).toBe("0.25");
  expect(total.toString()).toBe("1.01");
});

test("should create non-equal distribution with a negative penny", () => {
  let values = new currency(-0.01).distribute(2);
  let total = 0;
  for (let i = 0; i < values.length; i++) {
    total += values[i].toNumber();
  }

  expect(values[0].toString()).toBe("-0.01");
  expect(values[1].toString()).toBe("0");
  expect(total.toString()).toBe("-0.01");
});

test("should get yuan value", () => {
  let value = new currency(1.23);
  expect(value.add(2).yuan()).toBe("3.23");
  expect(value.add(0.8).yuan()).toBe("2.03");
  expect(value.subtract(0.8).yuan()).toBe("0.43");
});

test("should get cent value", () => {
  let value = new currency(1.23);
  expect(value.cents()).toBe("123");
  expect(value.yuan()).toBe("1.23");
});

test("should support different precision values", () => {
  let c1 = new currency(1.234, { precision: 3 });
  let c2 = new currency(1.234, { symbol: "$", precision: 0 });
  expect(c1.toString()).toBe("1.234");
  expect(c2.toString()).toBe("1");
  expect(c1.add(4.567).toString()).toBe("5.801");
  expect(c2.add(4.567).toString()).toBe("6");
  expect(c1.subtract(4.567).toString()).toBe("-3.333");
  expect(c2.subtract(4.567).toString()).toBe("-3");
  expect(c1.format()).toBe("￥1.234");
  expect(c2.format()).toBe("$1");
  expect(c1.distribute(4).map((x) => x.toNumber())).toStrictEqual([
    0.309, 0.309, 0.308, 0.308,
  ]);
  expect(c2.distribute(4).map((x) => x.toNumber())).toStrictEqual([1, 0, 0, 0]);
});

test("should use source precision for arithmetic with different precisions", () => {
  let c1 = new currency(1.23);
  let c2 = new currency(1.239, { precision: 3 });
  expect(c1.add(c2).toString()).toBe("2.47");
  expect(c2.add(c1).toString()).toBe("2.469");
});

test("should use source formatting for mixed currency formats", () => {
  let c1 = new currency("1,234.56");
  let c2 = new currency("1 234,56", { separator: " ", decimal: "," });
  expect(c1.add(c2).format()).toBe("￥2,469.12");
  expect(c2.add(c1).format()).toBe("￥2 469,12");
});

test("should default rounding when parsing", () => {
  let round1 = new currency(1.2349),
    round2 = new currency(5.6789),
    multiply = new currency(10.0),
    divide = new currency(0.01);
  expect(round1.toString()).toBe("1.23");
  expect(round2.toString()).toBe("5.68");
  expect(multiply.multiply(0.001).toString()).toBe("0.01");
  expect(divide.divide(0.001).toString()).toBe("10");
});

test("should stringify json", () => {
  let value1 = new currency(1.23),
    value2 = new currency(2.51).add(0.01),
    value3 = new currency(2.52).subtract(0.02);

  expect(JSON.stringify({ value: value1 })).toBe('{"value":"1.23"}');
  expect(JSON.stringify({ value: value2 })).toBe('{"value":"2.52"}');
  expect(JSON.stringify({ value: value3 })).toBe('{"value":"2.5"}');
});

test("should format value using defaults", () => {
  let value1 = new currency(1.23),
    value2 = new currency(1234.56),
    value3 = new currency(1234567.89),
    value4 = new currency(1234567.8912, { precision: 4 }),
    value5 = new currency(1234567, { precision: 0 });
  expect(value1.format()).toBe("￥1.23");
  expect(value2.format()).toBe("￥1,234.56");
  expect(value3.format()).toBe("￥1,234,567.89");
  expect(value4.format()).toBe("￥1,234,567.8912");
  expect(value5.format()).toBe("￥1,234,567");
  expect(value1.multiply(-1).format()).toBe("-￥1.23");
  expect(value2.multiply(-1).format()).toBe("-￥1,234.56");
  expect(value3.multiply(-1).format()).toBe("-￥1,234,567.89");
  expect(value4.multiply(-1).format()).toBe("-￥1,234,567.8912");
  expect(value5.multiply(-1).format()).toBe("-￥1,234,567");
});

test("should format value using international", () => {
  let c = (value: number) =>
    new currency(value, { separator: ".", decimal: "," });
  expect(c(1.23).format()).toBe("￥1,23");
  expect(c(1000.0).format()).toBe("￥1.000,00");
  expect(c(1000000.0).format()).toBe("￥1.000.000,00");
});

test("should format using patterns", () => {
  let c = (value: number, opts?: any) =>
      new currency(value, Object.assign({}, { pattern: "# !" }, opts)),
    value1 = c(1.23),
    value2 = c(1234.56),
    value3 = c(1234567.89),
    value4 = c(1234567.8912, { precision: 4 }),
    value5 = c(1234567, { precision: 0 });
  expect(value1.format()).toBe("1.23 ￥");
  expect(value2.format()).toBe("1,234.56 ￥");
  expect(value3.format()).toBe("1,234,567.89 ￥");
  expect(value4.format()).toBe("1,234,567.8912 ￥");
  expect(value5.format()).toBe("1,234,567 ￥");
});

test("should format without symbol", () => {
  expect(new currency(1.23, { pattern: "#" }).format()).toBe("1.23");
});

test("should format with international symbol", () => {
  expect(new currency(1.23, { symbol: "£" }).format()).toBe("£1.23");
  expect(new currency(1.23, { symbol: "$" }).format()).toBe("$1.23");
});

test("should use a function for formatting(when currency init)", () => {
  let called = false;

  const formatter = () => {
    called = true;
    return "1.23";
  };

  const val = new currency(1234.56, { formatter: formatter }).format();
  expect(called).toBe(true);
  expect(val).toBe("1.23");
});
test("should use a function for formatting(when format be called)", () => {
  let called = false;

  const formatter = () => {
    called = true;
    return "1.23";
  };

  const val = new currency(1234.56).format({ formatter: formatter });
  expect(called).toBe(true);
  expect(val).toBe("1.23");
});
test("should use opts for formatting(when format be called)", () => {
  const val = new currency(1234.56).format({ symbol: "-" });
  expect(val).toBe("-1,234.56");
});
test("should return 0 currency with invalid input", () => {
  //@ts-ignore
  let value = new currency(undefined);
  expect(value.value).toBe("0");
});

test("should handle over max safe integer", () => {
  let max1 = new currency("99071992547409.91"),
    max2 = new currency("-99071992547409.91"),
    max3 = new currency(
      "11111111111111111111111111111111111111111111111111111111"
    ),
    max4 = new currency(
      "-22222222222222222222222222222222222222222222222222222222"
    );
  expect(max1.add(2).toString()).toBe("99071992547411.91");
  expect(max2.add(2).toString()).toBe("-99071992547407.91");
  expect(max3.multiply(2).toString()).toBe(
    "22222222222222222222222222222222222222222222222222222222"
  );
  expect(max4.divide(2).toString()).toBe(
    "-11111111111111111111111111111111111111111111111111111111"
  );
  expect(max3.compareTo(max4)).toBe(1);
  expect(max4.compareTo(max3)).toBe(-1);
  expect(max3.compareTo(max3)).toBe(0);
  expect(max3.format()).toBe(
    "￥11,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111.00"
  );
  expect(max3.chineseNumber()).toBe(
    "壹仟壹佰壹拾壹万壹仟壹佰壹拾壹亿壹仟壹佰壹拾壹万壹仟壹佰壹拾壹亿壹仟壹佰壹拾壹万壹仟壹佰壹拾壹亿壹仟壹佰壹拾壹万壹仟壹佰壹拾壹亿壹仟壹佰壹拾壹万壹仟壹佰壹拾壹亿壹仟壹佰壹拾壹万壹仟壹佰壹拾壹亿壹仟壹佰壹拾壹万壹仟壹佰壹拾壹元整"
  );
});

test("should throw exception with invalid input", () => {
  expect(
    new currency(JSBD.BigDecimal("987654321123456789987654321123456789"), {
      strict: true,
    }).toString()
  ).toBe("987654321123456789987654321123456789");
  //@ts-ignore
  expect(() => new currency(undefined, { strict: true })).toThrow(
    "number format error"
  );
});

test("should parse cents from a number when using fromCents option", () => {
  let c1 = new currency(123, { fromCents: true });
  expect(c1.toString()).toBe("1.23");
});

test("should handle add with fromCents option", () => {
  let c1 = new currency(12345, { fromCents: true });
  let c2 = new currency(123, { fromCents: true });
  // expect(c1.toString()).toBe("123.45");
  expect(c1.add(c2).toString()).toBe("124.68");
  expect(c1.add("1.23").toString()).toBe("124.68");
});

test("should handle subtract with fromCents option", () => {
  let c1 = new currency(12345, { fromCents: true });
  let c2 = new currency(123, { fromCents: true });
  expect(c1.subtract(c2).toString()).toBe("122.22");
  expect(c1.subtract("1.23").toString()).toBe("122.22");
});

test("should handle multiply with fromCents option", () => {
  let c1 = new currency(12345, { fromCents: true });
  let c2 = new currency(200, { fromCents: true });
  expect(c1.multiply(c2).toString()).toBe("246.9");
  expect(c1.multiply(2).toString()).toBe("246.9");
});

test("should handle divide with fromCents option", () => {
  let c1 = new currency(12345, { fromCents: true });
  let c2 = new currency(200, { fromCents: true });
  expect(c1.divide(c2).toString()).toBe("61.73");
  expect(c1.divide(2).toString()).toBe("61.73");
});

test("should handle distribute with fromCents option", () => {
  var values = new currency(100, { fromCents: true }).distribute(4);
  expect(values.map((x) => x.toNumber())).toStrictEqual([
    0.25, 0.25, 0.25, 0.25,
  ]);
});
test("should compare correctly", () => {
  expect(new currency(7).compareTo(new currency(8))).toBe(-1);
  expect(new currency(8).compareTo(new currency(7))).toBe(1);
  expect(new currency(5).compareTo(new currency(5))).toBe(0);
  let value = [
    new currency(7),
    new currency(8),
    new currency(4),
    new currency(9),
    new currency(-3),
    new currency(6),
    new currency(5),
    new currency(0),
    new currency(5),
  ];
  value.sort((a, b) => a.compareTo(b));
  expect(value.map((a) => a.toString())).toStrictEqual([
    "-3",
    "0",
    "4",
    "5",
    "5",
    "6",
    "7",
    "8",
    "9",
  ]);
});
test("阿拉伯数字转中文", () => {
  let testData = [
    ["0", "零元整"],
    ["00", "零元整"],
    ["000", "零元整"],
    ["1", "壹元整"],
    ["001", "壹元整"],
    ["010", "壹拾元整"],
    ["0010", "壹拾元整"],
    ["12", "壹拾贰元整"],
    ["3456789", "叁佰肆拾伍万陆仟柒佰捌拾玖元整"],
    ["1001000021", "壹拾亿零壹佰万零贰拾壹元整"],
    ["10000000000000000", "壹亿亿元整"],
    ["100000001100000000", "壹拾亿零壹拾壹亿元整"],

    ["0.0", "零元整"],
    ["0.001", "零元整"],
    ["1.0", "壹元整"],
    ["1.00", "壹元整"],
    ["0.1", "壹角"],
    ["1.10", "壹元壹角"],
    ["1.101", "壹元壹角"],
    ["10.01", "壹拾元零壹分"],
    ["10.012", "壹拾元零壹分"],
    ["100111.11", "壹拾万零壹佰壹拾壹元壹角壹分"],

    ["-0.0", "零元整"],
    ["-0.001", "零元整"],
    ["-1.0", "负壹元整"],
    ["-1.00", "负壹元整"],
    ["-0.1", "负壹角"],
    ["-1.10", "负壹元壹角"],
    ["-1.101", "负壹元壹角"],
    ["-10.01", "负壹拾元零壹分"],
    ["-10.012", "负壹拾元零壹分"],
    ["-100111.11", "负壹拾万零壹佰壹拾壹元壹角壹分"],
  ];
  testData.map((item) => {
    expect(new currency(item[0]).chineseNumber()).toBe(item[1]);
  });
});
test("should not show tail0 while config", () => {
  let c1 = new currency("1,234.00");
  let c2 = new currency("1,234.00", { showTail0: false });
  expect(c1.format()).toBe("￥1,234.00");
  expect(c2.format()).toBe("￥1,234");
});
