import { ChartConfiguration, ScaleChartOptions } from "chart.js";
import { ChartCreationContext, Model } from "../../../../src";
import { UuidGenerator } from "../../../../src/helpers/uuid";
import {
  CalendarChartGranularity,
  CalendarChartRuntime,
} from "../../../../src/types/chart/calendar_chart";
import {
  createCalendarChart,
  createChartDefinitionFromContext,
  createSheet,
  setCellContent,
  setFormat,
} from "../../../test_helpers";
import {
  GENERAL_CHART_CREATION_CONTEXT,
  toChartDataSource,
} from "../../../test_helpers/chart_helpers";

const STAMPS_AND_LABELS: { stamp: CalendarChartGranularity; labels: string[] }[] = [
  {
    stamp: "day_of_week",
    labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
  },
  {
    stamp: "month_number",
    labels: [
      "January",
      "February",
      "March",
      "April",
      "May",
      "June",
      "July",
      "August",
      "September",
      "October",
      "November",
      "December",
    ],
  },
  { stamp: "year", labels: ["1901", "1902"] },
  {
    stamp: "iso_week_number",
    labels: [
      "W01",
      "W02",
      "W03",
      "W04",
      "W05",
      "W06",
      "W07",
      "W08",
      "W09",
      "W10",
      "W11",
      "W12",
      "W13",
      "W14",
      "W15",
      "W16",
      "W17",
      "W18",
      "W19",
      "W20",
      "W21",
      "W22",
      "W23",
      "W24",
      "W25",
      "W26",
      "W27",
      "W28",
      "W29",
      "W30",
      "W31",
      "W32",
      "W33",
      "W34",
      "W35",
      "W36",
      "W37",
      "W38",
      "W39",
      "W40",
      "W41",
      "W42",
      "W43",
      "W44",
      "W45",
      "W46",
      "W47",
      "W48",
      "W49",
      "W50",
      "W51",
      "W52",
    ],
  },
  {
    stamp: "hour_number",
    labels: [
      "00 AM",
      "01 AM",
      "02 AM",
      "03 AM",
      "04 AM",
      "05 AM",
      "06 AM",
      "07 AM",
      "08 AM",
      "09 AM",
      "10 AM",
      "11 AM",
      "00 PM",
      "01 PM",
      "02 PM",
      "03 PM",
      "04 PM",
      "05 PM",
      "06 PM",
      "07 PM",
      "08 PM",
      "09 PM",
      "10 PM",
      "11 PM",
    ],
  },
  {
    stamp: "day_of_month",
    labels: [
      "1",
      "2",
      "3",
      "4",
      "5",
      "6",
      "7",
      "8",
      "9",
      "10",
      "11",
      "12",
      "13",
      "14",
      "15",
      "16",
      "17",
      "18",
      "19",
      "20",
      "21",
      "22",
      "23",
      "24",
      "25",
      "26",
      "27",
      "28",
      "29",
      "30",
      "31",
    ],
  },
  {
    stamp: "quarter_number",
    labels: ["Q1", "Q2", "Q3", "Q4"],
  },
];

