import { v4 } from "uuid";
import { prisma } from "@langfuse/shared/src/db";
import {
  createObservation,
  createObservationsCh,
  createOrgProjectAndApiKey,
  createScoresCh,
  createSessionScore,
  createTracesCh,
  getSessionsWithMetrics,
} from "@langfuse/shared/src/server";
import { createTrace, getSessionsTable } from "@langfuse/shared/src/server";
import { type FilterState } from "@langfuse/shared";

describe("trpc.sessions", () => {
  describe("GET sessions.all", () => {
    it("should GET all session", async () => {
      const { projectId } = await createOrgProjectAndApiKey();
      const sessionId = v4();

      await prisma.traceSession.create({
        data: {
          id: sessionId,
          projectId: projectId,
        },
      });

      const traces = [
        createTrace({ session_id: sessionId, project_id: projectId }),
        createTrace({ session_id: sessionId, project_id: projectId }),
      ];

      await createTracesCh(traces);

      const uiSessions = await getSessionsTable({
        projectId: projectId,
        filter: [],
        orderBy: null,
        limit: 10000,
        page: 0,
      });

      expect(uiSessions.length).toBe(1);
      expect(uiSessions[0].session_id).toBe(sessionId);
      expect(uiSessions[0].trace_count).toBe(2);
      expect(uiSessions[0].trace_tags).toEqual(["doe", "john"]);
    });
  });

  it("should GET all session filtered by trace attribute only", async () => {
    const { projectId } = await createOrgProjectAndApiKey();
    const sessionId = v4();

    await prisma.traceSession.create({
      data: {
        id: sessionId,
        projectId: projectId,
      },
    });

    const traces = [
      createTrace({
        session_id: sessionId,
        project_id: projectId,
        user_id: "user1",
      }),
      createTrace({
        session_id: sessionId,
        project_id: projectId,
        user_id: undefined,
      }),
    ];

    await createTracesCh(traces);

    const uiSessions = await getSessionsTable({
      projectId: projectId,
      filter: [
        {
          column: "userIds",
          type: "stringOptions",
          operator: "any of",
          value: ["user1"],
        },
      ],
      orderBy: null,
      limit: 10000,
      page: 0,
    });

    expect(uiSessions.length).toBe(1);
    expect(uiSessions[0].session_id).toBe(sessionId);
    expect(uiSessions[0].trace_count).toBe(2);
    expect(uiSessions[0].trace_tags).toEqual(["doe", "john"]);
    expect(uiSessions[0].user_ids).toEqual(["user1"]);
  });

  it("should GET sessions ordered by total cost", async () => {
    const { projectId } = await createOrgProjectAndApiKey();
    const sessionId1 = v4();
    const sessionId2 = v4();

    await prisma.traceSession.createMany({
      data: [
        {
          id: sessionId1,
          projectId: projectId,
        },
        {
          id: sessionId2,
          projectId: projectId,
        },
      ],
    });

    const traces = [
      createTrace({
        session_id: sessionId1,
        project_id: projectId,
      }),
      createTrace({
        session_id: sessionId2,
        project_id: projectId,
      }),
    ];

    const observations = [
      createObservation({
        trace_id: traces[0].id,
        project_id: projectId,
        cost_details: {
          input: 0.1,
          output: 0.2,
          total: 0.3,
        },
        total_cost: 0.3,
      }),
      createObservation({
        trace_id: traces[1].id,
        project_id: projectId,
        cost_details: {
          input: 0.3,
          output: 0.4,
          total: 0.7,
        },
        total_cost: 0.7,
      }),
    ];

    await createTracesCh(traces);
    await createObservationsCh(observations);

    const uiSessions = await getSessionsTable({
      projectId: projectId,
      filter: [],
      orderBy: {
        column: "totalCost",
        order: "DESC" as const,
      },
      limit: 10000,
      page: 0,
    });

    expect(uiSessions.length).toBe(2);
    expect(uiSessions[0].session_id).toBe(sessionId2);
    expect(uiSessions[1].session_id).toBe(sessionId1);
  });

  it("should GET sessions ordered by duration", async () => {
    const { projectId } = await createOrgProjectAndApiKey();
    const sessionId1 = v4();
    const sessionId2 = v4();

    await prisma.traceSession.createMany({
      data: [
        {
          id: sessionId1,
          projectId: projectId,
        },
        {
          id: sessionId2,
          projectId: projectId,
        },
      ],
    });

    const traces = [
      createTrace({
        session_id: sessionId1,
        project_id: projectId,
      }),
      createTrace({
        session_id: sessionId2,
        project_id: projectId,
      }),
    ];
    const observations = [
      createObservation({
        trace_id: traces[0].id,
        project_id: projectId,
        start_time: new Date("2024-01-01T00:00:00Z").getTime(),
        end_time: new Date("2024-01-01T00:00:10Z").getTime(), // 10 second duration
      }),
      createObservation({
        trace_id: traces[1].id,
        project_id: projectId,
        start_time: new Date("2024-01-01T00:00:00Z").getTime(),
        end_time: new Date("2024-01-01T00:00:20Z").getTime(), // 20 second duration
      }),
    ];

    await createTracesCh(traces);
    await createObservationsCh(observations);

    const uiSessions = await getSessionsTable({
      projectId: projectId,
      filter: [],
      orderBy: {
        column: "sessionDuration",
        order: "DESC" as const,
      },
      limit: 10000,
      page: 0,
    });

    expect(uiSessions.length).toBe(2);
    expect(uiSessions[0].session_id).toBe(sessionId2);
    expect(uiSessions[1].session_id).toBe(sessionId1);
  });

  it("should GET metrics for a list of sessions", async () => {
    const { projectId } = await createOrgProjectAndApiKey();
    const sessionId1 = v4();
    const sessionId2 = v4();

    await prisma.traceSession.createMany({
      data: [
        {
          id: sessionId1,
          projectId: projectId,
        },
        {
          id: sessionId2,
          projectId: projectId,
        },
      ],
    });

    const traces = [
      createTrace({
        session_id: sessionId1,
        project_id: projectId,
        user_id: "user1",
      }),
      createTrace({
        session_id: sessionId1,
        project_id: projectId,
        user_id: "user2",
      }),
      createTrace({
        session_id: sessionId2,
        project_id: projectId,
        user_id: "user3",
      }),
    ];

    const observations = traces.flatMap((trace) => [
      createObservation({
        trace_id: trace.id,
        project_id: projectId,
      }),
      createObservation({
        trace_id: trace.id,
        project_id: projectId,
      }),
    ]);

    await createObservationsCh(observations);

    await createTracesCh(traces);

    const sessions = await getSessionsWithMetrics({
      projectId: projectId,
      filter: [
        {
          column: "id",
          type: "stringOptions",
          operator: "any of",
          value: [sessionId1, sessionId2],
        },
      ],
    });

    expect(sessions.length).toBe(2);

    // Session 1 checks
    const session1 = sessions.find((s) => s.session_id === sessionId1);
    expect(session1).toBeDefined();
    expect(session1?.trace_count).toBe(2);
    expect(session1?.user_ids).toEqual(
      expect.arrayContaining(["user1", "user2"]),
    );
    expect(session1?.trace_tags).toEqual(["doe", "john"]);
    expect(session1?.total_observations).toEqual(4);

    expect(Number(session1?.session_input_cost)).toBeGreaterThan(0);
    expect(Number(session1?.session_output_cost)).toBeGreaterThan(0);
    expect(Number(session1?.session_total_cost)).toBeGreaterThan(0);
    expect(Number(session1?.session_input_usage)).toBeGreaterThan(0);
    expect(Number(session1?.session_output_usage)).toBeGreaterThan(0);
    expect(Number(session1?.session_total_usage)).toBeGreaterThan(0);

    // Session 2 checks
    const session2 = sessions.find((s) => s.session_id === sessionId2);
    expect(session2).toBeDefined();
    expect(session2?.trace_count).toBe(1);
    expect(session2?.user_ids).toEqual(["user3"]);
    expect(session2?.trace_tags).toEqual(["doe", "john"]);
    expect(session2?.total_observations).toEqual(2);

    expect(Number(session2?.session_input_cost)).toBeGreaterThan(0);
    expect(Number(session2?.session_output_cost)).toBeGreaterThan(0);
    expect(Number(session2?.session_total_cost)).toBeGreaterThan(0);
    expect(Number(session2?.session_input_usage)).toBeGreaterThan(0);
    expect(Number(session2?.session_output_usage)).toBeGreaterThan(0);
    expect(Number(session2?.session_total_usage)).toBeGreaterThan(0);
  });

  it("LFE-4113: should GET correct metrics for a list of sessions without observations", async () => {
    const { projectId } = await createOrgProjectAndApiKey();
    const sessionId = v4();

    await prisma.traceSession.createMany({
      data: [
        {
          id: sessionId,
          projectId: projectId,
        },
      ],
    });

    const traces = [
      createTrace({
        session_id: sessionId,
        project_id: projectId,
        user_id: "user1",
      }),
      createTrace({
        session_id: sessionId,
        project_id: projectId,
        user_id: "user3",
      }),
    ];

    await createTracesCh(traces);

    // Only trace 2 has observations
    const observations = [
      createObservation({
        trace_id: traces[1].id,
        project_id: projectId,
        start_time: new Date().getTime() - 1000,
      }),
      createObservation({
        trace_id: traces[1].id,
        project_id: projectId,
        start_time: new Date().getTime(),
      }),
    ];

    await createObservationsCh(observations);

    const sessions = await getSessionsWithMetrics({
      projectId: projectId,
      filter: [
        {
          column: "id",
          type: "stringOptions",
          operator: "any of",
          value: [sessionId],
        },
      ],
    });

    expect(sessions.length).toBe(1);

    expect(sessions[0]).toBeDefined();
    expect(sessions[0]?.trace_count).toBe(2);
    expect(parseInt(sessions[0]?.duration as any)).toBe(1);
  });
  it("should GET correct session data with filters", async () => {
    const project_id = v4();
    const trace_id_with_score = v4();
    const session_id_with_score = v4();
    const trace_id_without_score = v4();
    const session_id_without_score = v4();

    const filterState: FilterState = [
      {
        type: "numberObject",
        column: "Scores (numeric)",
        key: "test",
        operator: ">",
        value: 0,
      },
    ];

    const trace_with_score = createTrace({
      id: trace_id_with_score,
      project_id,
      session_id: session_id_with_score,
    });
    const trace_without_score = createTrace({
      id: trace_id_without_score,
      project_id,
      session_id: session_id_without_score,
    });
    await createTracesCh([trace_with_score, trace_without_score]);

    const score = createSessionScore({
      project_id,
      session_id: session_id_with_score,
      name: "test",
      value: 1,
      data_type: "NUMERIC",
    });
    await createScoresCh([score]);

    const tableRows = await getSessionsTable({
      projectId: project_id,
      filter: filterState,
      limit: 10,
      page: 0,
    });

    expect(tableRows).toHaveLength(1);
    expect(tableRows[0].session_id).toEqual(session_id_with_score);
  });
});
