import { Model } from "../../../../src";
import {
  DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
  DEFAULT_SCORECARD_BASELINE_COLOR_UP,
  DEFAULT_SCORECARD_BASELINE_MODE,
} from "../../../../src/constants";
import { ViewportsStore } from "../../../../src/stores/viewports_store";
import {
  ScorecardChartDefinition,
  ScorecardChartRuntime,
} from "../../../../src/types/chart/scorecard_chart";
import { GENERAL_CHART_CREATION_CONTEXT } from "../../../test_helpers/chart_helpers";
import {
  addColumns,
  createChartDefinitionFromContext,
  createScorecardChart,
  createSheet,
  deleteFigure,
  deleteSheet,
  duplicateSheet,
  redo,
  setCellContent,
  undo,
  updateChart,
  updateLocale,
} from "../../../test_helpers/commands_helpers";
import { FR_LOCALE } from "../../../test_helpers/constants";
import { getEvaluatedCell } from "../../../test_helpers/getters_helpers";
import { makeStoreWithModel } from "../../../test_helpers/stores";

let model: Model;

beforeEach(() => {
  model = new Model({
    sheets: [
      {
        name: "Sheet1",
        colNumber: 10,
        rowNumber: 10,
        rows: {},
        cells: {},
      },
    ],
  });
});

