import { describe, expect, test } from "bun:test"
import { Share } from "../../src/core/share"
import { Storage } from "../../src/core/storage"
import { Identifier } from "@opencode-ai/core/util/identifier"

describe.concurrent("core.share", () => {
  test("should create a share", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    expect(share.sessionID).toBe(sessionID)
    expect(share.secret).toBeDefined()

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should remove a share as admin", async () => {
    const share = await Share.create({ sessionID: Identifier.descending() })

    await Share.removeAdmin({ id: share.id })

    expect(await Share.get(share.id)).toBeUndefined()
  })

  test("should sync data to a share", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    const data: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
      },
    ]

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data,
    })

    const snapshot = await Storage.read<{ data: Share.Data[] }>(["share_snapshot", share.id])
    expect(snapshot?.data).toHaveLength(1)

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should sync multiple batches of data", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    const data1: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
      },
    ]

    const data2: Share.Data[] = [
      {
        type: "part",
        data: { id: "part2", sessionID, messageID: "msg1", type: "text", text: "World" },
      },
    ]

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data: data1,
    })

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data: data2,
    })

    const snapshot = await Storage.read<{ data: Share.Data[] }>(["share_snapshot", share.id])
    expect(snapshot?.data).toHaveLength(2)

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should retrieve synced data", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    const data: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
      },
      {
        type: "part",
        data: { id: "part2", sessionID, messageID: "msg1", type: "text", text: "World" },
      },
    ]

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data,
    })

    const result = await Share.data(share.id)

    expect(result.length).toBe(2)
    expect(result[0].type).toBe("part")
    expect(result[1].type).toBe("part")

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should retrieve data from multiple syncs", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    const data1: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
      },
    ]

    const data2: Share.Data[] = [
      {
        type: "part",
        data: { id: "part2", sessionID, messageID: "msg2", type: "text", text: "World" },
      },
    ]

    const data3: Share.Data[] = [
      { type: "part", data: { id: "part3", sessionID, messageID: "msg3", type: "text", text: "!" } },
    ]

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data: data1,
    })

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data: data2,
    })

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data: data3,
    })

    const result = await Share.data(share.id)

    expect(result.length).toBe(3)
    const parts = result.filter((d) => d.type === "part")
    expect(parts.length).toBe(3)

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should return latest data when syncing duplicate parts", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    const data1: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
      },
    ]

    const data2: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello Updated" },
      },
    ]

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data: data1,
    })

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data: data2,
    })

    const result = await Share.data(share.id)

    expect(result.length).toBe(1)
    const [first] = result
    expect(first.type).toBe("part")
    expect(first.type === "part" && first.data.type === "text" && first.data.text).toBe("Hello Updated")

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should return empty array for share with no data", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    const result = await Share.data(share.id)

    expect(result).toEqual([])

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should migrate legacy event data into the snapshot", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })
    const data: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
      },
    ]

    await Storage.remove(["share_snapshot", share.id])
    await Storage.write(["share_event", share.id, Identifier.descending()], data)

    const result = await Share.data(share.id)
    const snapshot = await Storage.read<{ data: Share.Data[] }>(["share_snapshot", share.id])

    expect(result).toHaveLength(1)
    expect(snapshot?.data).toHaveLength(1)

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should throw error for invalid secret", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    const data: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Test" },
      },
    ]

    expect(async () => {
      await Share.sync({
        share: { id: share.id, secret: "invalid-secret" },
        data,
      })
    }).toThrow()

    await Share.remove({ id: share.id, secret: share.secret })
  })

  test("should throw error for non-existent share", async () => {
    const sessionID = Identifier.descending()
    const data: Share.Data[] = [
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Test" },
      },
    ]

    expect(async () => {
      await Share.sync({
        share: { id: "non-existent-id", secret: "some-secret" },
        data,
      })
    }).toThrow()
  })

  test("should handle different data types", async () => {
    const sessionID = Identifier.descending()
    const share = await Share.create({ sessionID })

    const data: Share.Data[] = [
      { type: "session", data: { id: sessionID, status: "running" } as any },
      { type: "message", data: { id: "msg1", sessionID } as any },
      {
        type: "part",
        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
      },
    ]

    await Share.sync({
      share: { id: share.id, secret: share.secret },
      data,
    })

    const result = await Share.data(share.id)

    expect(result.length).toBe(3)
    expect(result.some((d) => d.type === "session")).toBe(true)
    expect(result.some((d) => d.type === "message")).toBe(true)
    expect(result.some((d) => d.type === "part")).toBe(true)

    await Share.remove({ id: share.id, secret: share.secret })
  })
})
