import { describe, expect, it, vi } from 'vitest'
import { getUtcMonthStartIso } from '../../src/queries/projects'

describe('getUtcMonthStartIso', () => {
  it('returns a Postgres-safe ISO timestamp for the current UTC month', () => {
    expect(getUtcMonthStartIso(new Date('2026-08-07T12:22:11.000Z'))).toBe(
      '2026-08-01T00:00:00.000Z',
    )
  })
})

describe('getProjectWithStats', () => {
  it('returns live task, time, and invoice aggregates', async () => {
    const db = {
      select: vi
        .fn()
        .mockImplementationOnce(() => ({
          from: vi.fn(() => ({
            leftJoin: vi.fn(() => ({
              where: vi.fn(() => ({
                limit: vi.fn().mockResolvedValue([{
                  id: 'project-1',
                  tenantId: 'tenant-1',
                  customerId: 'customer-1',
                  customerName: 'Acme Corp',
                  name: 'Marathon',
                  description: null,
                  status: 'active',
                  billingType: 'hourly',
                  billingConfig: { rate_per_hour: 150, overtime_enabled: false, overtime_threshold_hours: 8, overtime_multiplier: 1.5 },
                  currency: 'ILS',
                  startDate: '2026-06-01',
                  endDate: null,
                  createdBy: 'user-1',
                  createdAt: new Date('2026-06-01T00:00:00.000Z'),
                  updatedAt: new Date('2026-06-02T00:00:00.000Z'),
                  completedAt: null,
                  archivedAt: null,
                }]),
              })),
            })),
          })),
        }))
        .mockImplementationOnce(() => ({
          from: vi.fn(() => ({
            innerJoin: vi.fn(() => ({
              where: vi.fn().mockResolvedValue([{ totalTasks: 8, openTasks: 3 }]),
            })),
          })),
        }))
        .mockImplementationOnce(() => ({
          from: vi.fn(() => ({
            where: vi.fn().mockResolvedValue([{ hoursAllTime: '12.5', hoursThisMonth: '4.75' }]),
          })),
        }))
        .mockImplementationOnce(() => ({
          from: vi.fn(() => ({
            where: vi.fn().mockResolvedValue([{ invoicesPaid: 2, invoicesOutstanding: 1 }]),
          })),
        })),
    }

    const { getProjectWithStats } = await import('../../src/queries/projects')
    const result = await getProjectWithStats(db as never, 'tenant-1', 'project-1')

    expect(result?.customer_name).toBe('Acme Corp')
    expect(result?.stats).toEqual({
      total_tasks: 8,
      open_tasks: 3,
      hours_this_month: 4.75,
      hours_all_time: 12.5,
      unbilled_hours: 0,
      invoices_paid: 2,
      invoices_outstanding: 1,
    })
  })
})

