import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';

vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    Head: ({ title }: { title: string }) => <title>{title}</title>,
    Link: ({ children, ...props }: { children: React.ReactNode; href?: string }) => (
      <a {...props}>{children}</a>
    ),
    usePage: () => ({ props: { auth: { user: { id: 1, name: 'T' } }, flash: {} } }),
  };
});

vi.mock('@/Layouts/DashboardLayout', () => ({
  default: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="dashboard-layout">{children}</div>
  ),
}));

import SpendIndex from './Index';

const basePortfolio = {
  mtd_spend: 12.5,
  projected_eom: 25.0,
  mom_change_pct: 10,
  active_site_count: 2,
  effective_cap: 20,
  cap_unlimited: false,
  cap_utilization_pct: 62.5,
  approaching_cap: false,
  lifetime_batches: 4,
  // R6PROD-012
  includes_scheduled: false,
  scheduled_breach_warning: false,
  // ROI-004
  total_clicks_recovered: null,
  cost_per_click_gained: null,
};

const baseSites = [
  {
    id: 'site-aaa',
    name: 'Blog One',
    mtd_spend: 8.0,
    projected_eom: 16.0,
    cost_per_click_gained: null,
    effective_cap: 20,
    cap_unlimited: false,
    cap_utilization_pct: 40,
  },
  {
    id: 'site-bbb',
    name: 'Blog Two',
    mtd_spend: 4.5,
    projected_eom: 9.0,
    cost_per_click_gained: null,
    effective_cap: 20,
    cap_unlimited: false,
    cap_utilization_pct: 22.5,
  },
];

const baseTrend = [
  { year_month: '2026-03', total_spend: 10 },
  { year_month: '2026-04', total_spend: 18 },
  { year_month: '2026-05', total_spend: 12.5 },
];

