import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import TeamPage from '@/app/dashboard/team/page'
import StoreSettingsPage from '@/app/dashboard/settings/page'
import InvitePage from '@/app/invite/[token]/page'
import { Sidebar } from '@/components/layout/Sidebar'

const meResult = (role: 'admin' | 'stock_keeper' | null, has_stock_keeper?: boolean) => ({
  success: true as const,
  data: {
    user_id: 'u-1',
    email: 'demo@imbaraga.rw',
    full_name: 'Admin A',
    status: 'active',
    business_id: 'b-1',
    business_name: 'Kigali Smart Supplies',
    business_slug: 'kigali-smart-supplies',
    role,
    has_stock_keeper,
    restock_mode: 'auto' as const,
  },
})

vi.mock('@/components/ui/Toast', () => ({
  useToast: () => ({ toast: vi.fn() }),
}))

vi.mock('@/components/providers/BusinessProvider', () => ({
  useBusiness: () => ({
    business: { businessId: '11111111-1111-1111-1111-111111111111' },
    refresh: vi.fn(),
  }),
}))

vi.mock('next/navigation', () => ({
  usePathname: () => '/dashboard',
  useParams: () => ({ token: 'testtoken1234' }),
  useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
}))

vi.mock('@supabase/ssr', () => ({
  createBrowserClient: vi.fn(() => ({})),
}))

function mockAgent(overrides: Record<string, unknown> = {}) {
  const mod = {
    getMe: vi.fn().mockResolvedValue(meResult('admin')),
    getTeam: vi.fn().mockResolvedValue({
      success: true,
      data: {
        members: [
          { id: 'm-admin', full_name: 'Admin A', email: 'demo@imbaraga.rw', role: 'admin', status: 'active', created_at: '' },
          { id: 'm-keeper', full_name: 'Jean', email: 'keeper@imbaraga.rw', role: 'stock_keeper', status: 'active', created_at: '' },
        ],
        invites: [],
      },
    }),
    createTeamInvite: vi.fn(),
    revokeTeamInvite: vi.fn(),
    setMemberStatus: vi.fn(),
    updateBusinessSettings: vi.fn(),
    acceptInvite: vi.fn(),
    ...overrides,
  }
  vi.doMock('@/lib/api/agent', () => mod)
  return mod
}

vi.mock('@/lib/api/agent', () => ({
  getMe: vi.fn(),
  getTeam: vi.fn(),
  createTeamInvite: vi.fn(),
  revokeTeamInvite: vi.fn(),
  setMemberStatus: vi.fn(),
  updateBusinessSettings: vi.fn(),
  acceptInvite: vi.fn(),
}))

import {
  getMe,
  getTeam,
  createTeamInvite,
  setMemberStatus,
  updateBusinessSettings,
} from '@/lib/api/agent'

beforeEach(() => {
  vi.clearAllMocks()
  vi.stubGlobal('confirm', vi.fn(() => true))
})

describe('TeamPage', () => {
  it('lists members with roles and status', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('admin') as never)
    vi.mocked(getTeam).mockResolvedValue({
      success: true,
      data: {
        members: [
          { id: 'm1', full_name: 'Jean', email: 'j@x.rw', role: 'stock_keeper', status: 'active', created_at: '' },
        ],
        invites: [],
      },
    } as never)
    render(<TeamPage />)
    expect(await screen.findByText('Jean')).toBeInTheDocument()
    expect(screen.getByText(/stock keeper/i)).toBeInTheDocument()
    expect(screen.getByText(/active/i)).toBeInTheDocument()
  })

  it('disable asks for confirmation then calls API', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('admin') as never)
    vi.mocked(getTeam).mockResolvedValue({
      success: true,
      data: {
        members: [
          { id: 'm1', full_name: 'Jean', email: null, role: 'stock_keeper', status: 'active', created_at: '' },
        ],
        invites: [],
      },
    } as never)
    vi.mocked(setMemberStatus).mockResolvedValue({
      success: true, data: { member_id: 'm1', status: 'disabled' },
    } as never)
    render(<TeamPage />)
    const btn = await screen.findByRole('button', { name: /disable/i })
    fireEvent.click(btn)
    await waitFor(() =>
      expect(setMemberStatus).toHaveBeenCalledWith('m1', 'disabled'))
    expect(confirm).toHaveBeenCalled()
  })

  it('invite employee generates a shareable link', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('admin') as never)
    vi.mocked(getTeam).mockResolvedValue({
      success: true, data: { members: [], invites: [] },
    } as never)
    vi.mocked(createTeamInvite).mockResolvedValue({
      success: true,
      data: { invite_id: 'i1', invite_url: 'http://x/invite/tok1', expires_at: '' },
    } as never)
    render(<TeamPage />)
    fireEvent.click(await screen.findByRole('button', { name: /invite employee/i }))
    expect(await screen.findByText(/http:\/\/x\/invite\/tok1/)).toBeInTheDocument()
    expect(createTeamInvite).toHaveBeenCalledWith('stock_keeper')
  })

  it('non-admins see a permission message', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('stock_keeper') as never)
    render(<TeamPage />)
    expect(await screen.findByText(/only the business admin/i)).toBeInTheDocument()
  })
})