describe("datasource tests", function () {
  test("create a scorecard chart", () => {
    createScorecardChart(
      model,
      {
        keyValue: "=B8",
        baseline: "=B7",
        type: "scorecard",
        baselineDescr: { text: "Description" },
        title: { text: "Title" },
      },
      "1"
    );
    expect(model.getters.getChartRuntime("1")).toMatchObject({
      keyValue: "",
      baselineDisplay: "",
      baselineDescr: "Description",
      title: { text: "Title" },
      baselineArrow: "neutral",
      baselineColor: undefined,
    });
  });

  test("create empty scorecard chart", () => {
    createScorecardChart(
      model,
      {
        type: "scorecard",
        keyValue: "=A1",
      },
      "1"
    );
    expect(model.getters.getChartRuntime("1")).toMatchObject({
      keyValue: "",
      baselineDisplay: "",
      baselineDescr: "",
      title: { text: "" },
      baselineArrow: "neutral",
      baselineColor: undefined,
    });
  });

  test("create scorecard from creation context", () => {
    const definition = createChartDefinitionFromContext(
      "scorecard",
      GENERAL_CHART_CREATION_CONTEXT
    );
    expect(definition).toEqual({
      type: "scorecard",
      background: "#123456",
      title: { text: "hello there" },
      keyValue: "=Sheet1!B1:B4",
      baseline: "=Sheet1!A1:A4",
      baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
      baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
      baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
      humanize: false,
      annotationText: "This is an annotation text",
      annotationLink: "https://www.odoo.com",
    });
  });

  test("ranges in scorecard definition change automatically", () => {
    createScorecardChart(
      model,
      {
        keyValue: "=Sheet1!B1:B4",
        baseline: "=Sheet1!A2:A4",
      },
      "1"
    );
    addColumns(model, "before", "A", 2);
    const chart = model.getters.getChartDefinition("1") as ScorecardChartDefinition;
    expect(chart.keyValue!).toStrictEqual("=Sheet1!D1:D4");
    expect(chart.baseline!).toStrictEqual("=Sheet1!C2:C4");
  });

  test("can delete an imported scorecard chart", () => {
    createScorecardChart(
      model,
      {
        keyValue: "=B7:B8",
        baseline: "=B7",
        type: "scorecard",
      },
      "1"
    );
    const exportedData = model.exportData();
    const newModel = new Model(exportedData);
    const { store: viewStore } = makeStoreWithModel(newModel, ViewportsStore);
    expect(viewStore.visibleFigures).toHaveLength(1);
    expect(newModel.getters.getChartRuntime("1")).toBeTruthy();
    deleteFigure(newModel, model.getters.getFigureIdFromChartId("1"));
    expect(viewStore.visibleFigures).toHaveLength(0);
    expect(() => newModel.getters.getChartRuntime("1")).toThrow();
  });

  test("update scorecard chart", () => {
    createScorecardChart(
      model,
      {
        keyValue: "=B7:B8",
        baseline: "=B7",
      },
      "1"
    );
    updateChart(model, "1", {
      keyValue: "=A7",
      baseline: "=E3",
      baselineMode: "percentage",
      baselineDescr: { text: "description" },
      title: { text: "hello1" },
    });
    expect(model.getters.getChartDefinition("1")).toMatchObject({
      keyValue: "=A7",
      baseline: "=E3",
      baselineMode: "percentage",
      baselineDescr: { text: "description" },
      title: { text: "hello1" },
    });
  });

  test("changing a scorecard chart type keeps the key value as the new chart's data range", () => {
    createScorecardChart(model, { keyValue: "=B2:B4", baseline: "=A1" }, "1");
    updateChart(model, "1", { type: "bar" } as any);
    const def = model.getters.getChartDefinition("1") as any;
    expect(def.type).toBe("bar");
    expect(def.dataSource.dataSets).toMatchObject([{ dataRange: "B2:B4" }]);
    expect(def.dataSource.labelRange).toEqual("A1");
  });

  test("Scorecard Chart is deleted on sheet deletion", () => {
    createSheet(model, { sheetId: "2", position: 1 });
    createScorecardChart(
      model,
      {
        keyValue: "=Sheet1!B1:B4",
      },
      "1",
      "2"
    );
    expect(model.getters.getChartRuntime("1")).not.toBeUndefined();
    deleteSheet(model, "2");
    expect(() => model.getters.getChartRuntime("1")).toThrow();
  });

  test("Scorecard chart is copied on sheet duplication", () => {
    const firstSheetId = model.getters.getActiveSheetId();
    const secondSheetId = "42";
    createScorecardChart(
      model,
      {
        title: { text: "test" },
        keyValue: "=B1:B4",
        baseline: "=A1",
      },
      firstSheetId
    );
    const figure = model.getters.getFigures(firstSheetId)[0]!;
    duplicateSheet(model, firstSheetId, secondSheetId);

    expect(model.getters.getFigures(secondSheetId)).toHaveLength(1);
    const duplicatedFigure = model.getters.getFigures(secondSheetId)[0];
    const duplicatedChartId = model.getters.getChartIds(secondSheetId)[0];

    const newChart = model.getters
      .getChart(duplicatedChartId)
      ?.getDefinition() as ScorecardChartDefinition;
    expect(newChart.title.text).toEqual("test");
    expect(newChart.keyValue).toEqual("=B1:B4");
    expect(newChart.baseline).toEqual("=A1");

    expect(duplicatedFigure).toMatchObject({ ...figure, id: expect.any(String) });
    expect(duplicatedFigure.id).not.toBe(figure?.id);
    // duplicated chart is not deleted if original sheet is deleted
    deleteSheet(model, firstSheetId);
    expect(model.getters.getSheetIds()).toHaveLength(1);
    expect(model.getters.getFigures(secondSheetId)).toEqual([duplicatedFigure]);
  });

  test("Explicit sheet references are correctly updated on sheet duplication", () => {
    const firstSheetId = model.getters.getActiveSheetId();
    const secondSheetId = "secondSheetId";
    const thirdSheetId = "thirdSheetId";
    createSheet(model, { sheetId: secondSheetId });
    setCellContent(model, "B1", "99", "secondSheetId");
    createScorecardChart(
      model,
      {
        keyValue: `=${model.getters.getSheetName(secondSheetId)}!B1`,
        baseline: `=${model.getters.getSheetName(firstSheetId)}!A1`,
      },
      firstSheetId
    );
    duplicateSheet(model, firstSheetId, thirdSheetId, "Sheet1Copy");
    const duplicatedChartId = model.getters.getChartIds(thirdSheetId)[0];
    const newChart = model.getters
      .getChart(duplicatedChartId)
      ?.getDefinition() as ScorecardChartDefinition;
    expect(newChart.keyValue).toEqual("=Sheet2!B1");
    expect(newChart.baseline).toEqual("=Sheet1Copy!A1");
  });

  test("percentage with key value smaller than baseline", () => {
    setCellContent(model, "A1", "40");
    setCellContent(model, "A2", "100");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineArrow: "down",
      baselineColor: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
      baselineDisplay: "60.0%",
      keyValue: "40",
    });
  });

  test("percentage with key value greater than baseline", () => {
    setCellContent(model, "A1", "140");
    setCellContent(model, "A2", "100");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineArrow: "up",
      baselineColor: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
      baselineDisplay: "40.0%",
      keyValue: "140",
    });
  });

  test("percentage with key value equal to baseline", () => {
    setCellContent(model, "A1", "140");
    setCellContent(model, "A2", "140");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineArrow: "neutral",
      baselineColor: undefined,
      baselineDisplay: "0.0%",
      keyValue: "140",
    });
  });

  test("percentage with key value not defined", () => {
    setCellContent(model, "A2", "140");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineArrow: "neutral",
      baselineColor: undefined,
      baselineDisplay: "140",
      keyValue: "",
    });
  });

  test("percentage with baseline value not defined", () => {
    setCellContent(model, "A1", "140");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineArrow: "neutral",
      baselineColor: undefined,
      baselineDisplay: "",
      keyValue: "140",
    });
  });

  test("percentage with key value not defined and baseline value not defined", () => {
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineArrow: "neutral",
      baselineColor: undefined,
      baselineDisplay: "",
      keyValue: "",
    });
  });

  test("percentage with baseline value being 0", () => {
    setCellContent(model, "A1", "1");
    setCellContent(model, "A2", "0");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineArrow: "up",
      baselineColor: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
      baselineDisplay: "∞%",
    });
  });

  test("percentage with key value and baseline value being 0", () => {
    setCellContent(model, "A1", "0");
    setCellContent(model, "A2", "0");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineArrow: "neutral",
      baselineColor: undefined,
      baselineDisplay: "0.0%",
    });
  });

  test("progress bar with positive baseline/key ratio", () => {
    setCellContent(model, "A1", "40");
    setCellContent(model, "A2", "100");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "progress",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineColor: undefined,
      baselineDisplay: "40.0%",
      keyValue: "40",
      progressBar: {
        color: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
        value: 0.4,
      },
    });
  });

  test("progress bar with negative baseline/key ratio", () => {
    setCellContent(model, "A1", "-40");
    setCellContent(model, "A2", "100");
    createScorecardChart(model, {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "progress",
    });
    const [scorecardId] = model.getters.getChartIds(model.getters.getActiveSheetId());
    expect(model.getters.getChartRuntime(scorecardId)).toMatchObject({
      baselineColor: undefined,
      baselineDisplay: "-40.0%",
      keyValue: "-40",
      progressBar: {
        color: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
        value: -0.4,
      },
    });
  });
});