describe('Spend/Index', () => {
  it('renders the portfolio month-to-date spend', () => {
    render(<SpendIndex portfolio={basePortfolio} sites={baseSites} trend={baseTrend} />);
    expect(screen.getAllByText('$12.50').length).toBeGreaterThan(0);
  });

  it('renders a row per site', () => {
    render(<SpendIndex portfolio={basePortfolio} sites={baseSites} trend={baseTrend} />);
    expect(screen.getByText('Blog One')).toBeInTheDocument();
    expect(screen.getByText('Blog Two')).toBeInTheDocument();
  });

  it('shows the approaching-cap banner when flagged', () => {
    render(
      <SpendIndex
        portfolio={{ ...basePortfolio, approaching_cap: true, cap_utilization_pct: 85 }}
        sites={baseSites}
        trend={baseTrend}
      />,
    );
    expect(screen.getByText(/of your\s+monthly AI spend cap/i)).toBeInTheDocument();
  });

  it('renders the empty state when there is no spend and no lifetime batches', () => {
    render(
      <SpendIndex
        portfolio={{ ...basePortfolio, mtd_spend: 0, lifetime_batches: 0 }}
        sites={[]}
        trend={baseTrend}
      />,
    );
    expect(screen.getByText('No spend yet')).toBeInTheDocument();
  });

  it('shows Unlimited when the cap is unlimited', () => {
    render(
      <SpendIndex
        portfolio={{ ...basePortfolio, cap_unlimited: true }}
        sites={baseSites}
        trend={baseTrend}
      />,
    );
    expect(screen.getAllByText('Unlimited').length).toBeGreaterThan(0);
  });

  it('hides the per-site table and N-SITES eyebrow for a single-site user (F5UXA-017)', () => {
    render(
      <SpendIndex
        portfolio={{ ...basePortfolio, active_site_count: 1 }}
        sites={[baseSites[0]]}
        trend={baseTrend}
      />,
    );
    // No 'N SITES' text in eyebrow
    expect(screen.queryByText(/\d+ SITES/i)).not.toBeInTheDocument();
    // No per-site table header
    expect(screen.queryByText('By site')).not.toBeInTheDocument();
    // Summary data still present
    expect(screen.getAllByText('$12.50').length).toBeGreaterThan(0);
  });

  it('shows the per-site table for multi-site users', () => {
    render(<SpendIndex portfolio={basePortfolio} sites={baseSites} trend={baseTrend} />);
    expect(screen.getByText('By site')).toBeInTheDocument();
    expect(screen.getByText('Blog One')).toBeInTheDocument();
    expect(screen.getByText('Blog Two')).toBeInTheDocument();
  });

  // R6PROD-012: batch-aware cockpit copy
  it('shows includes-scheduled copy when includes_scheduled is true', () => {
    render(
      <SpendIndex
        portfolio={{ ...basePortfolio, includes_scheduled: true }}
        sites={baseSites}
        trend={baseTrend}
      />,
    );
    expect(screen.getByText('Includes queued & scheduled spend.')).toBeInTheDocument();
    expect(screen.queryByText('Assumes current burn continues.')).not.toBeInTheDocument();
  });

  it('shows linear-burn copy when includes_scheduled is false', () => {
    render(<SpendIndex portfolio={basePortfolio} sites={baseSites} trend={baseTrend} />);
    expect(screen.getByText('Assumes current burn continues.')).toBeInTheDocument();
    expect(screen.queryByText('Includes queued & scheduled spend.')).not.toBeInTheDocument();
  });

  it('shows the breach-warning banner when scheduled_breach_warning is true', () => {
    render(
      <SpendIndex
        portfolio={{ ...basePortfolio, scheduled_breach_warning: true }}
        sites={baseSites}
        trend={baseTrend}
      />,
    );
    expect(
      screen.getByText(/projected to exceed your monthly\s+cap/i),
    ).toBeInTheDocument();
  });

  it('does not show the breach-warning banner when scheduled_breach_warning is false', () => {
    render(<SpendIndex portfolio={basePortfolio} sites={baseSites} trend={baseTrend} />);
    expect(screen.queryByText(/projected to exceed your monthly/i)).not.toBeInTheDocument();
  });

  // ROI-004: recovery card
  it('does not render the clicks-measured card when total_clicks_recovered is null', () => {
    render(<SpendIndex portfolio={basePortfolio} sites={baseSites} trend={baseTrend} />);
    expect(screen.queryByText('Clicks measured')).not.toBeInTheDocument();
  });

  // ROI-VOCAB-202: language-contract — measured before->after vocabulary, never causal "recovered"
  it('renders "Clicks measured" (not "Clicks recovered") when ROI data is present', () => {
    render(
      <SpendIndex
        portfolio={{ ...basePortfolio, total_clicks_recovered: 250, cost_per_click_gained: 0.05 }}
        sites={baseSites}
        trend={baseTrend}
      />,
    );
    // Label must use measured vocabulary
    expect(screen.getAllByText('Clicks measured').length).toBeGreaterThan(0);
    // Causal vocabulary is forbidden
    expect(screen.queryByText('Clicks recovered')).not.toBeInTheDocument();
    // '250' appears in both the header readout and the card value
    expect(screen.getAllByText('250').length).toBeGreaterThan(0);
    // Sub-label uses measured framing, not causal
    expect(screen.getByText('$0.05 per measured click')).toBeInTheDocument();
    expect(screen.queryByText(/per click recovered/i)).not.toBeInTheDocument();
  });

  // UXB-R16-01: empty-state CTA must not link to feature-gated route('portfolio')
  it('empty-state CTA resolves to route(dashboard) and labels match — not portfolio', () => {
    render(
      <SpendIndex
        // active_site_count: 1 — coherent with hasSites=true branch (uses portfolio.active_site_count)
        portfolio={{ ...basePortfolio, mtd_spend: 0, lifetime_batches: 0, active_site_count: 1 }}
        sites={[baseSites[0]]}
        trend={baseTrend}
      />,
    );
    const cta = screen.getByRole('link', { name: /go to your dashboard/i });
    expect(cta).toBeInTheDocument();
    expect(cta).toHaveAttribute('href', '/dashboard');
    // Must NOT point at the feature-gated portfolio route
    expect(cta).not.toHaveAttribute('href', '/portfolio');
  });

  it('empty-state CTA resolves to route(sites.create) and labels match when user has no sites', () => {
    render(
      <SpendIndex
        // active_site_count: 0 — coherent with no-sites branch; signal is portfolio.active_site_count
        portfolio={{ ...basePortfolio, mtd_spend: 0, lifetime_batches: 0, active_site_count: 0 }}
        sites={[]}
        trend={baseTrend}
      />,
    );
    const cta = screen.getByRole('link', { name: /add your first site/i });
    expect(cta).toBeInTheDocument();
    expect(cta).toHaveAttribute('href', '/sites/create');
    expect(cta).not.toHaveAttribute('href', '/portfolio');
  });

  it('empty-state CTA uses portfolio.active_site_count (not sites.length) as the hasSites signal', () => {
    // Cache-skew case: per_site_summary empty but portfolio reports active_site_count=1.
    // Signal must resolve to dashboard (not sites.create) because portfolio is the coherent source.
    render(
      <SpendIndex
        portfolio={{ ...basePortfolio, mtd_spend: 0, lifetime_batches: 0, active_site_count: 1 }}
        sites={[]}
        trend={baseTrend}
      />,
    );
    const cta = screen.getByRole('link', { name: /go to your dashboard/i });
    expect(cta).toBeInTheDocument();
    expect(cta).toHaveAttribute('href', '/dashboard');
  });

  it('renders the cost/click column in the per-site table', () => {
    render(<SpendIndex portfolio={basePortfolio} sites={baseSites} trend={baseTrend} />);
    expect(screen.getByText('Cost / click')).toBeInTheDocument();
  });

  it('shows cost per click for a site when available', () => {
    const sitesWithRoi = [
      { ...baseSites[0], cost_per_click_gained: 0.04 },
      baseSites[1],
    ];
    render(<SpendIndex portfolio={basePortfolio} sites={sitesWithRoi} trend={baseTrend} />);
    expect(screen.getByText('$0.04')).toBeInTheDocument();
  });
});