describe('StoreSettingsPage', () => {
  function stubFetchConfig() {
    vi.stubGlobal('fetch', vi.fn((url: string) => {
      if (String(url).includes('/shop/kigali-smart-supplies/config')) {
        return Promise.resolve({
          ok: true,
          json: () => Promise.resolve({
            success: true,
            data: {
              currency: 'USD', delivery_fee: 1500,
              free_delivery_threshold: null, tax_rate_pct: 18,
              branding: {
                name: 'Kigali Smart Supplies', slug: 'kigali-smart-supplies',
                description: 'Electronics', logo_url: null,
                hero_title: 'Quality Tech', hero_subtitle: '',
                brand_color: '#2563eb', address: '', whatsapp: '',
                website_url: '', show_about: true, show_contact: true,
              },
            },
          }),
        })
      }
      return Promise.resolve({ ok: true, json: () => Promise.resolve({}) })
    }))
  }

  it('admin sees the store URL card and prefilled fee', async () => {
    vi.mocked(getMe).mockImplementation(() =>
      Promise.resolve(meResult('admin')) as never)
    stubFetchConfig()
    render(<StoreSettingsPage />)
    expect(
      await screen.findByRole('heading', { name: /your store/i })
    ).toBeInTheDocument()
    await waitFor(() => {
      const input = screen.getByDisplayValue('1500') as HTMLInputElement
      expect(input).toBeDefined()
    })
  })

  it('keepers are blocked from settings', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('stock_keeper') as never)
    render(<StoreSettingsPage />)
    expect(await screen.findByText(/only the business admin/i)).toBeInTheDocument()
  })

  it('save sends branding + money overrides', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('admin') as never)
    vi.mocked(updateBusinessSettings).mockResolvedValue({
      success: true, data: { slug: 'kigali-smart-supplies', updated: [] },
    } as never)
    stubFetchConfig()
    render(<StoreSettingsPage />)
    const saveBtn = await screen.findByRole('button', { name: /save changes/i })
    fireEvent.click(saveBtn)
    await waitFor(() => expect(updateBusinessSettings).toHaveBeenCalledTimes(1))
    const payload = vi.mocked(updateBusinessSettings).mock.calls[0][0]
    expect(payload.brand_color).toBe('#2563eb')
    expect(payload.delivery_fee).toBe(1500)
  })

  it('restock mode prefills from the account and saves the toggle', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('admin') as never)
    vi.mocked(updateBusinessSettings).mockResolvedValue({
      success: true, data: { slug: 'kigali-smart-supplies', updated: [] },
    } as never)
    stubFetchConfig()
    render(<StoreSettingsPage />)

    const autoRadio = (await screen.findByLabelText(/auto — start restock on low stock/i)) as HTMLInputElement
    expect(autoRadio.checked).toBe(true)

    fireEvent.click(screen.getByLabelText(/manual — restock when i ask/i))
    fireEvent.click(await screen.findByRole('button', { name: /save changes/i }))
    await waitFor(() => expect(updateBusinessSettings).toHaveBeenCalledTimes(1))
    const payload = vi.mocked(updateBusinessSettings).mock.calls[0][0]
    expect(payload.restock_mode).toBe('manual')
  })
})

describe('InvitePage', () => {
  it('rejects mismatched passwords client-side', async () => {
    render(<InvitePage />)
    fireEvent.change(await screen.findByLabelText(/full name/i), { target: { value: 'Jean' } })
    fireEvent.change(screen.getByLabelText(/work email/i), { target: { value: 'j@x.rw' } })
    fireEvent.change(screen.getByLabelText(/^password/i), { target: { value: 'longenough1' } })
    fireEvent.change(screen.getByLabelText(/confirm password/i), { target: { value: 'different1' } })
    fireEvent.click(screen.getByRole('button', { name: /create my account/i }))
    expect(await screen.findByText(/passwords do not match/i)).toBeInTheDocument()
  })
})

describe('Sidebar role gating', () => {
  it('shows admin section only for admins', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('admin') as never)
    render(<Sidebar />)
    expect(await screen.findByText('Team')).toBeInTheDocument()
    expect(screen.getByText('Store Settings')).toBeInTheDocument()
  })

  it('hides admin links from stock keepers', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('stock_keeper') as never)
    render(<Sidebar />)
    await waitFor(() =>
      expect(screen.queryByText('Team')).not.toBeInTheDocument())
    expect(screen.queryByText('Store Settings')).not.toBeInTheDocument()
  })

  it('hides Stock Ops from admins when an active keeper exists', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('admin', true) as never)
    render(<Sidebar />)
    await waitFor(() =>
      expect(screen.queryByText('Stock Operations')).not.toBeInTheDocument())
    expect(screen.queryByText('Stock Ops')).not.toBeInTheDocument()
  })

  it('shows Stock Ops to admins when no active keeper exists', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('admin', false) as never)
    render(<Sidebar />)
    expect(await screen.findByText('Stock Operations')).toBeInTheDocument()
    expect(screen.getByText('Stock Ops')).toBeInTheDocument()
  })

  it('keeps Stock Ops visible for stock keepers', async () => {
    vi.mocked(getMe).mockResolvedValue(meResult('stock_keeper') as never)
    render(<Sidebar />)
    expect(await screen.findByText('Stock Operations')).toBeInTheDocument()
    expect(screen.getByText('Stock Ops')).toBeInTheDocument()
  })
})
