/**
 * [TEC-3077] Smoke test các route chính — sàn giao dịch Goodgo Platform AI
 *
 * Chạy E2E với backend thật (apps/api + Postgres + Redis).
 * Không stub/mock bất kỳ API call nào.
 *
 * npx playwright test --project=web e2e/web/trading-floor-smoke.spec.ts
 */
import { test, expect } from '@playwright/test';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Thu thập console errors và network errors trong suốt test */
function attachErrorListeners(page: import('@playwright/test').Page) {
  const consoleErrors: string[] = [];
  const networkErrors: { url: string; status: number }[] = [];

  page.on('console', (msg) => {
    if (msg.type() === 'error') {
      const text = msg.text();
      // Bỏ qua nhiễu từ Mapbox token và NEXT_PUBLIC vars chưa set trong test env
      if (
        text.includes('mapbox') ||
        text.includes('NEXT_PUBLIC_MAPBOX') ||
        text.includes('Failed to load resource') ||
        text.includes('net::ERR')
      ) return;
      consoleErrors.push(text);
    }
  });

  page.on('response', (res) => {
    const status = res.status();
    const url = res.url();
    // Chỉ bắt lỗi từ API nội bộ (bỏ qua CDN, fonts, static assets)
    if (
      status >= 400 &&
      (url.includes('/api/v1') || url.includes('localhost'))
    ) {
      networkErrors.push({ url, status });
    }
  });

  return { consoleErrors, networkErrors };
}

// ---------------------------------------------------------------------------
// Route: / — Home dashboard ticker-style (TEC-3058)
// ---------------------------------------------------------------------------

test.describe('@smoke Home dashboard — ticker-style', () => {
  test('tải trang, ticker hiển thị và không có lỗi', async ({ page }) => {
    const { consoleErrors, networkErrors } = attachErrorListeners(page);

    await page.goto('/');
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

    // Không crash với 500 error boundary
    await expect(
      page.getByRole('heading', { name: /500|server error|lỗi máy chủ/i }),
    ).not.toBeVisible();

    // Trang có tiêu đề hợp lệ
    await expect(page).toHaveTitle(/GoodGo/i);

    // Heading H1 hoặc ticker bar phải render
    const heroOrTicker = page
      .locator('h1')
      .or(page.locator('[data-testid="ticker"]'))
      .or(page.locator('[class*="ticker"]'));
    await expect(heroOrTicker.first()).toBeVisible({ timeout: 15_000 });

    expect(consoleErrors, `Console errors: ${consoleErrors.join(', ')}`).toHaveLength(0);
    expect(
      networkErrors.filter((e) => e.status >= 500),
      `Network 5xx: ${networkErrors.map((e) => `${e.status} ${e.url}`).join(', ')}`,
    ).toHaveLength(0);
  });

  test('market data section render (KPI cards hoặc market summary)', async ({ page }) => {
    await page.goto('/');
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

    // KPI card hoặc market summary phải có ít nhất 1 phần tử
    const marketSection = page
      .locator('[data-testid="kpi-card"]')
      .or(page.locator('[class*="kpi"]'))
      .or(page.getByText(/giá tb|avg price|tổng tin|market/i));
    await expect(marketSection.first()).toBeVisible({ timeout: 15_000 });
  });
});

// ---------------------------------------------------------------------------
// Route: /listings — Listings board high-density (TEC-3059)
// ---------------------------------------------------------------------------

