// @vitest-environment happy-dom
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import { DashboardShell } from '../DashboardShell.js';
import { Sidebar } from '../Sidebar.js';
import { TopBar } from '../TopBar.js';
import { ProfilePage } from '../ProfilePage.js';
import { BillingPage } from '../BillingPage.js';
import { SettingsPage } from '../SettingsPage.js';
import type { NavItem, NavSection, ShellUser } from '../types.js';

const NAV: NavItem[] = [
  { href: '/dashboard', label: 'Dashboard', icon: '◈' },
  { href: '/tasks', label: 'Tasks', icon: '✓' },
  { href: '/settings', label: 'Settings', icon: '⚙' },
];

const USER: ShellUser = {
  id: 'u1',
  name: 'Alice Smith',
  email: 'alice@example.com',
  role: 'admin',
};

// ── DashboardShell ───────────────────────────────────────────────────────────

describe('DashboardShell', () => {
  beforeEach(() => cleanup());

  it('renders sidebar, topbar, and content', () => {
    render(
      <DashboardShell productName="TestApp" nav={NAV} pathname="/dashboard">
        <div>Page content</div>
      </DashboardShell>
    );
    expect(screen.getByTestId('bl-dashboard-shell')).toBeDefined();
    expect(screen.getByTestId('bl-shell-sidebar')).toBeDefined();
    expect(screen.getByTestId('bl-shell-topbar')).toBeDefined();
    expect(screen.getByTestId('bl-shell-main')).toBeDefined();
    expect(screen.getByText('Page content')).toBeDefined();
  });

  it('passes product name to sidebar', () => {
    render(
      <DashboardShell productName="MyProduct" nav={NAV} pathname="/">
        <div />
      </DashboardShell>
    );
    expect(screen.getByTestId('bl-shell-product-name').textContent).toBe('MyProduct');
  });

  it('passes user to topbar', () => {
    render(
      <DashboardShell productName="TestApp" nav={NAV} pathname="/" user={USER}>
        <div />
      </DashboardShell>
    );
    expect(screen.getByText('Alice Smith')).toBeDefined();
  });

  it('calls onSignOut when sign out clicked', () => {
    const onSignOut = vi.fn();
    render(
      <DashboardShell
        productName="TestApp"
        nav={NAV}
        pathname="/"
        user={USER}
        onSignOut={onSignOut}
      >
        <div />
      </DashboardShell>
    );
    // Open user menu
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    fireEvent.click(screen.getByTestId('bl-shell-menu-signout'));
    expect(onSignOut).toHaveBeenCalledOnce();
  });

  it('toggles sidebar collapse', () => {
    render(
      <DashboardShell productName="TestApp" nav={NAV} pathname="/dashboard">
        <div />
      </DashboardShell>
    );
    const toggle = screen.getByTestId('bl-shell-collapse-toggle');
    // Initially expanded — product name shows full text
    expect(screen.getByTestId('bl-shell-product-name').textContent).toBe('TestApp');
    fireEvent.click(toggle);
    // Collapsed — shows first letter
    expect(screen.getByTestId('bl-shell-product-name').textContent).toBe('T');
  });
});

// ── Sidebar ──────────────────────────────────────────────────────────────────

