import type pg from 'pg';
import type { PosCashSessionRepository } from '../domain/ports.js';
import type { PosCashSession, OpenCashSessionInput, CloseCashSessionInput } from '../domain/cash-session.js';

interface SessionRow {
  id: string;
  terminal_id: string;
  store_id: string;
  user_id: string;
  status: 'OPEN' | 'CLOSED';
  opened_at: Date;
  closed_at: Date | null;
  opening_cash_cents: number;
  closing_cash_cents: number | null;
  expected_cash_cents: number | null;
  actual_cash_cents: number | null;
  difference_cents: number | null;
  notes: string | null;
  created_at: Date;
  updated_at: Date;
}

function toSession(row: SessionRow): PosCashSession {
  return {
    id: row.id,
    terminalId: row.terminal_id,
    storeId: row.store_id,
    userId: row.user_id,
    status: row.status,
    openedAt: row.opened_at,
    closedAt: row.closed_at,
    openingCashCents: row.opening_cash_cents,
    closingCashCents: row.closing_cash_cents,
    expectedCashCents: row.expected_cash_cents,
    actualCashCents: row.actual_cash_cents,
    differenceCents: row.difference_cents,
    notes: row.notes,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

export class PgCashSessionRepository implements PosCashSessionRepository {
  constructor(private readonly pool: pg.Pool) {}

  async findById(id: string): Promise<PosCashSession | undefined> {
    const result = await this.pool.query<SessionRow>(
      'SELECT * FROM pos_cash_sessions WHERE id = $1',
      [id],
    );
    return result.rows[0] ? toSession(result.rows[0]) : undefined;
  }

  async findOpenByTerminal(terminalId: string): Promise<PosCashSession | undefined> {
    const result = await this.pool.query<SessionRow>(
      "SELECT * FROM pos_cash_sessions WHERE terminal_id = $1 AND status = 'OPEN'",
      [terminalId],
    );
    return result.rows[0] ? toSession(result.rows[0]) : undefined;
  }

  async open(input: OpenCashSessionInput): Promise<PosCashSession> {
    // Get store_id from terminal
    const terminal = await this.pool.query<{ store_id: string }>(
      'SELECT store_id FROM pos_terminals WHERE id = $1',
      [input.terminalId],
    );
    if (!terminal.rows[0]) throw new Error(`Terminal ${input.terminalId} not found`);
    const storeId = terminal.rows[0].store_id;

    const result = await this.pool.query<SessionRow>(
      `INSERT INTO pos_cash_sessions (terminal_id, store_id, user_id, opening_cash_cents)
       VALUES ($1, $2, $3, $4) RETURNING *`,
      [input.terminalId, storeId, input.userId, input.openingCashCents],
    );
    if (!result.rows[0]) throw new Error('Failed to create session');
    return toSession(result.rows[0]);
  }

  async close(input: CloseCashSessionInput): Promise<PosCashSession> {
    const difference = input.actualCashCents - input.closingCashCents;
    const result = await this.pool.query<SessionRow>(
      `UPDATE pos_cash_sessions
       SET status = 'CLOSED', closed_at = now(),
           closing_cash_cents = $2, actual_cash_cents = $3,
           difference_cents = $4, notes = $5, updated_at = now()
       WHERE id = $1 RETURNING *`,
      [input.sessionId, input.closingCashCents, input.actualCashCents, difference, input.notes ?? null],
    );
    if (!result.rows[0]) throw new Error(`Session ${input.sessionId} not found`);
    return toSession(result.rows[0]);
  }

  async listByStore(
    storeId: string,
    limit = 50,
    offset = 0,
  ): Promise<{ sessions: PosCashSession[]; total: number }> {
    const [countResult, listResult] = await Promise.all([
      this.pool.query<{ count: string }>(
        'SELECT COUNT(*) as count FROM pos_cash_sessions WHERE store_id = $1',
        [storeId],
      ),
      this.pool.query<SessionRow>(
        `SELECT * FROM pos_cash_sessions WHERE store_id = $1
         ORDER BY opened_at DESC LIMIT $2 OFFSET $3`,
        [storeId, limit, offset],
      ),
    ]);
    return {
      sessions: listResult.rows.map(toSession),
      total: parseInt(countResult.rows[0]?.count ?? '0', 10),
    };
  }
}