describe("multiple sheets", () => {
  beforeEach(() => {
    model = new Model({
      sheets: [
        {
          name: "Sheet1",
          cells: {
            B1: "1",
            B2: "2",
          },
          figures: [
            {
              id: "1",
              tag: "chart",
              width: 400,
              height: 300,
              x: 100,
              y: 100,
              data: {
                type: "scorecard",
                title: "demo chart",
                keyValue: "=Sheet2!A1",
                baselineMode: "difference",
                baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
                baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
              },
            },
          ],
        },
        {
          name: "Sheet2",
          cells: {
            A1: "=Sheet1!B1*2",
            A2: "=Sheet1!B2*2",
          },
        },
      ],
    });
  });

  test("create a scorecard chart with data from another sheet", () => {
    createSheet(model, { sheetId: "42", activate: true });
    createScorecardChart(
      model,
      {
        keyValue: "=Sheet1!B1",
        baseline: "=Sheet1!C1",
      },
      "28"
    );
    const chart = model.getters.getChartDefinition("28") as ScorecardChartDefinition;
    expect(chart.keyValue).toEqual("=Sheet1!B1");
    expect(chart.baseline).toEqual("=Sheet1!C1");
  });
});

describe("undo/redo", () => {
  test("undo/redo scorecard chart creation", () => {
    const before = model.exportData();
    createScorecardChart(model, { keyValue: "=Sheet1!B1:B4" });
    const after = model.exportData();
    undo(model);
    expect(model).toExport(before);
    redo(model);
    expect(model).toExport(after);
  });

  test("undo/redo scorecard chart data rebuild the chart runtime", () => {
    createScorecardChart(
      model,
      {
        keyValue: "=Sheet1!A2",
      },
      "27"
    );
    let chart = model.getters.getChartRuntime("27") as ScorecardChartRuntime;
    setCellContent(model, "A2", "99");
    chart = model.getters.getChartRuntime("27") as ScorecardChartRuntime;
    expect(chart.keyValue).toEqual("99");
    setCellContent(model, "A2", "12");
    chart = model.getters.getChartRuntime("27") as ScorecardChartRuntime;
    expect(chart.keyValue).toEqual("12");
    undo(model);
    chart = model.getters.getChartRuntime("27") as ScorecardChartRuntime;
    expect(chart.keyValue).toEqual("99");
    redo(model);
    chart = model.getters.getChartRuntime("27") as ScorecardChartRuntime;
    expect(chart.keyValue).toEqual("12");
  });
});