describe('Sidebar', () => {
  beforeEach(() => cleanup());

  it('renders nav items', () => {
    render(<Sidebar productName="TestApp" nav={NAV} pathname="/dashboard" />);
    expect(screen.getByText('Dashboard')).toBeDefined();
    expect(screen.getByText('Tasks')).toBeDefined();
  });

  it('highlights active nav item', () => {
    render(<Sidebar productName="TestApp" nav={NAV} pathname="/dashboard" />);
    const dashLink = screen.getByTestId('bl-nav-dashboard');
    expect(dashLink.style.fontWeight).toBe('600');
  });

  it('calls onNavigate when item clicked', () => {
    const onNavigate = vi.fn();
    render(<Sidebar productName="TestApp" nav={NAV} pathname="/" onNavigate={onNavigate} />);
    fireEvent.click(screen.getByTestId('bl-nav-tasks'));
    expect(onNavigate).toHaveBeenCalledWith('/tasks');
  });

  it('supports NavSection format', () => {
    const sections: NavSection[] = [
      { title: 'Main', items: [{ href: '/home', label: 'Home' }] },
      { title: 'Admin', items: [{ href: '/admin', label: 'Admin Panel' }] },
    ];
    render(<Sidebar productName="TestApp" nav={sections} pathname="/" />);
    expect(screen.getByText('Main')).toBeDefined();
    expect(screen.getByText('Admin')).toBeDefined();
    expect(screen.getByText('Home')).toBeDefined();
    expect(screen.getByText('Admin Panel')).toBeDefined();
  });

  it('hides hidden nav items', () => {
    const items: NavItem[] = [
      { href: '/visible', label: 'Visible' },
      { href: '/hidden', label: 'Hidden', hidden: true },
    ];
    render(<Sidebar productName="TestApp" nav={items} pathname="/" />);
    expect(screen.getByText('Visible')).toBeDefined();
    expect(screen.queryByText('Hidden')).toBeNull();
  });

  it('shows badge on nav item', () => {
    const items: NavItem[] = [{ href: '/inbox', label: 'Inbox', badge: 5 }];
    render(<Sidebar productName="TestApp" nav={items} pathname="/" />);
    expect(screen.getByText('5')).toBeDefined();
  });

  it('renders version in footer', () => {
    render(<Sidebar productName="TestApp" nav={NAV} pathname="/" version="1.2.3" />);
    expect(screen.getByTestId('bl-shell-sidebar-footer').textContent).toContain('v1.2.3');
  });

  it('renders custom footer', () => {
    render(<Sidebar productName="TestApp" nav={NAV} pathname="/" footer={<span>Custom</span>} />);
    expect(screen.getByText('Custom')).toBeDefined();
  });

  it('renders logo instead of product name', () => {
    render(
      <Sidebar
        productName="TestApp"
        nav={NAV}
        pathname="/"
        logo={<span data-testid="logo">Logo</span>}
      />
    );
    expect(screen.getByTestId('logo')).toBeDefined();
  });

  it('auto-adds settings link when not present', () => {
    const items: NavItem[] = [{ href: '/dashboard', label: 'Dashboard' }];
    render(
      <Sidebar productName="TestApp" nav={items} pathname="/" features={{ settings: true }} />
    );
    expect(screen.getByTestId('bl-nav-settings')).toBeDefined();
  });

  it('does not duplicate settings link when already present', () => {
    render(<Sidebar productName="TestApp" nav={NAV} pathname="/" />);
    const settingsLinks = screen.getAllByText('Settings');
    expect(settingsLinks.length).toBe(1);
  });
});

// ── TopBar ───────────────────────────────────────────────────────────────────

