import { beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('../../lib/prisma', () => ({
  prisma: {
    menuItem: {
      findUnique: vi.fn(),
      findUniqueOrThrow: vi.fn(),
      findFirst: vi.fn(),
      findMany: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    },
    menuItemRoleVisibility: { deleteMany: vi.fn(), createMany: vi.fn() },
    subscriptionMenuItem: { deleteMany: vi.fn(), createMany: vi.fn() },
    companyMenuItem: { deleteMany: vi.fn(), createMany: vi.fn() },
    company: { findMany: vi.fn(), findUniqueOrThrow: vi.fn() },
    employee: { findUniqueOrThrow: vi.fn() },
    auditLog: { create: vi.fn(), findMany: vi.fn(), count: vi.fn() },
    $transaction: vi.fn(),
  },
}))

import { prisma } from '../../lib/prisma'
import {
  assignMenuItemToCompanies,
  assignMenuItemToPlans,
  createMenuItem,
  listMenuAuditLogs,
  setMenuItemStatus,
} from './menu.service'

const admin = { id: 'admin_1', role: 'ADMIN' }

describe('menu.service mutation boundaries', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.mocked(prisma.menuItem.findFirst).mockResolvedValue(null as never)
    vi.mocked(prisma.menuItem.findUnique).mockResolvedValue(null as never)
    vi.mocked(prisma.company.findMany).mockResolvedValue([] as never)
    vi.mocked(prisma.$transaction).mockImplementation(async (callback: any) => callback(prisma))
  })

  it('rejects external links that are not HTTPS before any write', async () => {
    await expect(createMenuItem({
      label: 'Docs',
      itemType: 'EXTERNAL_LINK',
      routeOrUrl: 'http://docs.example.test',
    }, admin)).rejects.toMatchObject({ statusCode: 400, error: 'validation_error' })

    expect(prisma.menuItem.create).not.toHaveBeenCalled()
    expect(prisma.auditLog.create).not.toHaveBeenCalled()
  })

  it('rejects duplicate routes under the same parent with a conflict error', async () => {
    vi.mocked(prisma.menuItem.findUnique).mockResolvedValue({ id: 'parent_1', itemType: 'PARENT_MENU' } as never)
    vi.mocked(prisma.menuItem.findFirst).mockResolvedValue({ id: 'item_existing' } as never)

    await expect(createMenuItem({
      label: 'Fleet',
      itemType: 'INTERNAL_PAGE',
      routeOrUrl: '/fleet',
      parentId: 'parent_1',
    }, admin)).rejects.toMatchObject({ statusCode: 409, error: 'duplicate_menu_route' })
  })

  it('normalizes create payloads and syncs role, plan and company assignments transactionally', async () => {
    vi.mocked(prisma.company.findMany).mockResolvedValue([{ id: 'company_1' }] as never)
    vi.mocked(prisma.menuItem.create).mockResolvedValue({ id: 'item_1' } as never)
    vi.mocked(prisma.menuItem.findUniqueOrThrow).mockResolvedValue({ id: 'item_1', label: 'Fleet' } as never)

    await createMenuItem({
      systemKey: ' fleet ',
      label: ' Fleet ',
      itemType: 'INTERNAL_PAGE',
      routeOrUrl: ' /fleet ',
      icon: ' Car ',
      displayOrder: 7,
      roles: ['MANAGER', 'MANAGER', 'AGENT'],
      subscriptionPlans: [{ plan: 'GROWTH' }, { plan: 'PRO', displayOrder: 3, isActive: false }],
      companyAssignments: [{ companyId: 'company_1' }],
    }, admin)

    expect(prisma.menuItem.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        systemKey: 'fleet',
        label: 'Fleet',
        itemType: 'INTERNAL_PAGE',
        routeOrUrl: '/fleet',
        icon: 'Car',
        displayOrder: 7,
        openInNewTab: false,
        isRequired: false,
        isActive: true,
        createdBy: 'admin_1',
        updatedBy: 'admin_1',
      }),
    })
    expect(prisma.menuItemRoleVisibility.createMany).toHaveBeenCalledWith({
      data: [
        { menuItemId: 'item_1', role: 'MANAGER' },
        { menuItemId: 'item_1', role: 'AGENT' },
      ],
    })
    expect(prisma.subscriptionMenuItem.createMany).toHaveBeenCalledWith({
      data: [
        { menuItemId: 'item_1', plan: 'GROWTH', displayOrder: 7, isActive: true },
        { menuItemId: 'item_1', plan: 'PRO', displayOrder: 3, isActive: false },
      ],
    })
    expect(prisma.companyMenuItem.createMany).toHaveBeenCalledWith({
      data: [{ menuItemId: 'item_1', companyId: 'company_1', displayOrder: 7, isActive: true }],
    })
    expect(prisma.auditLog.create).toHaveBeenCalledWith({
      data: expect.objectContaining({ action: 'CREATE_MENU_ITEM', resource: 'MenuItem', resourceId: 'item_1' }),
    })
  })

  it('protects required menu items from non-super-admin disable attempts', async () => {
    vi.mocked(prisma.menuItem.findUniqueOrThrow).mockResolvedValue({ id: 'item_core', isRequired: true, isActive: true } as never)

    await expect(setMenuItemStatus('item_core', false, admin)).rejects.toMatchObject({ statusCode: 403, error: 'forbidden' })

    expect(prisma.menuItem.update).not.toHaveBeenCalled()
  })

  it('replaces only requested plan assignments and writes an audit entry', async () => {
    vi.mocked(prisma.menuItem.findUniqueOrThrow).mockResolvedValue({ id: 'item_1' } as never)

    await assignMenuItemToPlans('item_1', [{ plan: 'STARTER' }, { plan: 'PRO', displayOrder: 4, isActive: false }], admin)

    expect(prisma.subscriptionMenuItem.deleteMany).toHaveBeenCalledWith({
      where: { menuItemId: 'item_1', plan: { in: ['STARTER', 'PRO'] } },
    })
    expect(prisma.subscriptionMenuItem.createMany).toHaveBeenCalledWith({
      data: [
        { menuItemId: 'item_1', plan: 'STARTER', displayOrder: 0, isActive: true },
        { menuItemId: 'item_1', plan: 'PRO', displayOrder: 4, isActive: false },
      ],
    })
    expect(prisma.auditLog.create).toHaveBeenCalledWith({
      data: expect.objectContaining({ action: 'ASSIGN_MENU_ITEM_TO_SUBSCRIPTIONS', resource: 'MenuSubscriptionAssignment' }),
    })
  })

  it('rejects company assignments when any company is cancelled or missing', async () => {
    vi.mocked(prisma.menuItem.findUniqueOrThrow).mockResolvedValue({ id: 'item_1' } as never)
    vi.mocked(prisma.company.findMany).mockResolvedValue([{ id: 'company_1' }] as never)

    await expect(assignMenuItemToCompanies('item_1', [
      { companyId: 'company_1' },
      { companyId: 'company_cancelled' },
    ], admin)).rejects.toMatchObject({ statusCode: 400, error: 'validation_error' })

    expect(prisma.companyMenuItem.deleteMany).not.toHaveBeenCalled()
  })

  it('paginates menu audit logs across menu-related resources only', async () => {
    vi.mocked(prisma.auditLog.findMany).mockResolvedValue([{ id: 'log_1' }] as never)
    vi.mocked(prisma.auditLog.count).mockResolvedValue(41 as never)

    const result = await listMenuAuditLogs({ page: 3, pageSize: 20 })

    expect(prisma.auditLog.findMany).toHaveBeenCalledWith({
      where: { resource: { in: ['MenuItem', 'MenuSubscriptionAssignment', 'MenuCompanyAssignment', 'MenuRoleVisibility'] } },
      include: { adminUser: { select: { firstName: true, lastName: true, email: true } } },
      skip: 40,
      take: 20,
      orderBy: { createdAt: 'desc' },
    })
    expect(result).toEqual({ data: [{ id: 'log_1' }], total: 41, page: 3, pageSize: 20, totalPages: 3 })
  })
})