describe("calendar chart", () => {
  test("create calendar chart from creation context", () => {
    const context: Required<ChartCreationContext> = {
      ...GENERAL_CHART_CREATION_CONTEXT,
      ...toChartDataSource({
        dataSets: [{ dataRange: "Sheet1!B1:B4", yAxisId: "y1" }],
        labelRange: "Sheet1!A1:A4",
        dataSetsHaveTitle: true,
      }),
    };
    const definition = createChartDefinitionFromContext("calendar", context);
    expect(definition).toEqual({
      type: "calendar",
      background: "#123456",
      title: { text: "hello there" },
      ...toChartDataSource({
        dataSets: [{ dataRange: "Sheet1!B1:B4", yAxisId: "y1" }],
        dataSetsHaveTitle: true,
        labelRange: "Sheet1!A1:A4",
      }),
      legendPosition: "left",
      showValues: false,
      horizontalGroupBy: "day_of_week",
      verticalGroupBy: "month_number",
      axesDesign: {},
      annotationLink: "https://www.odoo.com",
      annotationText: "This is an annotation text",
    });
  });

  test.each(STAMPS_AND_LABELS)(
    "Vertical axis grouping is taken into account",
    (grouping: { stamp: CalendarChartGranularity; labels: readonly string[] }) => {
      const model = new Model();
      createSheet(model, { sheetId: "calendar", activate: true, rows: 365, cols: 2 });
      setCellContent(model, "A1", "=DATE(1,1,1) + SEQUENCE(365,1,1,1) + SEQUENCE(365,1, 0, 1/366)");
      setFormat(model, "A1:A365", "mm/dd/yyyy hh:mm:ss");
      setCellContent(model, "B1", "=RANDARRAY(365,1)");
      const chartId = UuidGenerator.uuidv4();
      createCalendarChart(
        model,
        {
          type: "calendar" as const,
          ...toChartDataSource({
            dataSets: [{ dataRange: "B1:B365" }],
            labelRange: "A1:A365",
          }),
          verticalGroupBy: grouping.stamp,
        },
        chartId,
        "calendar"
      );
      const runtime = model.getters.getChartRuntime(chartId) as CalendarChartRuntime;
      expect(runtime.chartJsConfig.data.datasets.map((ds) => ds.label).reverse()).toEqual(
        grouping.labels
      );
    }
  );

  test.each(STAMPS_AND_LABELS)(
    "Horizontal axis grouping is taken into account",
    (grouping: { stamp: CalendarChartGranularity; labels: readonly string[] }) => {
      const model = new Model();
      createSheet(model, { sheetId: "calendar", activate: true, rows: 365, cols: 2 });
      setCellContent(model, "A1", "=DATE(1,1,1) + SEQUENCE(365,1,1,1) + SEQUENCE(365,1, 0, 1/366)");
      setFormat(model, "A1:A365", "mm/dd/yyyy hh:mm:ss");
      setCellContent(model, "B1", "=RANDARRAY(365,1)");
      const chartId = UuidGenerator.uuidv4();
      createCalendarChart(
        model,
        {
          type: "calendar" as const,
          ...toChartDataSource({
            dataSets: [{ dataRange: "B1:B365" }],
            labelRange: "A1:A365",
          }),
          horizontalGroupBy: grouping.stamp,
        },
        chartId,
        "calendar"
      );
      const runtime = model.getters.getChartRuntime(chartId) as CalendarChartRuntime;
      expect(runtime.chartJsConfig.data.labels).toEqual(grouping.labels);
    }
  );

  test("Missing values do not have a tooltip", () => {
    const model = new Model();
    setCellContent(model, "A1", "=DATE(2024,7,8)");
    setCellContent(model, "B1", "10");
    setCellContent(model, "A2", "=DATE(2024,8,4)");
    setCellContent(model, "B2", "20");
    createCalendarChart(
      model,
      {
        type: "calendar",
        ...toChartDataSource({
          dataSets: [{ dataRange: "B1:B2" }],
          labelRange: "A1:A2",
          dataSetsHaveTitle: false,
        }),
      },
      "chartId"
    );
    const runtime = model.getters.getChartRuntime("chartId") as CalendarChartRuntime;
    const [augustDataset, julyDataset] = runtime.chartJsConfig.data.datasets as any[];
    const tooltipFilter = (runtime.chartJsConfig.options?.plugins?.tooltip as any).filter;

    expect(augustDataset.values).toEqual([20, undefined]);
    expect(julyDataset.values).toEqual([undefined, 10]);
    expect(tooltipFilter({ dataset: augustDataset, dataIndex: 0 })).toBe(true);
    expect(tooltipFilter({ dataset: augustDataset, dataIndex: 1 })).toBe(false);
  });

  test("Axis borders are not shown in calendar charts", () => {
    const model = new Model();
    createCalendarChart(
      model,
      { type: "calendar", ...toChartDataSource({ dataSets: [{ dataRange: "B1:B365" }] }) },
      "chartId"
    );
    const runtime = model.getters.getChartRuntime("chartId") as CalendarChartRuntime;
    const scales = runtime.chartJsConfig.options?.scales as ScaleChartOptions<"bar">["scales"];
    expect(scales?.x?.border?.display).toBe(false);
    expect(scales?.y?.border?.display).toBe(false);
  });

  test("calendar chart without values counts entries per date bucket", () => {
    const model = new Model();
    createSheet(model, { sheetId: "calendar", activate: true, rows: 3, cols: 1 });
    setCellContent(model, "A1", "=DATE(2024,1,1)");
    setCellContent(model, "A2", "=DATE(2024,1,8)");
    setCellContent(model, "A3", "=DATE(2024,2,5)");
    const chartId = UuidGenerator.uuidv4();
    createCalendarChart(
      model,
      {
        type: "calendar" as const,
        ...toChartDataSource({ dataSets: [], labelRange: "A1:A3" }),
        horizontalGroupBy: "day_of_week",
        verticalGroupBy: "month_number",
      },
      chartId,
      "calendar"
    );
    const runtime = model.getters.getChartRuntime(chartId) as CalendarChartRuntime;
    const config = runtime.chartJsConfig as ChartConfiguration<"calendar">;
    const datasets = config.data.datasets!;
    expect(config.data.labels).toEqual(["Monday"]);
    // datasets are sorted descending by month: February first, January second
    expect(datasets.map((ds) => ds.label)).toEqual(["February", "January"]);
    expect(datasets[1].values).toEqual([2]); // January: 2 entries
    expect(datasets[0].values).toEqual([1]); // February: 1 entry
  });
});
