import { test, expect, type Browser, type Page } from '@playwright/test'
import { execFileSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import path from 'node:path'

type DashboardFixture = {
  email: string
  password: string
  username: string
}

const RECENT_VISITS_STORAGE_KEY = 'skinbase.dashboard.recent-visits'
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://skinbase26.test'
const VITE_MANIFEST_PATH = path.join(process.cwd(), 'public', 'build', 'manifest.json')

function ensureCompiledAssets() {
  if (existsSync(VITE_MANIFEST_PATH)) {
    return
  }

  const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'

  execFileSync(npmCommand, ['run', 'build'], {
    cwd: process.cwd(),
    stdio: 'inherit',
  })
}

function seedDashboardFixture(): DashboardFixture {
  const token = `${Date.now().toString().slice(-6)}${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`
  const email = `e2e-dashboard-${token}@example.test`
  const username = `e2ed${token}`.slice(0, 20)

  const script = [
    "use App\\Models\\User;",
    "use Illuminate\\Support\\Facades\\Hash;",
    `$user = User::updateOrCreate(['email' => '${email}'], [`,
    "  'name' => 'E2E Dashboard User',",
    `  'username' => '${username}',`,
    "  'onboarding_step' => 'complete',",
    "  'email_verified_at' => now(),",
    "  'is_active' => 1,",
    "  'password' => Hash::make('password'),",
    "]);",
    "echo json_encode(['email' => $user->email, 'password' => 'password', 'username' => $user->username]);",
  ].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 DashboardFixture
}

function setPinnedSpacesForFixture(fixture: DashboardFixture, pinnedSpaces: string[] = []) {
  const encodedPinnedSpaces = JSON.stringify(pinnedSpaces)
  const script = [
    "use App\\Models\\User;",
    "use App\\Models\\DashboardPreference;",
    `$user = User::where('email', '${fixture.email}')->firstOrFail();`,
    `DashboardPreference::updateOrCreate(['user_id' => $user->id], ['pinned_spaces' => json_decode('${encodedPinnedSpaces}', true)]);`,
    "echo 'ok';",
  ].join(' ')

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

function resetBotProtectionState() {
  const script = [
    "use Illuminate\\Support\\Facades\\DB;",
    "use Illuminate\\Support\\Facades\\Schema;",
    "foreach (['forum_bot_logs', 'forum_bot_ip_blacklist', 'forum_bot_device_fingerprints', 'forum_bot_behavior_profiles'] as $table) {",
    "  if (Schema::hasTable($table)) {",
    "    DB::table($table)->delete();",
    '  }',
    '}',
    "echo 'ok';",
  ].join(' ')

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

async function login(page: Page, fixture: DashboardFixture) {
  for (let attempt = 0; attempt < 2; attempt += 1) {
    await page.goto('/login')
    const emailField = page.locator('input[name="email"]')
    const viteManifestError = page.getByText(/Vite manifest not found/i)
    const internalServerError = page.getByText('Internal Server Error')

    await Promise.race([
      emailField.waitFor({ state: 'visible', timeout: 8000 }),
      viteManifestError.waitFor({ state: 'visible', timeout: 8000 }),
      internalServerError.waitFor({ state: 'visible', timeout: 8000 }),
    ])

    if (await viteManifestError.isVisible().catch(() => false)) {
      throw new Error('Dashboard Playwright login failed because the Vite manifest is missing. Run the frontend build before running this spec.')
    }

    if ((await internalServerError.isVisible().catch(() => false)) && !(await emailField.isVisible().catch(() => false))) {
      throw new Error('Dashboard Playwright login failed because the login page returned an internal server error before the form loaded.')
    }

    await emailField.fill(fixture.email)
    await page.locator('input[name="password"]').fill(fixture.password)
    await page.getByRole('button', { name: 'Sign In' }).click()

    try {
      await page.waitForURL((url) => url.pathname !== '/login', { timeout: 8000, waitUntil: 'domcontentloaded' })
      await expect(page.getByRole('button', { name: /E2E Dashboard User/i })).toBeVisible()
      return
    } catch {
      const suspiciousActivity = page.getByText('Suspicious activity detected.')
      if (attempt === 0 && (await suspiciousActivity.isVisible().catch(() => false))) {
        resetBotProtectionState()
        continue
      }

      throw new Error('Dashboard Playwright login failed before reaching an authenticated page.')
    }
  }
}

async function openDashboard(page: Page, fixture: DashboardFixture) {
  await login(page, fixture)
  await page.goto('/dashboard')
  await expect(page.getByRole('heading', { name: /welcome back/i })).toBeVisible()
}

async function openDashboardInFreshContext(browser: Browser, fixture: DashboardFixture) {
  const context = await browser.newContext({ baseURL: BASE_URL, ignoreHTTPSErrors: true })
  const page = await context.newPage()

  await openDashboard(page, fixture)

  return { context, page }
}

async function saveShortcutUpdate(page: Page, action: () => Promise<void>) {
  const responsePromise = page.waitForResponse(
    (response) => response.url().includes('/api/dashboard/preferences/shortcuts') && response.request().method() === 'PUT'
  )

  await action()

  const response = await responsePromise
  expect(response.ok()).toBeTruthy()
}

async function performShortcutUpdate(page: Page, action: () => Promise<void>) {
  const responsePromise = page.waitForResponse(
    (response) => response.url().includes('/api/dashboard/preferences/shortcuts') && response.request().method() === 'PUT'
  )

  await action()

  return responsePromise
}

function shortcutToast(page: Page) {
  return page.getByText(/Saving dashboard shortcuts|Dashboard shortcuts saved\./i).first()
}

async function seedRecentVisits(page: Page, items: Array<{ href: string; label: string; pinned?: boolean; lastVisitedAt?: string | null }>) {
  await page.evaluate(
    ({ storageKey, recentItems }) => {
      window.localStorage.setItem(storageKey, JSON.stringify(recentItems))
    },
    { storageKey: RECENT_VISITS_STORAGE_KEY, recentItems: items }
  )
}

function recentVisitsHeading(page: Page) {
  return page.getByRole('heading', { name: 'Recently visited dashboard spaces' })
}

function pinnedSection(page: Page) {
  return page
    .locator('section')
    .filter({ has: page.getByRole('heading', { name: 'Your fastest dashboard shortcuts' }) })
    .first()
}

async function pinnedShortcutLabels(page: Page): Promise<string[]> {
  return pinnedSection(page).locator('article h3').allTextContents()
}

async function pinShortcut(page: Page, name: string) {
  await saveShortcutUpdate(page, async () => {
    await page.getByRole('button', { name: `Pin ${name}` }).click()
  })
}

async function unpinShortcut(page: Page, name: string) {
  await saveShortcutUpdate(page, async () => {
    await pinnedSection(page).getByRole('button', { name: `Unpin ${name}` }).click()
  })
}

async function pinRecentShortcut(page: Page, name: string) {
  await saveShortcutUpdate(page, async () => {
    await page.getByRole('button', { name: `Pin ${name}` }).first().click()
  })
}

test.describe('Dashboard pinned shortcuts', () => {
  test.describe.configure({ mode: 'serial' })

  let fixture: DashboardFixture

  test.beforeAll(() => {
    ensureCompiledAssets()
    fixture = seedDashboardFixture()
  })

  test.beforeEach(() => {
    resetBotProtectionState()
    setPinnedSpacesForFixture(fixture, [])
  })

  test('pins shortcuts, preserves explicit order, and survives reload without local recents', async ({ page }) => {
    await openDashboard(page, fixture)

    await pinShortcut(page, 'Notifications')
    await expect(shortcutToast(page)).toBeVisible()
    await pinShortcut(page, 'Favorites')

    await expect(pinnedSection(page)).toBeVisible()
    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Notifications', 'Favorites'])

    await saveShortcutUpdate(page, async () => {
      await pinnedSection(page).getByRole('button', { name: 'Move Favorites earlier' }).click()
    })

    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Favorites', 'Notifications'])

    await page.evaluate((storageKey) => {
      window.localStorage.removeItem(storageKey)
    }, RECENT_VISITS_STORAGE_KEY)

    await page.reload({ waitUntil: 'domcontentloaded' })
    await expect(page.getByRole('heading', { name: /welcome back/i })).toBeVisible()
    await expect(pinnedSection(page)).toBeVisible()
    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Favorites', 'Notifications'])
  })

  test('pinned strip matches the visual baseline', async ({ page }) => {
    await openDashboard(page, fixture)

    await pinShortcut(page, 'Notifications')
    await pinShortcut(page, 'Favorites')

    await expect(pinnedSection(page)).toBeVisible()
    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Notifications', 'Favorites'])
    await expect(pinnedSection(page)).toHaveScreenshot('dashboard-pinned-strip.png', {
      animations: 'disabled',
      caret: 'hide',
      maxDiffPixels: 50,
    })
  })

  test('pinned strip matches the mobile visual baseline', async ({ page }) => {
    await page.setViewportSize({ width: 390, height: 844 })
    await openDashboard(page, fixture)

    await pinShortcut(page, 'Notifications')
    await pinShortcut(page, 'Favorites')

    await expect(pinnedSection(page)).toBeVisible()
    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Notifications', 'Favorites'])
    await expect(pinnedSection(page)).toHaveScreenshot('dashboard-pinned-strip-mobile.png', {
      animations: 'disabled',
      caret: 'hide',
      maxDiffPixels: 50,
    })
  })

  test('shows an error toast when shortcut persistence fails', async ({ page }) => {
    await openDashboard(page, fixture)

    await page.route('**/api/dashboard/preferences/shortcuts', async (route) => {
      await route.fulfill({
        status: 500,
        contentType: 'application/json',
        body: JSON.stringify({ message: 'Server error' }),
      })
    })

    const response = await performShortcutUpdate(page, async () => {
      await page.getByRole('button', { name: 'Pin Notifications' }).click()
    })

    expect(response.ok()).toBeFalsy()
    await expect(page.getByText('Could not save dashboard shortcuts. Refresh and try again.')).toBeVisible()
    await expect(pinnedSection(page)).toBeVisible()
    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Notifications'])
  })

  test('unpinning the last shortcut removes the pinned strip', async ({ page }) => {
    await openDashboard(page, fixture)

    await pinShortcut(page, 'Notifications')

    await expect(pinnedSection(page)).toBeVisible()
    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Notifications'])

    await unpinShortcut(page, 'Notifications')

    await expect(pinnedSection(page)).toHaveCount(0)
    await expect(page.getByRole('button', { name: 'Pin Notifications' }).first()).toBeVisible()
  })

  test('saved pinned order is restored in a fresh browser context', async ({ browser, page }) => {
    await openDashboard(page, fixture)

    await pinShortcut(page, 'Notifications')
    await pinShortcut(page, 'Favorites')

    await saveShortcutUpdate(page, async () => {
      await pinnedSection(page).getByRole('button', { name: 'Move Favorites earlier' }).click()
    })

    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Favorites', 'Notifications'])

    const fresh = await openDashboardInFreshContext(browser, fixture)

    try {
      await fresh.page.evaluate((storageKey) => {
        window.localStorage.removeItem(storageKey)
      }, RECENT_VISITS_STORAGE_KEY)

      await fresh.page.reload({ waitUntil: 'domcontentloaded' })
      await expect(fresh.page.getByRole('heading', { name: /welcome back/i })).toBeVisible()
      await expect(pinnedSection(fresh.page)).toBeVisible()
      await expect.poll(() => pinnedShortcutLabels(fresh.page)).toEqual(['Favorites', 'Notifications'])
    } finally {
      await fresh.context.close()
    }
  })

  test('pinning from recent cards feeds the same persisted pinned order', async ({ page }) => {
    await openDashboard(page, fixture)

    const now = new Date().toISOString()
    await seedRecentVisits(page, [
      {
        href: '/dashboard/notifications',
        label: 'Notifications',
        pinned: false,
        lastVisitedAt: now,
      },
      {
        href: '/dashboard/favorites',
        label: 'Favorites',
        pinned: false,
        lastVisitedAt: now,
      },
    ])

    await page.reload({ waitUntil: 'domcontentloaded' })
    await expect(page.getByRole('heading', { name: /welcome back/i })).toBeVisible()
    await expect(recentVisitsHeading(page)).toBeVisible()

    await pinRecentShortcut(page, 'Notifications')
    await pinRecentShortcut(page, 'Favorites')

    await expect(pinnedSection(page)).toBeVisible()
    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Notifications', 'Favorites'])

    await page.evaluate((storageKey) => {
      window.localStorage.removeItem(storageKey)
    }, RECENT_VISITS_STORAGE_KEY)

    await page.reload({ waitUntil: 'domcontentloaded' })
    await expect(page.getByRole('heading', { name: /welcome back/i })).toBeVisible()
    await expect(pinnedSection(page)).toBeVisible()
    await expect.poll(() => pinnedShortcutLabels(page)).toEqual(['Notifications', 'Favorites'])
  })
})
