import { test, expect } from '@playwright/test'
import { execFileSync } from 'node:child_process'

type Fixture = {
  email: string
  password: string
  conversation_id: number
  latest_message_id: number
}

type InsertedMessage = {
  id: number
  body: string
}

type ConversationSeed = {
  conversation_id: number
  latest_message_id: number
}

function seedMessagingFixture(): Fixture {
  const token = `${Date.now().toString().slice(-6)}${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`
  const ownerEmail = `e2e-messages-owner-${token}@example.test`
  const peerEmail = `e2e-messages-peer-${token}@example.test`
  const ownerUsername = `e2eo${token}`.slice(0, 20)
  const peerUsername = `e2ep${token}`.slice(0, 20)

  const script = [
    "use App\\Models\\User;",
    "use App\\Models\\Conversation;",
    "use App\\Models\\ConversationParticipant;",
    "use App\\Models\\Message;",
    "use Illuminate\\Support\\Facades\\Hash;",
    "use Illuminate\\Support\\Carbon;",
    `$owner = User::updateOrCreate(['email' => '${ownerEmail}'], [`,
    "  'name' => 'E2E Owner',",
    `  'username' => '${ownerUsername}',`,
    "  'onboarding_step' => 'complete',",
    "  'email_verified_at' => now(),",
    "  'is_active' => 1,",
    "  'password' => Hash::make('password'),",
    "]);",
    `$peer = User::updateOrCreate(['email' => '${peerEmail}'], [`,
    "  'name' => 'E2E Peer',",
    `  'username' => '${peerUsername}',`,
    "  'onboarding_step' => 'complete',",
    "  'email_verified_at' => now(),",
    "  'is_active' => 1,",
    "  'password' => Hash::make('password'),",
    "]);",
    "$conversation = Conversation::create(['type' => 'direct', 'created_by' => $owner->id]);",
    "ConversationParticipant::insert([",
    "  ['conversation_id' => $conversation->id, 'user_id' => $owner->id, 'role' => 'admin', 'joined_at' => now(), 'last_read_at' => null],",
    "  ['conversation_id' => $conversation->id, 'user_id' => $peer->id, 'role' => 'member', 'joined_at' => now(), 'last_read_at' => now()],",
    "]);",
    "$first = Message::create(['conversation_id' => $conversation->id, 'sender_id' => $peer->id, 'body' => 'Seed hello']);",
    "$last = Message::create(['conversation_id' => $conversation->id, 'sender_id' => $owner->id, 'body' => 'Seed latest from owner']);",
    "$conversation->update(['last_message_at' => $last->created_at]);",
    "ConversationParticipant::where('conversation_id', $conversation->id)->where('user_id', $peer->id)->update(['last_read_at' => Carbon::parse($last->created_at)->addSeconds(15)]);",
    "echo json_encode(['email' => $owner->email, 'password' => 'password', 'conversation_id' => $conversation->id, 'latest_message_id' => $last->id]);",
  ].join(' ')

  const raw = execFileSync('php', ['artisan', 'tinker', `--execute=${script}`], {
    cwd: process.cwd(),
    encoding: 'utf8',
  })

  const lines = raw
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter(Boolean)

  const jsonLine = [...lines].reverse().find((line) => line.startsWith('{') && line.endsWith('}'))
  if (!jsonLine) {
    throw new Error(`Unable to parse fixture JSON from tinker output: ${raw}`)
  }

  return JSON.parse(jsonLine) as Fixture
}