describe('TopBar', () => {
  beforeEach(() => cleanup());

  it('renders user name', () => {
    render(<TopBar user={USER} />);
    expect(screen.getByText('Alice Smith')).toBeDefined();
  });

  it('renders initials avatar when no avatarUrl', () => {
    render(<TopBar user={USER} />);
    expect(screen.getByTestId('bl-shell-user-avatar').textContent).toBe('AS');
  });

  it('opens and closes user menu', () => {
    render(<TopBar user={USER} />);
    expect(screen.queryByTestId('bl-shell-user-menu')).toBeNull();
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    expect(screen.getByTestId('bl-shell-user-menu')).toBeDefined();
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    expect(screen.queryByTestId('bl-shell-user-menu')).toBeNull();
  });

  it('shows profile link in menu by default', () => {
    render(<TopBar user={USER} />);
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    expect(screen.getByTestId('bl-shell-menu-profile')).toBeDefined();
  });

  it('shows billing link when feature enabled', () => {
    render(<TopBar user={USER} features={{ billing: true }} />);
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    expect(screen.getByTestId('bl-shell-menu-billing')).toBeDefined();
  });

  it('hides billing link when feature disabled', () => {
    render(<TopBar user={USER} features={{ billing: false }} />);
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    expect(screen.queryByTestId('bl-shell-menu-billing')).toBeNull();
  });

  it('shows notifications bell when enabled', () => {
    render(<TopBar user={USER} features={{ notifications: true }} />);
    expect(screen.getByTestId('bl-shell-notifications')).toBeDefined();
  });

  it('calls onSignOut', () => {
    const onSignOut = vi.fn();
    render(<TopBar user={USER} onSignOut={onSignOut} />);
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    fireEvent.click(screen.getByTestId('bl-shell-menu-signout'));
    expect(onSignOut).toHaveBeenCalledOnce();
  });

  it('calls onNavigate for menu items', () => {
    const onNavigate = vi.fn();
    render(<TopBar user={USER} onNavigate={onNavigate} />);
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    fireEvent.click(screen.getByTestId('bl-shell-menu-profile'));
    expect(onNavigate).toHaveBeenCalledWith('/profile');
  });

  it('renders custom actions', () => {
    render(<TopBar user={USER} actions={<button data-testid="custom-action">Action</button>} />);
    expect(screen.getByTestId('custom-action')).toBeDefined();
  });

  it('shows hamburger when onToggleSidebar provided', () => {
    const toggle = vi.fn();
    render(<TopBar user={USER} onToggleSidebar={toggle} />);
    fireEvent.click(screen.getByTestId('bl-shell-hamburger'));
    expect(toggle).toHaveBeenCalledOnce();
  });

  // ── routePrefix (added in 0.2.0) ──────────────────────────────────────────
  it('routePrefix prefixes profile/billing/settings hrefs', () => {
    const onNavigate = vi.fn();
    render(
      <TopBar
        user={USER}
        onNavigate={onNavigate}
        features={{ profile: true, billing: true, settings: true }}
        routePrefix="/app"
      />
    );
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));

    const profile = screen.getByTestId('bl-shell-menu-profile') as HTMLAnchorElement;
    const billing = screen.getByTestId('bl-shell-menu-billing') as HTMLAnchorElement;
    const settings = screen.getByTestId('bl-shell-menu-settings') as HTMLAnchorElement;
    expect(profile.getAttribute('href')).toBe('/app/profile');
    expect(billing.getAttribute('href')).toBe('/app/billing');
    expect(settings.getAttribute('href')).toBe('/app/settings');

    fireEvent.click(profile);
    expect(onNavigate).toHaveBeenCalledWith('/app/profile');
  });

  it('routePrefix defaults to empty string (back-compat with pre-0.2.0)', () => {
    const onNavigate = vi.fn();
    render(<TopBar user={USER} onNavigate={onNavigate} />);
    fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
    fireEvent.click(screen.getByTestId('bl-shell-menu-profile'));
    expect(onNavigate).toHaveBeenCalledWith('/profile');
  });
});

// ── ProfilePage ──────────────────────────────────────────────────────────────

