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

vi.mock('../../services/licenseValidationService', () => ({
  validateAndFlagLicense: vi.fn(),
}))
vi.mock('../../lib/storage', () => ({
  uploadImage: vi.fn(),
  deleteImage: vi.fn(),
  resolveStoredFilePath: vi.fn(),
}))
vi.mock('./customer.presenter', () => ({
  presentCustomer: vi.fn((customer: any) => ({ presented: true, ...customer })),
  presentCustomerList: vi.fn((customers: any[], meta: any) => ({ data: customers, meta })),
}))
vi.mock('./customer.repo', () => ({
  findMany: vi.fn(),
  findById: vi.fn(),
  create: vi.fn(),
  updateMany: vi.fn(),
  findUniqueOrThrow: vi.fn(),
  findByIdSimple: vi.fn(),
  updateById: vi.fn(),
}))

import { NotFoundError } from '../../http/errors'
import { validateAndFlagLicense } from '../../services/licenseValidationService'
import { uploadImage, deleteImage, resolveStoredFilePath } from '../../lib/storage'
import * as repo from './customer.repo'
import * as service from './customer.service'

describe('customer.service boundary behavior', () => {
  beforeEach(() => vi.clearAllMocks())

  it('trims and caps search while applying flagged and pagination filters', async () => {
    vi.mocked(repo.findMany).mockResolvedValue([[{ id: 'customer_1' }], 41] as never)

    const result = await service.listCustomers('company_1', {
      page: 3,
      pageSize: 10,
      q: `  ${'x'.repeat(150)}  `,
      flagged: 'false',
    })

    const expectedQ = 'x'.repeat(100)
    expect(repo.findMany).toHaveBeenCalledWith({
      companyId: 'company_1',
      flagged: false,
      OR: [
        { firstName: { contains: expectedQ, mode: 'insensitive' } },
        { lastName: { contains: expectedQ, mode: 'insensitive' } },
        { email: { contains: expectedQ, mode: 'insensitive' } },
      ],
    }, 20, 10)
    expect(result.meta).toEqual({ total: 41, page: 3, pageSize: 10, totalPages: 5 })
  })

  it('normalizes customer date fields and swallows async license validation failures after create', async () => {
    vi.mocked(repo.create).mockResolvedValue({ id: 'customer_1', email: 'renter@example.test' } as never)
    vi.mocked(validateAndFlagLicense).mockRejectedValue(new Error('provider down') as never)

    await service.createCustomer({
      firstName: 'Nora',
      dateOfBirth: '1992-02-03',
      licenseExpiry: '2027-01-01',
      licenseIssuedAt: '2022-01-01',
    }, 'company_1')

    expect(repo.create).toHaveBeenCalledWith(expect.objectContaining({
      companyId: 'company_1',
      dateOfBirth: new Date('1992-02-03'),
      licenseExpiry: new Date('2027-01-01'),
      licenseIssuedAt: new Date('2022-01-01'),
    }))
    expect(validateAndFlagLicense).toHaveBeenCalledWith('customer_1', 'company_1')
  })

  it('throws not found when tenant-scoped update touches no rows', async () => {
    vi.mocked(repo.updateMany).mockResolvedValue({ count: 0 } as never)

    await expect(service.updateCustomer('customer_1', 'company_1', { firstName: 'Nope' })).rejects.toBeInstanceOf(NotFoundError)
    expect(repo.findUniqueOrThrow).not.toHaveBeenCalled()
  })

  it('uploads the new license image and best-effort deletes the previous one', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue({ id: 'customer_1', licenseImageUrl: '/storage/old.png' } as never)
    vi.mocked(uploadImage).mockResolvedValue('/storage/new.png' as never)
    vi.mocked(deleteImage).mockRejectedValue(new Error('already gone') as never)
    vi.mocked(repo.updateById).mockResolvedValue({ id: 'customer_1', licenseImageUrl: '/storage/new.png' } as never)

    const file = { buffer: Buffer.from('fake') } as Express.Multer.File
    const result = await service.uploadLicenseImage('customer_1', 'company_1', file)

    expect(uploadImage).toHaveBeenCalledWith(file.buffer, 'companies/company_1/customers/customer_1')
    expect(deleteImage).toHaveBeenCalledWith('/storage/old.png')
    expect(repo.updateById).toHaveBeenCalledWith('customer_1', { licenseImageUrl: '/storage/new.png' })
    expect(result.licenseImageUrl).toBe('/storage/new.png')
  })

  it('rejects license-image access when the stored URL cannot resolve to a file path', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue({ id: 'customer_1', licenseImageUrl: '/storage/missing.png' } as never)
    vi.mocked(resolveStoredFilePath).mockReturnValue(null)

    await expect(service.getLicenseImageFile('customer_1', 'company_1')).rejects.toBeInstanceOf(NotFoundError)
  })

  it('records approver metadata when manually approving a license', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue({ id: 'customer_1' } as never)
    vi.mocked(repo.updateById).mockResolvedValue({ id: 'customer_1', licenseValidationStatus: 'APPROVED' } as never)

    await service.approveLicense('customer_1', 'company_1', 'APPROVE', undefined, 'Mina Manager')

    expect(repo.updateById).toHaveBeenCalledWith('customer_1', expect.objectContaining({
      licenseValidationStatus: 'APPROVED',
      licenseApprovedBy: 'Mina Manager',
      licenseApprovalNote: null,
      licenseApprovedAt: expect.any(Date),
    }))
  })
})