test("font color is white with a dark background color", () => {
  createScorecardChart(
    model,
    {
      keyValue: "=Sheet1!A2",
      background: "#000000",
    },
    "1"
  );
  expect((model.getters.getChartRuntime("1") as ScorecardChartRuntime).fontColor).toEqual(
    "#FFFFFF"
  );
});

test("Scorecard with formula cell", () => {
  setCellContent(model, "A2", "=MULTIPLY(1, 2)");
  setCellContent(model, "A1", "=SUM(1, 3)");
  createScorecardChart(
    model,
    {
      keyValue: "=A1",
      baseline: "=A2",
      baselineMode: "percentage",
    },
    "1"
  );
  const runtime = model.getters.getChartRuntime("1") as ScorecardChartRuntime;
  expect(runtime.keyValue).toEqual("4");
  expect(runtime.baselineDisplay).toEqual("100.0%");
});

describe("Keyvalue formula", () => {
  test("keyValue formula is evaluated", () => {
    const model = new Model();
    createScorecardChart(model, { keyValue: "=SUM(1, 2)" }, "1");
    expect(model.getters.getChartRuntime("1")).toMatchObject({ keyValue: "3" });
  });

  test("keyValue updates when referenced cell changes", () => {
    const model = new Model();
    setCellContent(model, "A1", "5");
    createScorecardChart(model, { keyValue: "=A1" }, "1");
    expect(model.getters.getChartRuntime("1")).toMatchObject({ keyValue: "5" });
    setCellContent(model, "A1", "99");
    expect(model.getters.getChartRuntime("1")).toMatchObject({ keyValue: "99" });
  });

  test("keyValue string is adapted on column insertion", () => {
    const model = new Model();
    createScorecardChart(model, { keyValue: "=SUM(A1, B1)" }, "1");
    addColumns(model, "before", "A", 1);
    const chart = model.getters.getChartDefinition("1") as ScorecardChartDefinition;
    expect(chart.keyValue).toEqual("=SUM(B1, C1)");
  });

  test("unformatted keyValue does not show floating point imprecision when humanize is disabled", () => {
    const model = new Model();
    setCellContent(model, "A1", "=0.1+0.7");
    createScorecardChart(model, { keyValue: "=A1", humanize: false }, "1");
    expect(getEvaluatedCell(model, "A1").formattedValue).toBe("0.8");
    expect(model.getters.getChartRuntime("1")).toMatchObject({ keyValue: "0.8" });
  });

  test("unformatted keyValue uses the locale decimal separator when humanize is disabled", () => {
    const model = new Model();
    updateLocale(model, FR_LOCALE);
    setCellContent(model, "C1", "1.5");
    createScorecardChart(model, { keyValue: "=C1", humanize: false }, "1");
    expect(model.getters.getChartRuntime("1")).toMatchObject({ keyValue: "1,5" });
  });
});

describe("Baseline formula", () => {
  test("baseline formula is evaluated", () => {
    const model = new Model();
    createScorecardChart(model, { baseline: "=SUM(3, 4)" }, "1");
    expect(model.getters.getChartRuntime("1")).toMatchObject({ baselineDisplay: "7" });
  });

  test("baseline updates when referenced cell changes", () => {
    const model = new Model();
    setCellContent(model, "A1", "5");
    createScorecardChart(model, { baseline: "=A1" }, "1");
    expect(model.getters.getChartRuntime("1")).toMatchObject({ baselineDisplay: "5" });
    setCellContent(model, "A1", "99");
    expect(model.getters.getChartRuntime("1")).toMatchObject({ baselineDisplay: "99" });
  });

  test("baseline string is adapted  on column insertion", () => {
    const model = new Model();
    createScorecardChart(model, { baseline: "=SUM(A1, B1)" }, "1");
    addColumns(model, "before", "A", 1);
    const chart = model.getters.getChartDefinition("1") as ScorecardChartDefinition;
    expect(chart.baseline).toEqual("=SUM(B1, C1)");
  });
});