describe('createProject', () => {
  it('adds the creator as owner even when members are omitted', async () => {
    const insertProjectReturning = vi.fn().mockResolvedValue([{
      id: 'project-1',
      tenantId: 'tenant-1',
      customerId: null,
      name: 'Internal',
      description: null,
      status: 'active',
      billingType: 'fixed',
      billingConfig: { total_amount: 1000, deposit_pct: 0 },
      currency: 'ILS',
      startDate: null,
      endDate: null,
      createdBy: 'user-1',
      createdAt: new Date('2026-06-01T00:00:00.000Z'),
      updatedAt: new Date('2026-06-01T00:00:00.000Z'),
      completedAt: null,
      archivedAt: null,
    }])
    const insertMembersValues = vi.fn(() => ({ values: vi.fn().mockResolvedValue(undefined) }))
    let insertCall = 0
    const tx = {
      insert: vi.fn(() => {
        insertCall += 1
        if (insertCall === 1) return { values: vi.fn(() => ({ returning: insertProjectReturning })) }
        if (insertCall === 2) return { values: insertMembersValues }
        return { values: vi.fn().mockResolvedValue(undefined) }
      }),
    }
    const db = { transaction: vi.fn(async (cb: (trx: typeof tx) => unknown) => cb(tx)) }

    const { createProject } = await import('../../src/queries/projects')
    await createProject(
      db as never,
      'tenant-1',
      {
        name: 'Internal',
        billing_type: 'fixed',
        billing_config: { total_amount: 1000, deposit_pct: 0 },
      },
      'user-1',
    )

    expect(insertMembersValues).toHaveBeenCalled()
    const insertedMembers = insertMembersValues.mock.calls[0][0]
    expect(insertedMembers).toEqual([
      expect.objectContaining({
        projectId: 'project-1',
        userId: 'user-1',
        role: 'owner',
      }),
    ])
  })

  it('creates a deposit milestone inside the project transaction when billing config requires it', async () => {
    const insertProjectReturning = vi.fn().mockResolvedValue([{
      id: 'project-1',
      tenantId: 'tenant-1',
      customerId: null,
      name: 'Retainer',
      description: null,
      status: 'active',
      billingType: 'fixed',
      billingConfig: { total_amount: 1000, deposit_pct: 25 },
      currency: 'ILS',
      startDate: null,
      endDate: null,
      createdBy: 'user-1',
      createdAt: new Date('2026-06-01T00:00:00.000Z'),
      updatedAt: new Date('2026-06-01T00:00:00.000Z'),
      completedAt: null,
      archivedAt: null,
    }])
    const insertMembersValues = vi.fn(() => ({ values: vi.fn().mockResolvedValue(undefined) }))
    const insertMilestoneReturning = vi.fn().mockResolvedValue([{
      id: 'milestone-1',
      tenantId: 'tenant-1',
      projectId: 'project-1',
      name: 'Project deposit',
      description: null,
      amount: '250.00',
      status: 'pending',
      dueDate: null,
      completedAt: null,
      invoiceId: null,
      position: 0,
      createdAt: new Date('2026-06-01T00:00:00.000Z'),
      createdBy: 'user-1',
      updatedAt: new Date('2026-06-01T00:00:00.000Z'),
    }])
    const insertMilestoneValues = vi.fn(() => ({ returning: insertMilestoneReturning }))
    const milestoneSelect = vi.fn(() => ({
      from: vi.fn(() => ({
        where: vi.fn(() => ({
          limit: vi.fn().mockResolvedValue([]),
        })),
      })),
    }))
    let insertCall = 0
    const tx = {
      select: milestoneSelect,
      insert: vi.fn(() => {
        insertCall += 1
        if (insertCall === 1) return { values: vi.fn(() => ({ returning: insertProjectReturning })) }
        if (insertCall === 2) return { values: insertMembersValues }
        if (insertCall === 4) return { values: insertMilestoneValues }
        return { values: vi.fn().mockResolvedValue(undefined) }
      }),
    }
    const db = { transaction: vi.fn(async (cb: (trx: typeof tx) => unknown) => cb(tx)) }

    const { createProject } = await import('../../src/queries/projects')
    await createProject(
      db as never,
      'tenant-1',
      {
        name: 'Retainer',
        billing_type: 'fixed',
        billing_config: { total_amount: 1000, deposit_pct: 25 },
      },
      'user-1',
    )

    expect(milestoneSelect).toHaveBeenCalled()
    expect(insertMilestoneValues).toHaveBeenCalled()
    expect(insertMilestoneReturning).toHaveBeenCalled()
    const insertedMilestone = insertMilestoneValues.mock.calls[0][0]
    expect(insertedMilestone).toEqual(expect.objectContaining({
      projectId: 'project-1',
      name: 'Project deposit',
      amount: '250.00',
      position: 0,
      createdBy: 'user-1',
    }))
  })

  it('does not create a deposit milestone for non-fixed projects even if billing_config carries deposit-like fields', async () => {
    const insertProjectReturning = vi.fn().mockResolvedValue([{
      id: 'project-1',
      tenantId: 'tenant-1',
      customerId: null,
      name: 'Hourly engagement',
      description: null,
      status: 'active',
      billingType: 'hourly',
      billingConfig: { rate_per_hour: 150, total_amount: 1000, deposit_pct: 25 },
      currency: 'ILS',
      startDate: null,
      endDate: null,
      createdBy: 'user-1',
      createdAt: new Date('2026-06-01T00:00:00.000Z'),
      updatedAt: new Date('2026-06-01T00:00:00.000Z'),
      completedAt: null,
      archivedAt: null,
    }])
    const insertMembersValues = vi.fn(() => ({ values: vi.fn().mockResolvedValue(undefined) }))
    const insertMilestoneValues = vi.fn(() => ({ returning: vi.fn().mockResolvedValue([]) }))
    let insertCall = 0
    const tx = {
      select: vi.fn(),
      insert: vi.fn(() => {
        insertCall += 1
        if (insertCall === 1) return { values: vi.fn(() => ({ returning: insertProjectReturning })) }
        if (insertCall === 2) return { values: insertMembersValues }
        if (insertCall === 4) return { values: insertMilestoneValues }
        return { values: vi.fn().mockResolvedValue(undefined) }
      }),
    }
    const db = { transaction: vi.fn(async (cb: (trx: typeof tx) => unknown) => cb(tx)) }

    const { createProject } = await import('../../src/queries/projects')
    await createProject(
      db as never,
      'tenant-1',
      {
        name: 'Hourly engagement',
        billing_type: 'hourly',
        billing_config: { rate_per_hour: 150 } as never,
      },
      'user-1',
    )

    expect(insertMilestoneValues).not.toHaveBeenCalled()
  })
})

describe('serializeProject lifecycle fields', () => {
  it('includes completed_at and archived_at in the API shape', async () => {
    const { serializeProject } = await import('../../src/serialize/projects')

    const result = serializeProject({
      id: 'project-1',
      tenantId: 'tenant-1',
      customerId: null,
      name: 'Marathon',
      description: null,
      status: 'archived',
      billingType: 'hourly',
      billingConfig: { rate_per_hour: 100 },
      currency: 'ILS',
      startDate: null,
      endDate: null,
      createdBy: 'user-1',
      createdAt: new Date('2026-06-01T00:00:00.000Z'),
      updatedAt: new Date('2026-06-02T00:00:00.000Z'),
      completedAt: new Date('2026-06-03T00:00:00.000Z'),
      archivedAt: new Date('2026-06-04T00:00:00.000Z'),
    } as never)

    expect(result.completed_at).toBe('2026-06-03T00:00:00.000Z')
    expect(result.archived_at).toBe('2026-06-04T00:00:00.000Z')
  })
})