function insertReconnectRecoveryMessage(conversationId: number): InsertedMessage {
  const token = `${Date.now()}-${Math.floor(Math.random() * 100000)}`
  const body = `Reconnect recovery ${token}`

  const script = [
    "use App\\Models\\Conversation;",
    "use App\\Models\\ConversationParticipant;",
    "use App\\Models\\Message;",
    `$conversation = Conversation::query()->findOrFail(${conversationId});`,
    "$senderId = ConversationParticipant::query()->where('conversation_id', $conversation->id)->where('role', 'member')->value('user_id');",
    `$message = Message::create(['conversation_id' => $conversation->id, 'sender_id' => $senderId, 'body' => '${body}']);`,
    "$conversation->update(['last_message_id' => $message->id, 'last_message_at' => $message->created_at]);",
    "echo json_encode(['id' => $message->id, 'body' => $message->body]);",
  ].join(' ')

  const raw = execFileSync('php', ['artisan', 'tinker', `--execute=${script}`], {
    cwd: process.cwd(),
    encoding: 'utf8',
  })

  const lines = raw
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter(Boolean)

  const jsonLine = [...lines].reverse().find((line) => line.startsWith('{') && line.endsWith('}'))
  if (!jsonLine) {
    throw new Error(`Unable to parse inserted message JSON from tinker output: ${raw}`)
  }

  return JSON.parse(jsonLine) as InsertedMessage
}

function seedAdditionalConversation(conversationId: number): ConversationSeed {
  const script = [
    "use App\\Models\\Conversation;",
    "use App\\Models\\ConversationParticipant;",
    "use App\\Models\\Message;",
    "use Illuminate\\Support\\Carbon;",
    "use Illuminate\\Support\\Facades\\Cache;",
    `$original = Conversation::query()->findOrFail(${conversationId});`,
    "$ownerId = (int) $original->created_by;",
    "$peerId = (int) ConversationParticipant::query()->where('conversation_id', $original->id)->where('user_id', '!=', $ownerId)->value('user_id');",
    "$conversation = Conversation::create(['type' => 'direct', 'created_by' => $ownerId]);",
    "ConversationParticipant::insert([",
    "  ['conversation_id' => $conversation->id, 'user_id' => $ownerId, 'role' => 'admin', 'joined_at' => now(), 'last_read_at' => null],",
    "  ['conversation_id' => $conversation->id, 'user_id' => $peerId, 'role' => 'member', 'joined_at' => now(), 'last_read_at' => now()],",
    "]);",
    "$first = Message::create(['conversation_id' => $conversation->id, 'sender_id' => $peerId, 'body' => 'Seed hello']);",
    "$last = Message::create(['conversation_id' => $conversation->id, 'sender_id' => $ownerId, 'body' => 'Seed latest from owner']);",
    "$conversation->update(['last_message_id' => $last->id, 'last_message_at' => $last->created_at]);",
    "ConversationParticipant::where('conversation_id', $conversation->id)->where('user_id', $peerId)->update(['last_read_at' => Carbon::parse($last->created_at)->addSeconds(15)]);",
    "foreach ([$ownerId, $peerId] as $uid) {",
    "  $versionKey = 'messages:conversations:version:' . $uid;",
    "  Cache::add($versionKey, 1, now()->addDay());",
    "  Cache::increment($versionKey);",
    "}",
    "echo json_encode(['conversation_id' => $conversation->id, 'latest_message_id' => $last->id]);",
  ].join(' ')

  const raw = execFileSync('php', ['artisan', 'tinker', `--execute=${script}`], {
    cwd: process.cwd(),
    encoding: 'utf8',
  })

  const lines = raw
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter(Boolean)

  const jsonLine = [...lines].reverse().find((line) => line.startsWith('{') && line.endsWith('}'))
  if (!jsonLine) {
    throw new Error(`Unable to parse additional conversation JSON from tinker output: ${raw}`)
  }

  return JSON.parse(jsonLine) as ConversationSeed
}

async function login(page: Parameters<typeof test>[0]['page'], fixture: Fixture) {
  const baseUrl = process.env.PLAYWRIGHT_BASE_URL || 'http://skinbase26.test'

  await page.context().addCookies([
    {
      name: 'e2e_bot_bypass',
      value: '1',
      url: baseUrl,
    },
  ])

  await page.goto('/login')
  await page.locator('input[name="email"]').fill(fixture.email)
  await page.locator('input[name="password"]').fill(fixture.password)
  await page.getByRole('button', { name: 'Sign In' }).click()
  await page.waitForURL(/\/dashboard/)
}