test.describe('@smoke Listings board — high-density', () => {
  test('tải trang, DataTable render và không có 5xx', async ({ page }) => {
    const { consoleErrors, networkErrors } = attachErrorListeners(page);

    await page.goto('/listings');
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

    // Không có error boundary
    await expect(
      page.getByRole('heading', { name: /500|server error/i }),
    ).not.toBeVisible();

    // Bảng listings hoặc danh sách BĐS phải hiển thị
    const listingContainer = page
      .locator('table')
      .or(page.locator('[data-testid="data-table"]'))
      .or(page.locator('[class*="listings"]'))
      .or(page.getByRole('grid'));
    await expect(listingContainer.first()).toBeVisible({ timeout: 15_000 });

    expect(consoleErrors, `Console errors: ${consoleErrors.join(', ')}`).toHaveLength(0);
    expect(
      networkErrors.filter((e) => e.status >= 500),
      `Network 5xx: ${networkErrors.map((e) => `${e.status} ${e.url}`).join(', ')}`,
    ).toHaveLength(0);
  });

  test('DensityToggle hiển thị và chuyển đổi được', async ({ page }) => {
    await page.goto('/listings');

    const densityToggle = page
      .locator('[data-testid="density-toggle"]')
      .or(page.getByRole('button', { name: /compact|comfortable|density/i }))
      .or(page.locator('[class*="density"]'));

    // Chờ trang load xong
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

    if (await densityToggle.first().isVisible()) {
      await densityToggle.first().click();
      // Sau click vẫn không crash
      await expect(
        page.getByRole('heading', { name: /500|server error/i }),
      ).not.toBeVisible();
    }
  });

  test('bộ lọc loại BĐS hoạt động', async ({ page }) => {
    await page.goto('/listings');
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

    // Tìm filter select cụ thể — dùng aria-label chính xác để tránh match combobox ngôn ngữ
    const filterSelect = page.locator('select[aria-label="Loại BĐS"]')
      .or(page.locator('select[aria-label*="loại"]'))
      .or(page.locator('select').filter({ has: page.locator('option[value="APARTMENT"]') }));

    if (await filterSelect.first().isVisible({ timeout: 5_000 }).catch(() => false)) {
      // Chọn APARTMENT — selectOption triggers router.replace
      // Nếu trang navigate (locale redirect), chấp nhận — test chỉ verify không crash 500
      await filterSelect.first().selectOption('APARTMENT').catch(() => {
        // router.replace có thể làm page reference stale — bình thường
      });

      // Đợi page ổn định sau navigation
      await page.waitForLoadState('domcontentloaded', { timeout: 8_000 }).catch(() => {});
      await page.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => {});

      // Sau filter trang không crash — check bằng URL/title thay vì heading
      const url = page.url();
      const title = await page.title().catch(() => '');
      // Trang không được là 500 error page
      expect(title, `Sau filter /listings, page title là: ${title}`).not.toMatch(/500|server error/i);
      expect(url, `URL sau filter: ${url}`).not.toMatch(/500|error/i);
    }
  });
});

// ---------------------------------------------------------------------------
// Route: /listings/[id] — Listing detail trader-style (TEC-3060)
// ---------------------------------------------------------------------------

test.describe('@smoke Listing detail — trader-style', () => {
  let firstListingId: string | null = null;

  test.beforeAll(async ({ browser }) => {
    // Lấy ID listing đầu tiên từ seed data thật
    const ctx = await browser.newContext();
    const page = await ctx.newPage();

    // Intercept API response để lấy ID
    let captured = false;
    page.on('response', async (res) => {
      if (captured) return;
      if (res.url().includes('/api/v1/listings') && !res.url().includes('/api/v1/listings/')) {
        try {
          const body = await res.json();
          const data = body?.data ?? body;
          if (Array.isArray(data) && data.length > 0) {
            firstListingId = data[0].id;
            captured = true;
          }
        } catch {
          // ignore
        }
      }
    });

    await page.goto('/listings');
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});
    await ctx.close();
  });

  test('tải trang listing detail, hiển thị tiêu đề và giá', async ({ page }) => {
    const { consoleErrors, networkErrors } = attachErrorListeners(page);

    const url = firstListingId ? `/listings/${firstListingId}` : '/listings';
    await page.goto(url);
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

    // Nếu có listing ID thật
    if (firstListingId) {
      await expect(
        page.getByRole('heading', { name: /500|server error/i }),
      ).not.toBeVisible();

      // Giá BĐS phải hiện (VND) — định dạng có thể là "8.500.000.000 đ", "8.5 tỷ", "VNĐ", v.v.
      const priceEl = page
        .getByText(/tỷ|triệu|vnđ|vnd|\d[\d.]+\s*đ/i)
        .or(page.locator('[class*="price"]'));
      await expect(priceEl.first()).toBeVisible({ timeout: 15_000 });
    }

    expect(consoleErrors, `Console errors: ${consoleErrors.join(', ')}`).toHaveLength(0);
    expect(
      networkErrors.filter((e) => e.status >= 500),
      `Network 5xx: ${networkErrors.map((e) => `${e.status} ${e.url}`).join(', ')}`,
    ).toHaveLength(0);
  });

  test('404 cho listing ID không tồn tại (không crash 500)', async ({ page }) => {
    const res = await page.goto('/listings/nonexistent-trading-floor-qa');
    const status = res?.status();
    if (status && status >= 500) {
      throw new Error(`Listing detail trả về ${status} cho ID không tồn tại (mong đợi 404)`);
    }
    // Không nên có 500 error heading
    await expect(
      page.getByRole('heading', { name: /500|server error/i }),
    ).not.toBeVisible();
  });
});

// ---------------------------------------------------------------------------
// Route: /agents/[id] — Agent profile trader-style (TEC-3061)
// ---------------------------------------------------------------------------

