import { describe, it, expect, vi } from 'vitest';
import { OpenCashSessionUseCase } from '../application/open-cash-session.js';
import { CloseCashSessionUseCase } from '../application/close-cash-session.js';
import { CashSessionAlreadyOpenError, TerminalDisabledError, TerminalNotBoundError } from '../domain/errors.js';
import type { PosCashSessionRepository, PosTerminalRepository } from '../domain/ports.js';

const mockTerminal = {
  id: 't1',
  storeId: 's1',
  name: 'TPV-1',
  bindingCode: 'ABC123',
  boundAt: new Date(),
  status: 'active' as const,
  interfaceMode: 'auto' as const,
  settings: {},
  lastSeenAt: null,
  createdAt: new Date(),
  updatedAt: new Date(),
};

const mockSession = {
  id: 'ses1',
  terminalId: 't1',
  storeId: 's1',
  userId: 'u1',
  status: 'OPEN' as const,
  openedAt: new Date(),
  closedAt: null,
  openingCashCents: 5000,
  closingCashCents: null,
  expectedCashCents: null,
  actualCashCents: null,
  differenceCents: null,
  notes: null,
  createdAt: new Date(),
  updatedAt: new Date(),
};

describe('OpenCashSessionUseCase', () => {
  it('opens session when terminal is bound and active', async () => {
    const sessionRepo = {
      findOpenByTerminal: vi.fn().mockResolvedValue(undefined),
      open: vi.fn().mockResolvedValue(mockSession),
    } as unknown as PosCashSessionRepository;
    const terminalRepo = {
      findById: vi.fn().mockResolvedValue(mockTerminal),
    } as unknown as PosTerminalRepository;
    const uc = new OpenCashSessionUseCase(sessionRepo, terminalRepo);
    const result = await uc.execute({ terminalId: 't1', userId: 'u1', openingCashCents: 5000 });
    expect(result.id).toBe('ses1');
    expect(sessionRepo.open).toHaveBeenCalled();
  });

  it('throws when terminal already has open session', async () => {
    const sessionRepo = {
      findOpenByTerminal: vi.fn().mockResolvedValue(mockSession),
    } as unknown as PosCashSessionRepository;
    const terminalRepo = { findById: vi.fn().mockResolvedValue(mockTerminal) } as unknown as PosTerminalRepository;
    const uc = new OpenCashSessionUseCase(sessionRepo, terminalRepo);
    await expect(uc.execute({ terminalId: 't1', userId: 'u1', openingCashCents: 0 }))
      .rejects.toThrow(CashSessionAlreadyOpenError);
  });

  it('throws when terminal is disabled', async () => {
    const disabledTerminal = { ...mockTerminal, status: 'disabled' as const };
    const sessionRepo = { findOpenByTerminal: vi.fn().mockResolvedValue(undefined) } as unknown as PosCashSessionRepository;
    const terminalRepo = { findById: vi.fn().mockResolvedValue(disabledTerminal) } as unknown as PosTerminalRepository;
    const uc = new OpenCashSessionUseCase(sessionRepo, terminalRepo);
    await expect(uc.execute({ terminalId: 't1', userId: 'u1', openingCashCents: 0 }))
      .rejects.toThrow(TerminalDisabledError);
  });

  it('throws when terminal is not bound', async () => {
    const unboundTerminal = { ...mockTerminal, bindingCode: null };
    const sessionRepo = { findOpenByTerminal: vi.fn().mockResolvedValue(undefined) } as unknown as PosCashSessionRepository;
    const terminalRepo = { findById: vi.fn().mockResolvedValue(unboundTerminal) } as unknown as PosTerminalRepository;
    const uc = new OpenCashSessionUseCase(sessionRepo, terminalRepo);
    await expect(uc.execute({ terminalId: 't1', userId: 'u1', openingCashCents: 0 }))
      .rejects.toThrow(TerminalNotBoundError);
  });
});

describe('CloseCashSessionUseCase', () => {
  it('closes an open session', async () => {
    const closedSession = { ...mockSession, status: 'CLOSED' as const, closedAt: new Date(), closingCashCents: 5000, actualCashCents: 4900, differenceCents: -100 };
    const sessionRepo = {
      findById: vi.fn().mockResolvedValue(mockSession),
      close: vi.fn().mockResolvedValue(closedSession),
    } as unknown as PosCashSessionRepository;
    const uc = new CloseCashSessionUseCase(sessionRepo);
    const result = await uc.execute({ sessionId: 'ses1', closingCashCents: 5000, actualCashCents: 4900, notes: 'Falta' });
    expect(result.status).toBe('CLOSED');
  });

  it('throws when session already closed', async () => {
    const sessionRepo = {
      findById: vi.fn().mockResolvedValue({ ...mockSession, status: 'CLOSED' as const }),
    } as unknown as PosCashSessionRepository;
    const uc = new CloseCashSessionUseCase(sessionRepo);
    await expect(uc.execute({ sessionId: 'ses1', closingCashCents: 0, actualCashCents: 0 }))
      .rejects.toThrow();
  });
});
