import { describe, test, expect, beforeEach } from "bun:test";
import { mockClient } from "aws-sdk-client-mock";
import { DynamoDBClient, GetItemCommand, PutItemCommand } from "@aws-sdk/client-dynamodb";
import { DailyReportStore } from "@/scheduler/daily-report-store.js";
import type { PrSnapshot } from "@/scheduler/daily-report.js";

const ddbMock = mockClient(DynamoDBClient);

beforeEach(() => {
  ddbMock.reset();
});

describe("DailyReportStore", () => {
  const TABLE = "test-daily-report";

  describe("saveOpenPrs", () => {
    test("sends PutItemCommand with correct table, pk, and serialized data", async () => {
      ddbMock.on(PutItemCommand).resolves({});

      const store = new DailyReportStore(TABLE);
      const prs = new Map<string, PrSnapshot>([
        [
          "repo-a#1",
          { repo: "repo-a", number: 1, url: "https://github.com/repo-a/pull/1", title: "Fix A" },
        ],
      ]);

      await store.saveOpenPrs(prs);

      const calls = ddbMock.commandCalls(PutItemCommand);
      expect(calls).toHaveLength(1);

      const input = calls[0]!.args[0].input;
      expect(input.TableName).toBe(TABLE);
      expect(input.Item?.pk?.S).toBe("open-prs");

      const serialized = input.Item?.data?.S;
      expect(serialized).toBeDefined();
      const parsed: [string, PrSnapshot][] = JSON.parse(serialized!);
      expect(parsed).toEqual([
        [
          "repo-a#1",
          { repo: "repo-a", number: 1, url: "https://github.com/repo-a/pull/1", title: "Fix A" },
        ],
      ]);
    });
  });

  describe("loadOpenPrs", () => {
    test("returns populated Map when Item contains data", async () => {
      const entries: [string, PrSnapshot][] = [
        [
          "repo-b#42",
          {
            repo: "repo-b",
            number: 42,
            url: "https://github.com/repo-b/pull/42",
            title: "Update B",
          },
        ],
        [
          "repo-c#7",
          {
            repo: "repo-c",
            number: 7,
            url: "https://github.com/repo-c/pull/7",
            title: "Add C",
          },
        ],
      ];

      ddbMock.on(GetItemCommand).resolves({
        Item: {
          pk: { S: "open-prs" },
          data: { S: JSON.stringify(entries) },
        },
      });

      const store = new DailyReportStore(TABLE);
      const result = await store.loadOpenPrs();

      expect(result).toBeInstanceOf(Map);
      expect(result.size).toBe(2);
      expect(result.get("repo-b#42")).toEqual({
        repo: "repo-b",
        number: 42,
        url: "https://github.com/repo-b/pull/42",
        title: "Update B",
      });
      expect(result.get("repo-c#7")).toEqual({
        repo: "repo-c",
        number: 7,
        url: "https://github.com/repo-c/pull/7",
        title: "Add C",
      });
    });

    test("returns empty Map when GetItem returns no Item", async () => {
      ddbMock.on(GetItemCommand).resolves({});

      const store = new DailyReportStore(TABLE);
      const result = await store.loadOpenPrs();

      expect(result).toBeInstanceOf(Map);
      expect(result.size).toBe(0);
    });

    test("returns empty Map when Item exists but data.S is undefined", async () => {
      ddbMock.on(GetItemCommand).resolves({
        Item: {
          pk: { S: "open-prs" },
        },
      });

      const store = new DailyReportStore(TABLE);
      const result = await store.loadOpenPrs();

      expect(result).toBeInstanceOf(Map);
      expect(result.size).toBe(0);
    });

    test("propagates DynamoDB errors", async () => {
      ddbMock.on(GetItemCommand).rejects(new Error("DynamoDB service unavailable"));

      const store = new DailyReportStore(TABLE);

      await expect(store.loadOpenPrs()).rejects.toThrow("DynamoDB service unavailable");
    });
  });

  describe("GetItemCommand input", () => {
    test("uses correct table name and pk key in GetItem", async () => {
      ddbMock.on(GetItemCommand).resolves({});

      const store = new DailyReportStore(TABLE);
      await store.loadOpenPrs();

      const calls = ddbMock.commandCalls(GetItemCommand);
      expect(calls).toHaveLength(1);

      const input = calls[0]!.args[0].input;
      expect(input.TableName).toBe(TABLE);
      expect(input.Key).toEqual({ pk: { S: "open-prs" } });
    });
  });
});
