import { World, setWorldConstructor } from '@cucumber/cucumber'
import {
  chromium,
  type Browser,
  type BrowserContext,
  type Page,
} from '@playwright/test'
import { config } from './types.js'

export class AppWorld extends World {
  browser!: Browser
  context!: BrowserContext
  page!: Page

  async openBrowser() {
    const headed = process.env.HEADED === '1' || process.env.HEADED === 'true'
    this.browser = await chromium.launch({
      headless: !headed,
      slowMo: headed ? 150 : 0,
    })
    this.context = await this.browser.newContext({
      locale: process.env.E2E_LOCALE ?? 'de-DE',
    })
    await this.context.addInitScript((apiUrl) => {
      ;(window as typeof window & { __E2E_API_URL?: string }).__E2E_API_URL = apiUrl
    }, config.apiUrl)
    this.page = await this.context.newPage()
    this.page.setDefaultTimeout(config.responseTimeout)
  }

  async closeBrowser() {
    await this.context?.close()
    await this.browser?.close()
  }

  async gotoApp(path: string) {
    const url = path.startsWith('http')
      ? path
      : `${config.appUrl}${path.startsWith('/') ? path : `/${path}`}`
    await this.page.goto(url)
    await this.page.waitForFunction(
      () => document.documentElement.dataset.appHydrated === 'true',
      undefined,
      { timeout: config.responseTimeout },
    )
    await this.page.waitForLoadState('networkidle', { timeout: 1000 }).catch(() => {})
    await this.page.waitForTimeout(500)
  }

  async login(email: string, password: string) {
    // Use Playwright's request API (same cookie jar, no SameSite restrictions)
    // so CSRF double-submit works across the auth-js proxy boundary.
    const csrfRes = await this.page.request.get(`${config.apiUrl}/auth/csrf`)
    const { csrfToken } = (await csrfRes.json()) as { csrfToken: string }
    const body = new URLSearchParams({ email, password, csrfToken, callbackUrl: '/' })
    await this.page.request.post(`${config.apiUrl}/auth/callback/credentials`, {
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      data: body.toString(),
    })
    // Navigate to the app after auth cookies are set
    await this.gotoApp('/companies')
  }

  async logout() {
    await this.gotoApp('/logout')
  }

  async callRpc(name: string, data: Record<string, unknown> = {}) {
    const apiUrl = config.apiUrl
    const result = await this.page.evaluate(
      async ({ apiUrl, name, data }) => {
        const res = await fetch(`${apiUrl}/rpc/${name}`, {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          credentials: 'include',
          body: JSON.stringify({ data }),
        })
        if (!res.ok) {
          const body = await res.text().catch(() => '')
          throw new Error(`RPC ${name} failed (${res.status}): ${body}`)
        }
        return res.json()
      },
      { apiUrl, name, data },
    )
    return result
  }

  async expectText(text: string) {
    const locator = this.page.getByText(text, { exact: false })
    const deadline = Date.now() + config.responseTimeout
    while (Date.now() < deadline) {
      const count = await locator.count()
      for (let i = 0; i < count; i++) {
        if (await locator.nth(i).isVisible().catch(() => false)) {
          return
        }
      }
      await this.page.waitForTimeout(100)
    }
    throw new Error(`Timed out waiting for visible text: ${text}`)
  }

  async getPageText(): Promise<string> {
    return this.page.innerText('body')
  }
}

setWorldConstructor(AppWorld)
