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

describe('getCustomerWithStats', () => {
  it('returns invoice and project aggregates from live module tables', async () => {
    const db = {
      select: vi
        .fn()
        .mockImplementationOnce(() => ({
          from: vi.fn(() => ({
            where: vi.fn(() => ({
              limit: vi.fn().mockResolvedValue([
                {
                  id: 'customer-1',
                  tenantId: 'tenant-1',
                  name: 'Acme',
                  taxId: null,
                  company: null,
                  email: 'billing@example.com',
                  phone: null,
                  address: null,
                  notes: null,
                  status: 'active',
                  createdAt: new Date('2026-06-01T00:00:00.000Z'),
                  updatedAt: new Date('2026-06-02T00:00:00.000Z'),
                },
              ]),
            })),
          })),
        }))
        .mockImplementationOnce(() => ({
          from: vi.fn(() => ({
            where: vi.fn().mockResolvedValue([
              {
                totalInvoices: 3,
                totalPaid: '120.50',
                outstandingBalance: '30.25',
                openInvoices: 2,
              },
            ]),
          })),
        }))
        .mockImplementationOnce(() => ({
          from: vi.fn(() => ({
            where: vi.fn().mockResolvedValue([
              {
                openProjects: 4,
                activeProjects: 2,
              },
            ]),
          })),
        })),
    }

    const { getCustomerWithStats } = await import('../../src/queries/customers')
    const result = await getCustomerWithStats(db as never, 'tenant-1', 'customer-1')

    expect(result?.stats).toEqual({
      totalInvoices: 3,
      totalPaid: 120.5,
      outstandingBalance: 30.25,
      openProjects: 4,
      activeProjects: 2,
      openInvoices: 2,
    })
  })
})

describe('archiveCustomer', () => {
  it('throws when the customer still has open invoices', async () => {
    const db = {
      select: vi.fn(() => ({
        from: vi.fn(() => ({
          where: vi.fn().mockResolvedValue([{ count: 1 }]),
        })),
      })),
      transaction: vi.fn(),
    }

    const { archiveCustomer, OpenInvoicesError } = await import('../../src/queries/customers')

    await expect(archiveCustomer(db as never, 'tenant-1', 'customer-1')).rejects.toBeInstanceOf(
      OpenInvoicesError,
    )
    expect(db.transaction).not.toHaveBeenCalled()
  })
})

describe('listPortalUsers', () => {
  it('maps unaccepted active portal users to pending status', async () => {
    const db = {
      select: vi.fn(() => ({
        from: vi.fn(() => ({
          where: vi.fn(() => ({
            orderBy: vi.fn().mockResolvedValue([
              {
                id: 'portal-user-1',
                customerId: 'customer-1',
                tenantId: 'tenant-1',
                contactId: 'contact-1',
                userId: 'user-1',
                portalRole: 'customer_viewer',
                status: 'active',
                invitedAt: new Date('2026-06-01T00:00:00.000Z'),
                acceptedAt: null,
                createdAt: new Date('2026-06-01T00:00:00.000Z'),
              },
            ]),
          })),
        })),
      })),
    }

    const { listPortalUsers } = await import('../../src/queries/customers')
    const result = await listPortalUsers(db as never, 'tenant-1', 'customer-1')

    expect(result[0]?.status).toBe('pending')
  })
})

describe('mergeCustomers', () => {
  it('reassigns spec-owned linked records, accepts the suggestion, and archives the losing customer', async () => {
    const execute = vi
      .fn()
      .mockResolvedValueOnce({ rowCount: 2 })
      .mockResolvedValueOnce({ rowCount: 1 })
      .mockResolvedValueOnce({ rowCount: 3 })
      .mockResolvedValueOnce({ rowCount: 4 })
      .mockResolvedValueOnce({ rowCount: 5 })
      .mockResolvedValueOnce({ rowCount: 6 })
      .mockResolvedValueOnce({ rowCount: 0 })

    const updateChain = {
      set: vi.fn(() => ({
        where: vi.fn().mockResolvedValue(undefined),
      })),
    }

    const insertValues = vi.fn().mockResolvedValue(undefined)
    const tx = {
      select: vi
        .fn()
        .mockReturnValueOnce({
          from: vi.fn(() => ({
            where: vi.fn(() => ({
              for: vi.fn(() => [
                { id: 'customer-keep', status: 'active', name: 'Keep Co', email: 'keep@example.com' },
                { id: 'customer-delete', status: 'active', name: 'Delete Co', email: 'delete@example.com' },
              ]),
            })),
          })),
        }),
      execute,
      update: vi.fn(() => updateChain),
      insert: vi.fn(() => ({ values: insertValues })),
    }

    const db = {
      transaction: vi.fn(async (fn: (trx: typeof tx) => Promise<unknown>) => fn(tx)),
    }

    const { mergeCustomers } = await import('../../src/queries/customer-dedup')
    const result = await mergeCustomers(
      db as never,
      'tenant-1',
      { userId: 'user-1', name: 'Owner', email: 'owner@example.com' },
      { keepId: 'customer-keep', deleteId: 'customer-delete' },
    )

    expect(result).toEqual({
      keptCustomerId: 'customer-keep',
      archivedCustomerId: 'customer-delete',
      reassigned: {
        invoices: 2,
        projects: 1,
        contacts: 3,
        tickets: 4,
        portalUsers: 5,
        activities: 6,
      },
    })
    expect(execute).toHaveBeenCalledTimes(7)
    expect(tx.insert).toHaveBeenCalled()
    expect(insertValues).toHaveBeenCalledWith(expect.objectContaining({
      tenantId: 'tenant-1',
      actorId: 'user-1',
      action: 'customer.merged',
      entityType: 'customer',
      entityId: 'customer-keep',
    }))
  })
})
