/* ============================================================
 * Copyright (c) 2026 RAVAPI Soluções. www.ravapi.com
 * Login local (docs/decisoes.md §18) — não é o login único da RAVAPI,
 * que ainda não existe. É a implementação de IdentityProvider que roda
 * hoje; trocar pelo login único depois é troca de provider, não reescrita.
 * ============================================================ */

import { comContexto, emTransacao, type Pool } from '../db/client.ts';
import { verificarSenha } from '../core/senha.ts';
import { assinarSessao, type ClaimsSessao } from '../core/sessao.ts';
import { CredenciaisInvalidas, ErroDeNegocio } from '../core/errors.ts';
import type { UsuarioRef } from '../core/identity.ts';

export interface ResultadoLogin {
  token: string;
  usuario: UsuarioRef;
}

export class AuthService {
  constructor(
    private readonly pool: Pool,
    private readonly segredoSessao: string,
  ) {}

  async login(email: string, senha: string): Promise<ResultadoLogin> {
    const conn = await this.pool.connect();
    let candidatos: any[];
    try {
      const { rows } = await conn.query(
        'SELECT * FROM recibos.buscar_usuario_por_email($1)', [email.trim()],
      );
      candidatos = rows;
    } finally {
      conn.release();
    }

    // Mesma mensagem pra "não existe" e "senha errada" — CredenciaisInvalidas
    // já cuida disso. Aqui só o caso extra: e-mail em mais de um tenant.
    if (candidatos.length === 0) throw new CredenciaisInvalidas();
    if (candidatos.length > 1) {
      throw new ErroDeNegocio(
        'Este e-mail existe em mais de uma empresa. Fale com o suporte pra identificar qual.',
        'LOGIN_AMBIGUO', 409,
      );
    }

    const u = candidatos[0];
    if (!u.senha_hash || !(await verificarSenha(senha, u.senha_hash))) {
      throw new CredenciaisInvalidas();
    }

    const seriesPermitidas = await comContexto({ tenantId: u.tenant_id }, () =>
      emTransacao(this.pool, async (x) => {
        const { rows } = await x.query(
          'SELECT series_id FROM recibos.user_series_permissions WHERE user_id = $1', [u.id],
        );
        return rows.map((r: { series_id: string }) => r.series_id);
      }));

    const usuario: UsuarioRef = {
      id: u.id, nome: u.nome, email: u.email, papel: u.papel, seriesPermitidas,
    };

    const claims: Omit<ClaimsSessao, 'exp' | 'iat'> = {
      sub: u.id, tenant_id: u.tenant_id, papel: u.papel,
      nome: u.nome, email: u.email, seriesPermitidas,
    };
    return { token: assinarSessao(claims, this.segredoSessao), usuario };
  }
}
