import {afterAll, beforeAll, expect, test} from 'bun:test'

import {
  befriend,
  cleanupCreatedUsers,
  createUser,
  request,
  type TestUser,
} from '../test-fixtures'

let alice: TestUser
let bob: TestUser
let stranger: TestUser

beforeAll(async () => {
  ;[alice, bob, stranger] = await Promise.all([
    createUser(),
    createUser(),
    createUser(),
  ])
  await befriend(alice, bob)
  // alice authors 3 crumbs and 2 trails
  for (const name of ['one', 'two', 'three'])
    await request(alice, 'POST', '/crumbs', {name})
  for (const name of ['t1', 't2'])
    await request(alice, 'POST', '/trails', {name})
})

afterAll(cleanupCreatedUsers)

test('profile returns counts and previews to a friend', async () => {
  const res = await request(bob, 'GET', `/users/${alice.id}`)
  expect(res.status).toBe(200)
  const {profile} = res.json
  expect(profile.crumbCount).toBe(3)
  expect(profile.trailCount).toBe(2)
  // newest first
  expect(profile.crumbPreview).toBe('three')
  expect(profile.trailPreview).toBe('t2')
})

test('a stranger sees the profile but no content counts', async () => {
  const res = await request(stranger, 'GET', `/users/${alice.id}`)
  expect(res.status).toBe(200)
  expect(res.json.profile.crumbCount).toBe(0)
  expect(res.json.profile.trailCount).toBe(0)
})

test('profile crumbs paginate for a friend', async () => {
  const first = await request(
    bob,
    'GET',
    `/users/${alice.id}/crumbs?limit=2`,
  )
  expect(first.status).toBe(200)
  expect(first.json.crumbs).toHaveLength(2)
  expect(first.json.nextCursor).toBeTruthy()

  const second = await request(
    bob,
    'GET',
    `/users/${alice.id}/crumbs?limit=2&cursor=${encodeURIComponent(first.json.nextCursor)}`,
  )
  expect(second.json.crumbs).toHaveLength(1)
  expect(second.json.nextCursor).toBeNull()
})

test("a friend can page through a profile's friends", async () => {
  const carol = await createUser()
  await befriend(carol, alice)

  const first = await request(
    bob,
    'GET',
    `/users/${alice.id}/friends?limit=1`,
  )
  expect(first.status).toBe(200)
  expect(first.json.friends).toHaveLength(1)
  expect(first.json.nextCursor).toBeTruthy()

  const second = await request(
    bob,
    'GET',
    `/users/${alice.id}/friends?limit=1&cursor=${encodeURIComponent(first.json.nextCursor)}`,
  )
  expect(second.json.friends).toHaveLength(1)
  expect(second.json.nextCursor).toBeNull()

  const ids = [...first.json.friends, ...second.json.friends].map(
    (f: {id: string}) => f.id,
  )
  expect(new Set(ids)).toEqual(new Set([bob.id, carol.id]))

  // statuses are viewer-relative: bob sees himself, not alice's view
  const all = await request(bob, 'GET', `/users/${alice.id}/friends`)
  const self = all.json.friends.find(
    (f: {id: string}) => f.id === bob.id,
  )
  expect(self.friendshipStatus).toBe('self')
})

test("a profile's friends are friends-only", async () => {
  expect(
    (await request(stranger, 'GET', `/users/${alice.id}/friends`))
      .status,
  ).toBe(403)
})

test('profile crumbs and trails are friends-only', async () => {
  expect(
    (await request(stranger, 'GET', `/users/${alice.id}/crumbs`))
      .status,
  ).toBe(403)
  expect(
    (await request(stranger, 'GET', `/users/${alice.id}/trails`))
      .status,
  ).toBe(403)
})

test("profile trails carry the viewer's pin state, not the target's", async () => {
  const created = await request(alice, 'POST', '/trails', {
    name: 'pinned by alice',
  })
  const trailId = created.json.trail.id
  await request(alice, 'PUT', `/trails/${trailId}/pin`)

  const own = await request(alice, 'GET', '/trails')
  expect(
    own.json.trails.find((t: {id: string}) => t.id === trailId)
      .pinned,
  ).toBe(true)

  const viewed = await request(
    bob,
    'GET',
    `/users/${alice.id}/trails`,
  )
  expect(viewed.status).toBe(200)
  expect(
    viewed.json.trails.find((t: {id: string}) => t.id === trailId)
      .pinned,
  ).toBe(false)
})