async function waitForRealtimeConnection(page: Parameters<typeof test>[0]['page']) {
  await page.waitForFunction(() => {
    const echo = window.Echo
    const connection = echo?.connector?.pusher?.connection

    return Boolean(echo && connection && typeof connection.emit === 'function')
  }, { timeout: 10000 })
}

test.describe('Messaging UI', () => {
  test.describe.configure({ mode: 'serial' })

  let fixture: Fixture

  test.beforeAll(() => {
    fixture = seedMessagingFixture()
  })

  test('restores draft from localStorage on reload', async ({ page }) => {
    await login(page, fixture)
    await page.goto(`/messages/${fixture.conversation_id}`)

    const textarea = page.locator('textarea[placeholder^="Write a message"]')
    const draft = 'E2E draft should survive reload'

    await textarea.fill(draft)
    await expect(textarea).toHaveValue(draft)

    await page.reload({ waitUntil: 'domcontentloaded' })

    await expect(textarea).toHaveValue(draft)

    const stored = await page.evaluate((key) => window.localStorage.getItem(key), `nova_draft_${fixture.conversation_id}`)
    expect(stored).toBe(draft)
  })

  test('shows seen indicator on latest direct message from current user', async ({ page }) => {
    await login(page, fixture)
    await page.goto(`/messages/${fixture.conversation_id}`)

    await expect(page.locator(`#message-${fixture.latest_message_id}`)).toContainText('Seed latest from owner')
    await expect(page.locator('text=/^Seen\\s.+\\sago$/')).toBeVisible()
  })

  test('reconnect recovery fetches missed messages through delta without duplicates', async ({ page }) => {
    await login(page, fixture)
    await page.goto(`/messages/${fixture.conversation_id}`)
    await waitForRealtimeConnection(page)

    await expect(page.locator(`#message-${fixture.latest_message_id}`)).toContainText('Seed latest from owner')

    const inserted = insertReconnectRecoveryMessage(fixture.conversation_id)

    await page.evaluate(() => {
      const connection = window.Echo?.connector?.pusher?.connection
      connection?.emit?.('connected')
    })

    await expect(page.locator(`#message-${inserted.id}`)).toContainText(inserted.body)
    await expect(page.locator(`#message-${inserted.id}`)).toHaveCount(1)

    await page.evaluate(() => {
      const connection = window.Echo?.connector?.pusher?.connection
      connection?.emit?.('connected')
    })

    await expect(page.locator(`#message-${inserted.id}`)).toHaveCount(1)
  })

  test('reconnect recovery keeps sidebar unread summary consistent', async ({ page }) => {
    await login(page, fixture)

    await page.route(/\/api\/messages\/\d+\/read$/, (route) => route.abort())

    const isolatedConversation = seedAdditionalConversation(fixture.conversation_id)
    await page.goto(`/messages/${isolatedConversation.conversation_id}`)

    const unreadStat = page.getByTestId('messages-stat-unread')
    const unreadBefore = parseUnreadCount(await unreadStat.innerText())
    await waitForRealtimeConnection(page)

    const inserted = insertReconnectRecoveryMessage(isolatedConversation.conversation_id)

    await page.evaluate(() => {
      const connection = window.Echo?.connector?.pusher?.connection
      connection?.emit?.('connected')
    })

    await expect(page.locator(`#message-${inserted.id}`)).toContainText(inserted.body)

    const unreadAfter = parseUnreadCount(await unreadStat.innerText())
    expect(unreadAfter).toBeGreaterThan(unreadBefore)
  })
})

function parseUnreadCount(text: string): number {
  const digits = (text.match(/\d+/g) ?? []).join('')
  return Number.parseInt(digits || '0', 10)
}
