/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import useAssociateProcessor from "../useAssociateProcessor";
import type { EventParams } from "@/types/app";
import type { AssociateProcessorProps, FilterOption } from "@/types/dashboard";

// Mock the store
let mockAppStore: any;
vi.mock("@/store/modules/app", () => ({
  useAppStoreWithOut: () => mockAppStore,
}));

// Mock utility functions
vi.mock("@/utils/dateFormat", () => ({
  default: vi.fn((date: Date, step: string, monthDayDiff?: boolean) => {
    if (step === "HOUR" && monthDayDiff) {
      return "2023-01-01 12";
    }
    return "2023-01-01 12:00:00";
  }),
}));

vi.mock("@/utils/localtime", () => ({
  default: vi.fn((utc: boolean, date: Date) => new Date(date)),
}));

// Mock structuredClone
const structuredCloneMock = vi.fn((obj: any) => JSON.parse(JSON.stringify(obj)));
Object.defineProperty(window, "structuredClone", {
  value: structuredCloneMock,
  writable: true,
});

// Helper function to create mock legend options
const createMockLegendOptions = () => ({
  show: false,
  total: false,
  min: false,
  max: false,
  mean: false,
  asTable: false,
  toTheRight: false,
  width: 0,
  asSelector: false,
});

describe("useAssociateProcessor", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockAppStore = {
      utc: false,
      intervalUnix: [1640995200000, 1640998800000, 1641002400000], // Sample timestamps
      durationRow: { step: "HOUR" },
    };
  });

  describe("eventAssociate", () => {
    it("returns undefined when no filters provided", () => {
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option: { series: [], type: "line", legend: createMockLegendOptions() },
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { eventAssociate } = useAssociateProcessor(mockProps);
      const result = eventAssociate();
      expect(result).toEqual({ series: [], type: "line", legend: createMockLegendOptions() });
    });

    it("returns option when no duration in filters", () => {
      const option: FilterOption = {
        series: [
          {
            name: "test",
            data: [[1, 2]] as (number | string)[][],
          },
        ],
        type: "line",
        legend: createMockLegendOptions(),
      };
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option,
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { eventAssociate } = useAssociateProcessor(mockProps);
      const result = eventAssociate();
      expect(result).toBe(option);
    });

    it("returns undefined when no series data", () => {
      const option: FilterOption = { series: [], type: "line", legend: createMockLegendOptions() };
      const mockProps: AssociateProcessorProps = {
        filters: {
          dataIndex: 0,
          sourceId: "test",
          duration: { startTime: "1000", endTime: "2000", step: "HOUR" },
        },
        option,
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { eventAssociate } = useAssociateProcessor(mockProps);
      const result = eventAssociate();
      expect(result).toBeUndefined();
    });

    it("returns undefined when endTime not in series data", () => {
      const option: FilterOption = {
        series: [
          {
            name: "test",
            data: [
              [1000, 1],
              [1500, 2],
            ] as (number | string)[][],
          },
        ],
        type: "line",
        legend: createMockLegendOptions(),
      };
      const mockProps: AssociateProcessorProps = {
        filters: {
          dataIndex: 0,
          sourceId: "test",
          duration: { startTime: "1000", endTime: "3000", step: "HOUR" },
        },
        option,
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { eventAssociate } = useAssociateProcessor(mockProps);
      const result = eventAssociate();
      expect(result).toBeUndefined();
    });

    it("adds markArea when endTime exists in series data", () => {
      const option: FilterOption = {
        series: [
          {
            name: "test",
            data: [
              ["1000", 1],
              ["2000", 2],
              ["3000", 3],
            ] as (number | string)[][],
          },
        ],
        type: "line",
        legend: createMockLegendOptions(),
      };
      const mockProps: AssociateProcessorProps = {
        filters: {
          dataIndex: 0,
          sourceId: "test",
          duration: { startTime: "1000", endTime: "2000", step: "HOUR" },
        },
        option,
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { eventAssociate } = useAssociateProcessor(mockProps);
      const result = eventAssociate();

      expect(result).toBeDefined();
      expect(result?.series[0].markArea).toEqual({
        silent: true,
        itemStyle: { opacity: 0.3 },
        data: [[{ xAxis: "1000" }, { xAxis: "2000" }]],
      });
      expect(structuredCloneMock).toHaveBeenCalledWith(option.series);
    });

    it("preserves other series properties when adding markArea", () => {
      const option: FilterOption = {
        series: [
          {
            name: "Series1",
            data: [
              ["1000", 1],
              ["2000", 2],
            ] as (number | string)[][],
          },
          {
            name: "Series2",
            data: [
              ["1000", 3],
              ["2000", 4],
            ] as (number | string)[][],
          },
        ],
        type: "line",
        legend: createMockLegendOptions(),
      };
      const mockProps: AssociateProcessorProps = {
        filters: {
          dataIndex: 0,
          sourceId: "test",
          duration: { startTime: "1000", endTime: "2000", step: "HOUR" },
        },
        option,
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { eventAssociate } = useAssociateProcessor(mockProps);
      const result = eventAssociate();

      expect(result?.series).toHaveLength(2);
      expect(result?.series[0].name).toBe("Series1");
      expect(result?.series[0].markArea).toBeDefined();
      expect(result?.series[1].name).toBe("Series2");
      expect(result?.series[1].markArea).toBeUndefined();
    });
  });

  describe("traceFilters", () => {
    it("returns undefined when no currentParams provided", () => {
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option: { series: [], type: "line", legend: createMockLegendOptions() },
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { traceFilters } = useAssociateProcessor(mockProps);
      const result = traceFilters(null);
      expect(result).toBeUndefined();
    });

    it("returns object with undefined duration when no start time in intervalUnix", () => {
      mockAppStore.intervalUnix = [];
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option: { series: [], type: "line", legend: createMockLegendOptions() },
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { traceFilters } = useAssociateProcessor(mockProps);
      const currentParams: EventParams = {
        componentType: "chart",
        seriesType: "line",
        seriesIndex: 0,
        seriesName: "test",
        name: "test",
        data: [1000, 1],
        dataType: "number",
        value: 1,
        color: "#000",
        event: {},
        dataIndex: 0,
      };
      const result = traceFilters(currentParams);
      expect(result).toBeDefined();
      expect(result?.duration).toBeUndefined();
      expect(result?.metricValue).toEqual([]);
    });

    it("returns trace filters with duration when start time exists", () => {
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option: { series: [], type: "line", legend: createMockLegendOptions() },
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { traceFilters } = useAssociateProcessor(mockProps);
      const currentParams: EventParams = {
        componentType: "chart",
        seriesType: "line",
        seriesIndex: 0,
        seriesName: "test",
        name: "test",
        data: [1000, 1],
        dataType: "number",
        value: 1,
        color: "#000",
        event: {},
        dataIndex: 0,
      };
      const result = traceFilters(currentParams);

      expect(result).toBeDefined();
      expect(result?.duration).toEqual({
        startTime: "2023-01-01 12",
        endTime: "2023-01-01 12",
        step: "HOUR",
      });
      expect(result?.queryOrder).toBe("");
      expect(result?.status).toBe("");
    });

    it("includes relatedTrace properties when provided", () => {
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option: { series: [], type: "line", legend: createMockLegendOptions() },
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "SUCCESS",
          queryOrder: "BY_START_TIME",
          latency: true,
          enableRelate: true,
        },
      };
      const { traceFilters } = useAssociateProcessor(mockProps);
      const currentParams: EventParams = {
        componentType: "chart",
        seriesType: "line",
        seriesIndex: 0,
        seriesName: "test",
        name: "test",
        data: [1000, 1],
        dataType: "number",
        value: 1,
        color: "#000",
        event: {},
        dataIndex: 0,
      };
      const result = traceFilters(currentParams);

      expect(result?.status).toBe("SUCCESS");
      expect(result?.queryOrder).toBe("BY_START_TIME");
    });

    it("generates latency list when latency is enabled", () => {
      const option: FilterOption = {
        series: [
          {
            name: "Service1",
            data: [[1000, 100] as (number | string)[], [2000, 200] as (number | string)[]],
          },
          {
            name: "Service2",
            data: [[1000, 150] as (number | string)[], [2000, 250] as (number | string)[]],
          },
        ],
        type: "line",
        legend: createMockLegendOptions(),
      };
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option,
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: true,
          enableRelate: false,
        },
      };
      const { traceFilters } = useAssociateProcessor(mockProps);
      const currentParams: EventParams = {
        componentType: "chart",
        seriesType: "line",
        seriesIndex: 0,
        seriesName: "test",
        name: "test",
        data: [1000, 1],
        dataType: "number",
        value: 1,
        color: "#000",
        event: {},
        dataIndex: 0,
      };
      const result = traceFilters(currentParams);

      expect(result?.latency).toHaveLength(2);
      expect(result?.latency[0]).toEqual({
        label: "Service1--Service2",
        value: "0",
        data: [100, 150],
      });
      expect(result?.latency[1]).toEqual({
        label: "Service2--Infinity",
        value: "1",
        data: [150, Infinity],
      });
    });

    it("generates metricValue for all series", () => {
      const option: FilterOption = {
        series: [
          {
            name: "Service1",
            data: [[1000, 100] as (number | string)[], [2000, 200] as (number | string)[]],
          },
          {
            name: "Service2",
            data: [[1000, 150] as (number | string)[], [2000, 250] as (number | string)[]],
          },
        ],
        type: "line",
        legend: createMockLegendOptions(),
      };
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option,
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { traceFilters } = useAssociateProcessor(mockProps);
      const currentParams: EventParams = {
        componentType: "chart",
        seriesType: "line",
        seriesIndex: 0,
        seriesName: "test",
        name: "test",
        data: [1000, 1],
        dataType: "number",
        value: 1,
        color: "#000",
        event: {},
        dataIndex: 0,
      };
      const result = traceFilters(currentParams);

      expect(result?.metricValue).toHaveLength(2);
      expect(result?.metricValue[0]).toEqual({
        label: "Service1",
        value: "0",
        data: 100,
        date: 1000,
      });
      expect(result?.metricValue[1]).toEqual({
        label: "Service2",
        value: "1",
        data: 150,
        date: 1000,
      });
    });

    it("handles empty series gracefully", () => {
      const mockProps: AssociateProcessorProps = {
        filters: { dataIndex: 0, sourceId: "test" },
        option: { series: [], type: "line", legend: createMockLegendOptions() },
        relatedTrace: {
          duration: { start: "0", end: "0", step: "HOUR" },
          refIdType: "",
          status: "",
          queryOrder: "",
          latency: false,
          enableRelate: false,
        },
      };
      const { traceFilters } = useAssociateProcessor(mockProps);
      const currentParams: EventParams = {
        componentType: "chart",
        seriesType: "line",
        seriesIndex: 0,
        seriesName: "test",
        name: "test",
        data: [1000, 1],
        dataType: "number",
        value: 1,
        color: "#000",
        event: {},
        dataIndex: 0,
      };
      const result = traceFilters(currentParams);

      expect(result?.metricValue).toEqual([]);
      expect(result?.latency).toBeUndefined();
    });
  });
});