test.describe('@smoke Agent profile — trader-style', () => {
  test('agent profile với ID từ seed data, hiển thị đúng', async ({ page }) => {
    const { consoleErrors, networkErrors } = attachErrorListeners(page);

    // Dùng agent seed ID (từ pnpm db:seed — agent đầu tiên thường có slug hoặc ID cố định)
    // Thử truy cập trang listing rồi click vào agent link
    await page.goto('/listings');
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

    // Tìm link agent đầu tiên trên trang
    const agentLink = page
      .locator('a[href*="/agents/"]')
      .first();

    if (await agentLink.isVisible({ timeout: 5_000 }).catch(() => false)) {
      const href = await agentLink.getAttribute('href');
      await page.goto(href!);
      await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

      // Quality score, tên agent, hoặc thông tin liên hệ phải hiển thị
      const agentInfo = page
        .locator('[data-testid="agent-quality-score"]')
        .or(page.locator('[class*="quality"]'))
        .or(page.getByText(/điểm chất lượng|quality score/i))
        .or(page.getByRole('heading', { level: 1 }));
      await expect(agentInfo.first()).toBeVisible({ timeout: 10_000 });
    }

    expect(consoleErrors, `Console errors: ${consoleErrors.join(', ')}`).toHaveLength(0);
    expect(
      networkErrors.filter((e) => e.status >= 500),
      `Network 5xx: ${networkErrors.map((e) => `${e.status} ${e.url}`).join(', ')}`,
    ).toHaveLength(0);
  });
});

// ---------------------------------------------------------------------------
// Route: /my-listings — Dashboard CRUD flow (TEC-3086 — route rename)
// ---------------------------------------------------------------------------

test.describe('@smoke Dashboard /my-listings — route rename (TEC-3086)', () => {
  test('redirect đến login khi chưa đăng nhập (không crash 500)', async ({ page }) => {
    const res = await page.goto('/my-listings');
    await page.waitForLoadState('domcontentloaded', { timeout: 10_000 });

    const finalUrl = page.url();
    const isLoginRedirect = finalUrl.includes('/login') || finalUrl.includes('/auth');
    const is403 = res?.status() === 403;

    expect(
      isLoginRedirect || is403,
      `/my-listings phải redirect login hoặc 403. Nhận: ${res?.status()} tại ${finalUrl}`,
    ).toBeTruthy();

    // Không có webpack parallel-pages error hay 500
    await expect(
      page.getByRole('heading', { name: /500|server error|parallel pages/i }),
    ).not.toBeVisible();
  });

  test('/my-listings/new redirect đến login khi chưa đăng nhập', async ({ page }) => {
    await page.goto('/my-listings/new');
    await page.waitForLoadState('domcontentloaded', { timeout: 10_000 });

    const finalUrl = page.url();
    const isLoginRedirect = finalUrl.includes('/login') || finalUrl.includes('/auth');
    expect(isLoginRedirect, `Nhận URL: ${finalUrl}`).toBeTruthy();
  });

  test('public /listings vẫn hoạt động (không bị ảnh hưởng bởi route rename)', async ({ page }) => {
    const { consoleErrors } = attachErrorListeners(page);
    const res = await page.goto('/listings');
    await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => {});

    // Public listings phải trả 200 — không redirect, không 500
    expect(res?.status(), `Public /listings trả ${res?.status()}`).toBeLessThan(400);
    await expect(
      page.getByRole('heading', { name: /500|server error/i }),
    ).not.toBeVisible();

    expect(consoleErrors, `Console errors: ${consoleErrors.join(', ')}`).toHaveLength(0);
  });
});

// ---------------------------------------------------------------------------
// Route: /admin — Admin board moderation/KYC (TEC-3062)
// ---------------------------------------------------------------------------

test.describe('@smoke Admin board — moderation & KYC', () => {
  test('redirect đến login khi chưa đăng nhập (không crash)', async ({ page }) => {
    const res = await page.goto('/admin');
    await page.waitForLoadState('domcontentloaded', { timeout: 10_000 });

    // Admin phải redirect đến login hoặc hiển thị 403 — không được 500
    const finalUrl = page.url();
    const isLoginRedirect = finalUrl.includes('/login') || finalUrl.includes('/auth');
    const is403 = res?.status() === 403;
    const is404 = res?.status() === 404;

    expect(
      isLoginRedirect || is403 || is404,
      `Admin page với unauthenticated user phải redirect hoặc trả 403/404. Nhận: ${res?.status()} tại ${finalUrl}`,
    ).toBeTruthy();

    // Không có 500 error heading
    await expect(
      page.getByRole('heading', { name: /500|server error/i }),
    ).not.toBeVisible();
  });
});