describe('ProfilePage', () => {
  beforeEach(() => cleanup());

  it('renders user info', () => {
    render(<ProfilePage user={USER} />);
    expect(screen.getByTestId('bl-shell-profile-page')).toBeDefined();
    expect(screen.getByTestId('bl-profile-avatar')).toBeDefined();
  });

  it('pre-fills form fields', () => {
    render(<ProfilePage user={USER} />);
    const nameInput = screen.getByTestId('bl-profile-name') as unknown as { value: string };
    const emailInput = screen.getByTestId('bl-profile-email') as unknown as { value: string };
    expect(nameInput.value).toBe('Alice Smith');
    expect(emailInput.value).toBe('alice@example.com');
  });

  it('calls onUpdateProfile with form data', () => {
    const onUpdate = vi.fn();
    render(<ProfilePage user={USER} onUpdateProfile={onUpdate} />);
    fireEvent.change(screen.getByTestId('bl-profile-name'), { target: { value: 'Bob Jones' } });
    fireEvent.submit(screen.getByTestId('bl-profile-submit').closest('form')!);
    expect(onUpdate).toHaveBeenCalledWith({ name: 'Bob Jones', email: 'alice@example.com' });
  });

  it('shows loading state', () => {
    render(<ProfilePage user={USER} onUpdateProfile={vi.fn()} isLoading />);
    expect(screen.getByText('Saving...')).toBeDefined();
  });

  it('shows error and success messages', () => {
    const { rerender } = render(<ProfilePage user={USER} error="Failed" />);
    expect(screen.getByTestId('bl-profile-error')).toBeDefined();

    rerender(<ProfilePage user={USER} success="Saved" />);
    expect(screen.getByTestId('bl-profile-success')).toBeDefined();
  });

  it('shows role when present', () => {
    render(<ProfilePage user={USER} />);
    expect(screen.getByText('Role: admin')).toBeDefined();
  });
});

// ── BillingPage ──────────────────────────────────────────────────────────────

describe('BillingPage', () => {
  beforeEach(() => cleanup());

  it('renders current plan', () => {
    render(<BillingPage currentPlan="Pro" status="active" />);
    expect(screen.getByTestId('bl-shell-billing-page')).toBeDefined();
    expect(screen.getByText('Pro')).toBeDefined();
  });

  it('shows status badge', () => {
    render(<BillingPage currentPlan="Free" status="trialing" />);
    expect(screen.getByTestId('bl-billing-status').textContent).toBe('trialing');
  });

  it('shows trial end date', () => {
    render(<BillingPage trialEndsAt="2026-04-01" />);
    expect(screen.getByTestId('bl-billing-trial').textContent).toContain('2026-04-01');
  });

  it('calls onManageBilling', () => {
    const onManage = vi.fn();
    render(<BillingPage onManageBilling={onManage} />);
    fireEvent.click(screen.getByTestId('bl-billing-manage'));
    expect(onManage).toHaveBeenCalledOnce();
  });

  it('renders plan comparison grid', () => {
    const plans = [
      { name: 'Free', price: '$0/mo', features: ['Basic features'], current: true },
      { name: 'Pro', price: '$9/mo', features: ['All features', 'Priority support'] },
    ];
    render(<BillingPage plans={plans} />);
    expect(screen.getByTestId('bl-billing-plans')).toBeDefined();
    expect(screen.getByTestId('bl-billing-plan-free')).toBeDefined();
    expect(screen.getByTestId('bl-billing-plan-pro')).toBeDefined();
    expect(screen.getByText('$9/mo')).toBeDefined();
  });

  it('defaults to Free plan when not specified', () => {
    render(<BillingPage />);
    expect(screen.getByText('Free')).toBeDefined();
  });
});

// ── SettingsPage ─────────────────────────────────────────────────────────────

describe('SettingsPage', () => {
  beforeEach(() => cleanup());

  it('renders empty state when no sections', () => {
    render(<SettingsPage productName="TestApp" />);
    expect(screen.getByTestId('bl-shell-settings-page')).toBeDefined();
    expect(screen.getByTestId('bl-settings-empty')).toBeDefined();
    expect(screen.getByText('No settings configured for TestApp.')).toBeDefined();
  });

  it('renders sections', () => {
    const sections = [
      { title: 'Notifications', description: 'Manage alerts', content: <div>Toggle</div> },
      { title: 'Theme', content: <div>Dark mode</div> },
    ];
    render(<SettingsPage productName="TestApp" sections={sections} />);
    expect(screen.getByTestId('bl-settings-section-0')).toBeDefined();
    expect(screen.getByTestId('bl-settings-section-1')).toBeDefined();
    expect(screen.getByText('Notifications')).toBeDefined();
    expect(screen.getByText('Manage alerts')).toBeDefined();
    expect(screen.getByText('Toggle')).toBeDefined();
    expect(screen.getByText('Dark mode')).toBeDefined();
  });
});
